[cli] Allow state reporting control via env (#16746)

This commit is contained in:
Clyde Stubbs
2026-06-02 07:04:35 +10:00
committed by GitHub
parent ab46f8bd74
commit d7d20f4f6b
2 changed files with 96 additions and 18 deletions

View File

@@ -1351,6 +1351,19 @@ def _validate_bootloader_binary(binary: Path) -> None:
)
def _should_subscribe_states(args: ArgsProtocol) -> bool:
"""Determine whether entity state changes should be shown in log output.
The ``--states``/``--no-states`` command line flags take precedence. When
neither is given, the ``ESPHOME_LOG_STATES`` environment variable controls
the behavior, defaulting to showing states.
"""
states = getattr(args, "states", None)
if states is not None:
return states
return get_bool_env("ESPHOME_LOG_STATES", True)
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
@@ -1380,7 +1393,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
return run_logs(
config,
network_devices,
subscribe_states=not getattr(args, "no_states", False),
subscribe_states=_should_subscribe_states(args),
)
if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging():
@@ -2019,6 +2032,29 @@ SIMPLE_CONFIG_ACTIONS = [
]
def _add_states_args(parser: argparse.ArgumentParser) -> None:
"""Add mutually exclusive ``--states``/``--no-states`` flags to a parser.
When neither flag is given, the ``ESPHOME_LOG_STATES`` environment variable
controls whether entity state changes are shown (defaulting to showing them).
"""
states_group = parser.add_mutually_exclusive_group()
states_group.add_argument(
"--states",
dest="states",
action="store_true",
default=None,
help="Show entity state changes in log output (overrides ESPHOME_LOG_STATES).",
)
states_group.add_argument(
"--no-states",
dest="states",
action="store_false",
default=None,
help="Do not show entity state changes in log output.",
)
def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument(
@@ -2195,11 +2231,7 @@ def parse_args(argv):
help="Reset the device before starting serial logs.",
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
)
parser_logs.add_argument(
"--no-states",
action="store_true",
help="Do not show entity state changes in log output.",
)
_add_states_args(parser_logs)
parser_discover = subparsers.add_parser(
"discover",
@@ -2231,11 +2263,7 @@ def parse_args(argv):
"--no-logs", help="Disable starting logs.", action="store_true"
)
parser_run.add_argument(
"--no-states",
action="store_true",
help="Do not show entity state changes in log output.",
)
_add_states_args(parser_run)
parser_run.add_argument(
"--reset",

View File

@@ -1269,6 +1269,7 @@ class MockArgs:
ota_platform: str | None = None
partition_table: bool = False
bootloader: bool = False
states: bool | None = None
def test_upload_program_serial_esp32(
@@ -2663,7 +2664,7 @@ def test_show_logs_api_no_states(
mock_run_logs.return_value = 0
args = MockArgs()
args.no_states = True
args.states = False
devices = ["192.168.1.100"]
result = show_logs(CORE.config, args, devices)
@@ -5989,19 +5990,68 @@ def test_upload_using_esptool_subprocess_passes_crystal_callback(
def test_parse_args_run_no_states() -> None:
"""Test that --no-states is parsed for the run command."""
args = parse_args(["esphome", "run", "--no-states", "device.yaml"])
assert args.no_states is True
assert args.states is False
def test_parse_args_run_no_states_default() -> None:
"""Test that no_states defaults to False for the run command."""
def test_parse_args_run_states() -> None:
"""Test that --states is parsed for the run command."""
args = parse_args(["esphome", "run", "--states", "device.yaml"])
assert args.states is True
def test_parse_args_run_states_default() -> None:
"""Test that states defaults to None (unset) for the run command."""
args = parse_args(["esphome", "run", "device.yaml"])
assert args.no_states is False
assert args.states is None
def test_parse_args_logs_no_states() -> None:
"""Test that --no-states is parsed for the logs command."""
args = parse_args(["esphome", "logs", "--no-states", "device.yaml"])
assert args.no_states is True
assert args.states is False
def test_parse_args_logs_states() -> None:
"""Test that --states is parsed for the logs command."""
args = parse_args(["esphome", "logs", "--states", "device.yaml"])
assert args.states is True
def test_should_subscribe_states_default() -> None:
"""Test that states are shown by default when nothing is set."""
from esphome.__main__ import _should_subscribe_states
args = parse_args(["esphome", "logs", "device.yaml"])
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("ESPHOME_LOG_STATES", None)
assert _should_subscribe_states(args) is True
def test_should_subscribe_states_env_suppresses() -> None:
"""Test that ESPHOME_LOG_STATES=false suppresses states by default."""
from esphome.__main__ import _should_subscribe_states
args = parse_args(["esphome", "logs", "device.yaml"])
with patch.dict(os.environ, {"ESPHOME_LOG_STATES": "false"}):
assert _should_subscribe_states(args) is False
def test_should_subscribe_states_flag_overrides_env() -> None:
"""Test that --states overrides ESPHOME_LOG_STATES=false."""
from esphome.__main__ import _should_subscribe_states
args = parse_args(["esphome", "logs", "--states", "device.yaml"])
with patch.dict(os.environ, {"ESPHOME_LOG_STATES": "false"}):
assert _should_subscribe_states(args) is True
def test_should_subscribe_states_no_flag_overrides_env() -> None:
"""Test that --no-states overrides ESPHOME_LOG_STATES=true."""
from esphome.__main__ import _should_subscribe_states
args = parse_args(["esphome", "logs", "--no-states", "device.yaml"])
with patch.dict(os.environ, {"ESPHOME_LOG_STATES": "true"}):
assert _should_subscribe_states(args) is False
@patch("esphome.components.api.client.run_logs")
@@ -6020,7 +6070,7 @@ def test_command_run_passes_no_states_to_show_logs(
mock_run_logs.return_value = 0
args = MockArgs()
args.no_states = True
args.states = False
args.no_logs = False
args.device = None