mirror of
https://github.com/esphome/esphome.git
synced 2026-07-11 01:15:33 +00:00
[core] Lazy-load zeroconf/writer/yaml_util in __main__
Every \`esphome\` CLI invocation pays the cost of whatever \`esphome/__main__.py\` imports at module scope before the requested command even runs. This moves three heavy imports into the functions that actually use them: - \`esphome.zeroconf\` (discover_mdns_devices): only needed for the name_add_mac_suffix OTA discovery path. - \`esphome.writer\`: only needed by compile/clean paths. - \`esphome.yaml_util\`: only needed by codegen, config dump, and rename. Local measurement drops from ~75ms to ~47ms (-37%) for a cold \`python -c 'import esphome.__main__'\`. The zeroconf chain alone accounts for most of the gain — the case that motivated the CI budget check. Also adds a module-level note explaining the intent so future PRs don't innocently promote these back to the top. Retargets four test patches from \`esphome.__main__.discover_mdns_devices\` to \`esphome.zeroconf.discover_mdns_devices\` now that the symbol is only bound inside the function that calls it.
This commit is contained in:
+23
-2
@@ -21,7 +21,7 @@ import argcomplete
|
||||
# Note: Do not import modules from esphome.components here, as this would
|
||||
# cause them to be loaded before external components are processed, resulting
|
||||
# in the built-in version being used instead of the external component one.
|
||||
from esphome import const, writer, yaml_util
|
||||
from esphome import const
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
||||
from esphome.const import (
|
||||
@@ -72,7 +72,12 @@ from esphome.util import (
|
||||
run_external_process,
|
||||
safe_print,
|
||||
)
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
# Keep expensive imports (zeroconf, writer, yaml_util, etc.) out of this
|
||||
# module's top level. Every `esphome` invocation — including fast paths
|
||||
# like `esphome version` — pays the cost of what's imported here before
|
||||
# any command runs. Import inside the function that needs it instead.
|
||||
# `script/check_import_time.py` enforces a budget in CI.
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -241,6 +246,8 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
"""
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
|
||||
return None
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
_LOGGER.info("Discovering devices...")
|
||||
if not (discovered := discover_mdns_devices(CORE.name)):
|
||||
_LOGGER.warning(
|
||||
@@ -661,6 +668,8 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
from esphome import yaml_util
|
||||
|
||||
coro = coroutine(comp.to_code)
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
@@ -680,6 +689,8 @@ def wrap_to_code(name, comp):
|
||||
|
||||
|
||||
def write_cpp(config: ConfigType, native_idf: bool = False) -> int:
|
||||
from esphome import writer
|
||||
|
||||
if not get_bool_env(ENV_NOGITIGNORE):
|
||||
writer.write_gitignore()
|
||||
|
||||
@@ -702,6 +713,8 @@ def generate_cpp_contents(config: ConfigType) -> None:
|
||||
|
||||
|
||||
def write_cpp_file(native_idf: bool = False) -> int:
|
||||
from esphome import writer
|
||||
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
writer.write_cpp(code_s)
|
||||
|
||||
@@ -1180,6 +1193,8 @@ def command_wizard(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import yaml_util
|
||||
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
output = yaml_util.dump(config, args.show_secrets)
|
||||
@@ -1321,6 +1336,8 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
|
||||
def command_clean_all(args: ArgsProtocol) -> int | None:
|
||||
from esphome import writer
|
||||
|
||||
try:
|
||||
writer.clean_all(args.configuration)
|
||||
except OSError as err:
|
||||
@@ -1336,6 +1353,8 @@ def command_version(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import writer
|
||||
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
@@ -1538,6 +1557,8 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
|
||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import yaml_util
|
||||
|
||||
new_name = args.name
|
||||
for c in new_name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
|
||||
@@ -2605,7 +2605,7 @@ def test_choose_upload_log_host_discovers_mac_suffix_devices(tmp_path: Path) ->
|
||||
}
|
||||
with (
|
||||
patch(
|
||||
"esphome.__main__.discover_mdns_devices", return_value=discovered
|
||||
"esphome.zeroconf.discover_mdns_devices", return_value=discovered
|
||||
) as mock_discover,
|
||||
patch(
|
||||
"esphome.__main__.choose_prompt", return_value="mydevice-abc123.local"
|
||||
@@ -2653,7 +2653,7 @@ def test_choose_upload_log_host_mac_suffix_no_devices_found(
|
||||
)
|
||||
|
||||
with (
|
||||
patch("esphome.__main__.discover_mdns_devices", return_value={}),
|
||||
patch("esphome.zeroconf.discover_mdns_devices", return_value={}),
|
||||
caplog.at_level(logging.WARNING, logger="esphome.__main__"),
|
||||
pytest.raises(EsphomeError),
|
||||
):
|
||||
@@ -2686,7 +2686,7 @@ def test_choose_upload_log_host_default_ota_discovers_mac_suffix(
|
||||
"mydevice-def456.local": ["10.0.0.2"],
|
||||
}
|
||||
with patch(
|
||||
"esphome.__main__.discover_mdns_devices", return_value=discovered
|
||||
"esphome.zeroconf.discover_mdns_devices", return_value=discovered
|
||||
) as mock_discover:
|
||||
result = choose_upload_log_host(
|
||||
default="OTA",
|
||||
@@ -2715,7 +2715,7 @@ def test_choose_upload_log_host_default_ota_no_suffix_discovery(
|
||||
name="mydevice",
|
||||
)
|
||||
|
||||
with patch("esphome.__main__.discover_mdns_devices") as mock_discover:
|
||||
with patch("esphome.zeroconf.discover_mdns_devices") as mock_discover:
|
||||
result = choose_upload_log_host(
|
||||
default="OTA",
|
||||
check_default=None,
|
||||
|
||||
Reference in New Issue
Block a user