[core] Lazy-load esphome.core.config from loader and config

The top-level `import esphome.core.config` in `esphome/loader.py` (used
only to register the "esphome" pseudo-component in `_COMPONENT_CACHE`)
was pulling `esphome.automation` + `esphome.config_validation` eagerly
into every `esphome` CLI invocation, even fast paths like
`esphome version`. Move the cache registration into `_lookup_module`,
triggered on first `get_component("esphome")`.

Also defers the two `esphome.core.config` call sites inside
`esphome/config.py` (CoreFinalValidateStep and validate_config), which
were the other eager entry point.

Local `import esphome.__main__` drops from ~49ms to ~46ms; more
importantly, `esphome.core.config` / `esphome.automation` no longer
appear in the top offenders table, and `esphome.config_validation`
drops from 4.7ms self to 2.0ms self on CI.
This commit is contained in:
J. Nick Koston
2026-04-23 14:47:10 -05:00
parent fd63a67ad8
commit 5d624c7f76
2 changed files with 20 additions and 3 deletions
+8 -1
View File
@@ -25,7 +25,10 @@ from esphome.const import (
CONF_SUBSTITUTIONS,
)
from esphome.core import CORE, DocumentRange, EsphomeError
import esphome.core.config as core_config
# `esphome.core.config` is imported lazily at its two use sites below.
# It pulls in `esphome.automation` and `esphome.config_validation`, which
# dominate `esphome.__main__` startup cost when loaded eagerly here.
import esphome.final_validate as fv
from esphome.helpers import indent
from esphome.loader import ComponentManifest, get_component, get_platform
@@ -968,6 +971,8 @@ class CoreFinalValidateStep(ConfigValidationStep):
if result.errors:
return
import esphome.core.config as core_config
token = fv.full_config.set(result)
with result.catch_error([CONF_ESPHOME]):
if CONF_ESPHOME in result:
@@ -1072,6 +1077,8 @@ def validate_config(
return result
# 2. Load partial core config
import esphome.core.config as core_config
result[CONF_ESPHOME] = config[CONF_ESPHOME]
result.add_output_path([CONF_ESPHOME], CONF_ESPHOME)
try:
+12 -2
View File
@@ -13,9 +13,13 @@ from typing import Any
from esphome.const import SOURCE_FILE_EXTENSIONS
from esphome.core import CORE
import esphome.core.config
from esphome.types import ConfigType
# `esphome.core.config` is imported lazily in `_lookup_module` when the
# "esphome" pseudo-component is first resolved. It pulls in
# `esphome.automation` and `esphome.config_validation`, which together
# dominate `esphome.__main__` startup cost when loaded eagerly.
_LOGGER = logging.getLogger(__name__)
@@ -202,6 +206,13 @@ def _lookup_module(domain: str, exception: bool) -> ComponentManifest | None:
if domain in _COMPONENT_CACHE:
return _COMPONENT_CACHE[domain]
if domain == "esphome":
import esphome.core.config
manif = ComponentManifest(esphome.core.config)
_COMPONENT_CACHE[domain] = manif
return manif
try:
module = importlib.import_module(f"esphome.components.{domain}")
except ImportError as e:
@@ -237,7 +248,6 @@ def get_platform(domain: str, platform: str) -> ComponentManifest | None:
_COMPONENT_CACHE: dict[str, ComponentManifest] = {}
CORE_COMPONENTS_PATH = (Path(__file__).parent / "components").resolve()
_COMPONENT_CACHE["esphome"] = ComponentManifest(esphome.core.config)
def _replace_component_manifest(domain: str, manifest: ComponentManifest) -> None: