mirror of
https://github.com/esphome/esphome.git
synced 2026-08-23 06:36:23 +00:00
[sensor] Defer integration imports out of module scope
This commit is contained in:
@@ -3,7 +3,6 @@ import math
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_B_CONSTANT
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -110,10 +109,12 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WIND_SPEED,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.config import UNIT_OF_MEASUREMENT_MAX_LENGTH
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
lazy_load_validator,
|
||||
mqtt_component_class,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
@@ -314,12 +315,14 @@ validate_icon = cv.icon
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
.extend(zigbee.SENSOR_SCHEMA)
|
||||
.extend(entity_helpers.ZIGBEE_SENSOR_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt_component_class("MQTTSensorComponent")
|
||||
),
|
||||
cv.GenerateID(): cv.declare_id(Sensor),
|
||||
cv.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT, visibility=cv.Visibility.ADVANCED
|
||||
@@ -359,7 +362,7 @@ _SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
|
||||
_SENSOR_SCHEMA.add_extra(zigbee.validate_sensor)
|
||||
_SENSOR_SCHEMA.add_extra(lazy_load_validator("zigbee", "validate_sensor"))
|
||||
|
||||
|
||||
def sensor_schema(
|
||||
@@ -980,6 +983,8 @@ async def setup_sensor_core_(var, config):
|
||||
CORE.add_job(_build_sensor_automations, var, config)
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
from esphome.components import mqtt
|
||||
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
@@ -992,9 +997,14 @@ async def setup_sensor_core_(var, config):
|
||||
cg.add(mqtt_.set_expire_after(expire_after))
|
||||
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
from esphome.components import web_server
|
||||
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
await zigbee.setup_sensor(var, config)
|
||||
if "zigbee" in CORE.loaded_integrations:
|
||||
from esphome.components import zigbee
|
||||
|
||||
await zigbee.setup_sensor(var, config)
|
||||
|
||||
|
||||
async def register_sensor(var, config):
|
||||
|
||||
@@ -47,7 +47,7 @@ from .zigbee_esp32 import (
|
||||
validate_sensor_esp32,
|
||||
zigbee_require_vfs_select,
|
||||
)
|
||||
from .zigbee_zephyr import zephyr_number, zephyr_sensor, zephyr_switch
|
||||
from .zigbee_zephyr import zephyr_number, zephyr_switch
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,7 +60,7 @@ CONFLICTS_WITH = ["openthread"]
|
||||
# them without importing this package; re-exported here for existing consumers.
|
||||
BASE_SCHEMA = entity_helpers.ZIGBEE_BASE_ENTITY_SCHEMA
|
||||
BINARY_SENSOR_SCHEMA = entity_helpers.ZIGBEE_BINARY_SENSOR_SCHEMA
|
||||
SENSOR_SCHEMA = cv.Schema({}).extend(BASE_SCHEMA).extend(zephyr_sensor)
|
||||
SENSOR_SCHEMA = entity_helpers.ZIGBEE_SENSOR_SCHEMA
|
||||
SWITCH_SCHEMA = cv.Schema({}).extend(zephyr_switch)
|
||||
NUMBER_SCHEMA = cv.Schema({}).extend(zephyr_number)
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
from esphome.const import ( # noqa: F401 # pylint: disable=unused-import
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
CONF_ZIGBEE_ID,
|
||||
CONF_ZIGBEE_SENSOR,
|
||||
)
|
||||
|
||||
CONF_MAX_EP_NUMBER_ZEPHYR = 8
|
||||
CONF_ZIGBEE_SENSOR = "zigbee_sensor"
|
||||
CONF_ZIGBEE_SWITCH = "zigbee_switch"
|
||||
CONF_ZIGBEE_NUMBER = "zigbee_number"
|
||||
CONF_SLEEPY = "sleepy"
|
||||
|
||||
@@ -57,15 +57,6 @@ ZigbeeSensor = zigbee_ns.class_("ZigbeeSensor", cg.Component)
|
||||
ZigbeeSwitch = zigbee_ns.class_("ZigbeeSwitch", cg.Component)
|
||||
ZigbeeNumber = zigbee_ns.class_("ZigbeeNumber", cg.Component)
|
||||
|
||||
zephyr_sensor = cv.Schema(
|
||||
{
|
||||
cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent),
|
||||
cv.OnlyWith(CONF_ZIGBEE_SENSOR, ["nrf52", "zigbee"]): cv.declare_id(
|
||||
ZigbeeSensor
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
zephyr_switch = cv.Schema(
|
||||
{
|
||||
cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent),
|
||||
|
||||
@@ -1151,6 +1151,7 @@ CONF_YEAR = "year"
|
||||
CONF_ZERO = "zero"
|
||||
CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor"
|
||||
CONF_ZIGBEE_ID = "zigbee_id"
|
||||
CONF_ZIGBEE_SENSOR = "zigbee_sensor"
|
||||
|
||||
TYPE_GIT = "git"
|
||||
TYPE_LOCAL = "local"
|
||||
|
||||
@@ -27,6 +27,7 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER_ID,
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
CONF_ZIGBEE_ID,
|
||||
CONF_ZIGBEE_SENSOR,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.config import (
|
||||
@@ -743,6 +744,7 @@ ZIGBEE_BASE_ENTITY_SCHEMA = cv.Schema(
|
||||
# class against the owning declaration in zigbee_zephyr.
|
||||
_ZIGBEE_ENTITY_CLASSES = {
|
||||
"binary_sensor": (CONF_ZIGBEE_BINARY_SENSOR, "ZigbeeBinarySensor"),
|
||||
"sensor": (CONF_ZIGBEE_SENSOR, "ZigbeeSensor"),
|
||||
}
|
||||
|
||||
|
||||
@@ -762,6 +764,8 @@ def _zigbee_entity_schema(platform: str) -> cv.Schema:
|
||||
|
||||
ZIGBEE_BINARY_SENSOR_SCHEMA = _zigbee_entity_schema("binary_sensor")
|
||||
|
||||
ZIGBEE_SENSOR_SCHEMA = _zigbee_entity_schema("sensor")
|
||||
|
||||
|
||||
def lazy_load_validator(
|
||||
component: str, name: str
|
||||
|
||||
+1
-1
@@ -557,7 +557,7 @@ def lint_constants_usage():
|
||||
# Maximum allowed CONF_ constants in esphome/const.py.
|
||||
# This file is frozen — new constants go in esphome/components/const/__init__.py.
|
||||
# Decrease this number when constants are moved out of const.py.
|
||||
CONST_PY_MAX_CONF = 1024
|
||||
CONST_PY_MAX_CONF = 1025
|
||||
|
||||
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
|
||||
@@ -19,6 +19,8 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
# Modules that must only load for the commands that actually use them
|
||||
# (compile/config validation, shell completion), never from a bare
|
||||
# ``import esphome.__main__``.
|
||||
@@ -190,21 +192,22 @@ def test_api_client_does_not_import_heavy_modules() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_binary_sensor_does_not_import_integrations() -> None:
|
||||
@pytest.mark.parametrize("component", ["binary_sensor", "sensor"])
|
||||
def test_entity_component_does_not_import_integrations(component: str) -> None:
|
||||
"""An entity component must not drag in its optional integrations.
|
||||
|
||||
binary_sensor's mqtt/web_server/zigbee schema fragments live in
|
||||
The mqtt/web_server/zigbee schema fragments live in
|
||||
``esphome.core.entity_helpers``; the integration packages (and the
|
||||
esp32/logger chains mqtt and zigbee pull in) must only load when the
|
||||
user's config actually uses them.
|
||||
"""
|
||||
allowed = (
|
||||
"esphome.components",
|
||||
"esphome.components.binary_sensor",
|
||||
f"esphome.components.{component}",
|
||||
"esphome.components.const",
|
||||
)
|
||||
check = (
|
||||
"import sys; import esphome.components.binary_sensor; "
|
||||
f"import sys; import esphome.components.{component}; "
|
||||
f"leaked = [m for m in sys.modules "
|
||||
f"if m.startswith('esphome.components') and m not in {allowed!r}]; "
|
||||
"print(','.join(leaked))"
|
||||
@@ -217,7 +220,7 @@ def test_binary_sensor_does_not_import_integrations() -> None:
|
||||
)
|
||||
leaked = result.stdout.strip()
|
||||
assert not leaked, (
|
||||
f"esphome.components.binary_sensor imports integration packages at "
|
||||
f"esphome.components.{component} imports integration packages at "
|
||||
f"top level: {leaked}. Keep the shared schema fragments in "
|
||||
"esphome.core.entity_helpers and import the integration inside "
|
||||
"to_code instead."
|
||||
|
||||
Reference in New Issue
Block a user