mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 13:56:10 +00:00
[core] Add cg.add_optional for optional plain config values (#21180)
This commit is contained in:
@@ -80,13 +80,15 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
```python
|
||||
# Bad - looks up CONF_BLAH twice
|
||||
if CONF_BLAH in config:
|
||||
cg.add(var.set_blah(config[CONF_BLAH]))
|
||||
cg.add(var.set_blah_ms(config[CONF_BLAH].total_milliseconds))
|
||||
|
||||
# Good - single lookup, value bound inline
|
||||
if (blah := config.get(CONF_BLAH)) is not None:
|
||||
cg.add(var.set_blah(blah))
|
||||
cg.add(var.set_blah_ms(blah.total_milliseconds))
|
||||
```
|
||||
The same applies to `while` loops and comprehensions where it avoids recomputing a value. Don't contort code to use it — reach for `:=` only when it genuinely cuts repetition or an extra assignment line.
|
||||
When the setter takes the value unchanged, use `cg.add_optional(var.set_blah, config.get(CONF_BLAH))`
|
||||
instead; it is not awaited, and it skips only `None`, so `0` and `False` are still set.
|
||||
|
||||
* **C++ Field Visibility:**
|
||||
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
|
||||
|
||||
@@ -53,6 +53,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
with_local_variable,
|
||||
)
|
||||
from esphome.cpp_helpers import ( # noqa: F401
|
||||
add_optional,
|
||||
add_pin,
|
||||
add_variable,
|
||||
build_registry_entry,
|
||||
|
||||
@@ -251,6 +251,13 @@ async def add_variable(
|
||||
return var
|
||||
|
||||
|
||||
def add_optional[T](setter: Callable[[T], Expression], value: T | None) -> T | None:
|
||||
"""Pass ``value`` to ``setter`` and return it; skip None."""
|
||||
if value is not None:
|
||||
add(setter(value))
|
||||
return value
|
||||
|
||||
|
||||
def set_setup_priority(var, priority: float) -> None:
|
||||
"""Emit a setup-priority override for the given component.
|
||||
|
||||
|
||||
@@ -339,3 +339,26 @@ async def test_add_helpers_pass_value_to_setter(
|
||||
lookup_mock.assert_awaited_once_with(arg)
|
||||
setter.assert_called_once_with(value)
|
||||
add_mock.assert_called_once_with(setter.return_value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [0, False, "", 5])
|
||||
def test_add_optional_passes_value_to_setter(
|
||||
monkeypatch: pytest.MonkeyPatch, value: Any
|
||||
) -> None:
|
||||
add_mock = Mock()
|
||||
monkeypatch.setattr(ch, "add", add_mock)
|
||||
setter = Mock()
|
||||
|
||||
assert ch.add_optional(setter, value) is value
|
||||
setter.assert_called_once_with(value)
|
||||
add_mock.assert_called_once_with(setter.return_value)
|
||||
|
||||
|
||||
def test_add_optional_skips_none(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
add_mock = Mock()
|
||||
monkeypatch.setattr(ch, "add", add_mock)
|
||||
setter = Mock()
|
||||
|
||||
assert ch.add_optional(setter, None) is None
|
||||
setter.assert_not_called()
|
||||
add_mock.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user