mirror of
https://github.com/esphome/esphome.git
synced 2026-08-30 17:46:01 +00:00
Address review: stable schema anchors, const.py home, validator hardening
This commit is contained in:
@@ -58,10 +58,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.core.entity_helpers import (
|
||||
WEBSERVER_SORTING_SCHEMA,
|
||||
ZIGBEE_BINARY_SENSOR_SCHEMA,
|
||||
entity_duplicate_validator,
|
||||
lazy_load_validator,
|
||||
mqtt_component_class,
|
||||
@@ -436,9 +434,9 @@ def validate_publish_initial_state(value):
|
||||
|
||||
|
||||
_BINARY_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(WEBSERVER_SORTING_SCHEMA)
|
||||
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
.extend(ZIGBEE_BINARY_SENSOR_SCHEMA)
|
||||
.extend(entity_helpers.ZIGBEE_BINARY_SENSOR_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
|
||||
@@ -10,7 +10,11 @@ from esphome.components import web_server_base
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
|
||||
# Re-exported so entity components and external components keep importing
|
||||
# these from web_server; defined outside this package so entity base
|
||||
# schemas do not need to import it.
|
||||
from esphome.const import ( # noqa: F401
|
||||
CONF_AUTH,
|
||||
CONF_COMPRESSION,
|
||||
CONF_CSS_INCLUDE,
|
||||
@@ -26,6 +30,8 @@ from esphome.const import (
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SORTING_GROUP_ID,
|
||||
CONF_SORTING_WEIGHT,
|
||||
CONF_TYPE,
|
||||
CONF_USERNAME,
|
||||
CONF_VERSION,
|
||||
@@ -39,15 +45,7 @@ from esphome.const import (
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
|
||||
# Re-exported so entity components and external components keep importing
|
||||
# these from web_server; defined in entity_helpers so entity base schemas
|
||||
# do not need to import this package.
|
||||
from esphome.core.entity_helpers import ( # noqa: F401
|
||||
CONF_SORTING_GROUP_ID,
|
||||
CONF_SORTING_WEIGHT,
|
||||
WEBSERVER_SORTING_SCHEMA,
|
||||
)
|
||||
from esphome.core.entity_helpers import WEBSERVER_SORTING_SCHEMA # noqa: F401
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
@@ -14,11 +14,7 @@ from esphome.components.esp32.const import (
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MODEL, CONF_NAME, CONF_ON_START
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
ZIGBEE_BASE_ENTITY_SCHEMA,
|
||||
ZIGBEE_BINARY_SENSOR_SCHEMA,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
|
||||
from esphome.types import ConfigType
|
||||
|
||||
# CONF_ENDPOINT, CONF_MAX_EP_NUMBER, CONF_REPORT, CONF_USE_DEVICE_TYPE and
|
||||
@@ -62,8 +58,8 @@ CONFLICTS_WITH = ["openthread"]
|
||||
|
||||
# Defined in esphome.core.entity_helpers so entity base schemas can reference
|
||||
# them without importing this package; re-exported here for existing consumers.
|
||||
BASE_SCHEMA = ZIGBEE_BASE_ENTITY_SCHEMA
|
||||
BINARY_SENSOR_SCHEMA = ZIGBEE_BINARY_SENSOR_SCHEMA
|
||||
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)
|
||||
SWITCH_SCHEMA = cv.Schema({}).extend(zephyr_switch)
|
||||
NUMBER_SCHEMA = cv.Schema({}).extend(zephyr_number)
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
from enum import IntEnum
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
|
||||
# The entity schema keys and report enum live outside this package so
|
||||
# entity base schemas can use them without importing it; re-imported here
|
||||
# so zigbee code keeps its existing import paths.
|
||||
from esphome.const import ( # noqa: F401 # pylint: disable=unused-import
|
||||
CONF_ENDPOINT,
|
||||
CONF_REPORT,
|
||||
CONF_USE_DEVICE_TYPE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_DURATION,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
@@ -48,14 +55,7 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
# The entity schema keys and report enum live in esphome.core.entity_helpers
|
||||
# so entity base schemas can use them without importing this package;
|
||||
# re-imported here so zigbee code keeps its existing import paths.
|
||||
from esphome.core.entity_helpers import ( # noqa: F401 # pylint: disable=unused-import
|
||||
CONF_ENDPOINT,
|
||||
CONF_REPORT,
|
||||
CONF_USE_DEVICE_TYPE,
|
||||
ZIGBEE_MAX_EP_NUMBER as CONF_MAX_EP_NUMBER,
|
||||
ZIGBEE_REPORT as REPORT,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# These two schema keys live in esphome.core.entity_helpers so entity base
|
||||
# These two schema keys live in esphome.components.const so entity base
|
||||
# schemas can use them without importing this package.
|
||||
from esphome.core.entity_helpers import ( # noqa: F401 # pylint: disable=unused-import
|
||||
from esphome.const import ( # noqa: F401 # pylint: disable=unused-import
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
CONF_ZIGBEE_ID,
|
||||
)
|
||||
|
||||
@@ -387,6 +387,7 @@ CONF_ENABLE_PRIVATE_NETWORK_ACCESS = "enable_private_network_access"
|
||||
CONF_ENABLE_RRM = "enable_rrm"
|
||||
CONF_ENABLE_TIME = "enable_time"
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_ENERGY = "energy"
|
||||
CONF_ENTITY_CATEGORY = "entity_category"
|
||||
CONF_ENTITY_ID = "entity_id"
|
||||
@@ -885,6 +886,7 @@ CONF_REFERENCE_VOLTAGE = "reference_voltage"
|
||||
CONF_REFRESH = "refresh"
|
||||
CONF_RELABEL = "relabel"
|
||||
CONF_REPEAT = "repeat"
|
||||
CONF_REPORT = "report"
|
||||
CONF_REPOSITORY = "repository"
|
||||
CONF_RESET = "reset"
|
||||
CONF_RESET_DURATION = "reset_duration"
|
||||
@@ -962,6 +964,8 @@ CONF_SLEEP_DURATION = "sleep_duration"
|
||||
CONF_SLEEP_PIN = "sleep_pin"
|
||||
CONF_SLEEP_WHEN_DONE = "sleep_when_done"
|
||||
CONF_SONY = "sony"
|
||||
CONF_SORTING_GROUP_ID = "sorting_group_id"
|
||||
CONF_SORTING_WEIGHT = "sorting_weight"
|
||||
CONF_SOURCE = "source"
|
||||
CONF_SOURCE_ID = "source_id"
|
||||
CONF_SPEAKER = "speaker"
|
||||
@@ -1093,6 +1097,7 @@ CONF_UPDATE_ON_BOOT = "update_on_boot"
|
||||
CONF_URL = "url"
|
||||
CONF_USE_ABBREVIATIONS = "use_abbreviations"
|
||||
CONF_USE_ADDRESS = "use_address"
|
||||
CONF_USE_DEVICE_TYPE = "use_device_type"
|
||||
CONF_USE_DMA = "use_dma"
|
||||
CONF_USE_FAHRENHEIT = "use_fahrenheit"
|
||||
CONF_USERNAME = "username"
|
||||
@@ -1144,6 +1149,8 @@ CONF_Y = "y"
|
||||
CONF_Y_GRID = "y_grid"
|
||||
CONF_YEAR = "year"
|
||||
CONF_ZERO = "zero"
|
||||
CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor"
|
||||
CONF_ZIGBEE_ID = "zigbee_id"
|
||||
|
||||
TYPE_GIT = "git"
|
||||
TYPE_LOCAL = "local"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
import functools
|
||||
import importlib
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
@@ -10,14 +11,21 @@ from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENDPOINT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
CONF_REPORT,
|
||||
CONF_SORTING_GROUP_ID,
|
||||
CONF_SORTING_WEIGHT,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_USE_DEVICE_TYPE,
|
||||
CONF_WEB_SERVER,
|
||||
CONF_WEB_SERVER_ID,
|
||||
CONF_ZIGBEE_BINARY_SENSOR,
|
||||
CONF_ZIGBEE_ID,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.config import (
|
||||
@@ -655,9 +663,6 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
|
||||
# owning integrations re-export the shared schema names and key strings so
|
||||
# each stays defined once.
|
||||
|
||||
CONF_SORTING_GROUP_ID = "sorting_group_id"
|
||||
CONF_SORTING_WEIGHT = "sorting_weight"
|
||||
|
||||
_WebServer = cg.esphome_ns.namespace("web_server").class_(
|
||||
"WebServer", cg.Component, cg.Controller
|
||||
)
|
||||
@@ -691,11 +696,6 @@ def mqtt_component_class(name: str) -> MockObjClass:
|
||||
return _mqtt_ns.class_(name, _MQTTComponent)
|
||||
|
||||
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_REPORT = "report"
|
||||
CONF_USE_DEVICE_TYPE = "use_device_type"
|
||||
CONF_ZIGBEE_ID = "zigbee_id"
|
||||
CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor"
|
||||
ZIGBEE_MAX_EP_NUMBER = 239
|
||||
|
||||
_zigbee_ns = cg.esphome_ns.namespace("zigbee")
|
||||
@@ -753,11 +753,15 @@ def lazy_load_validator(
|
||||
component: str, name: str
|
||||
) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Schema extra delegating to ``components.<component>.<name>`` when loaded."""
|
||||
if find_spec(f"esphome.components.{component}") is None:
|
||||
raise ValueError(f"No such component {component!r}")
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
if component not in CORE.loaded_integrations:
|
||||
return config
|
||||
module = importlib.import_module(f"esphome.components.{component}")
|
||||
return getattr(module, name)(config)
|
||||
module = import_module(f"esphome.components.{component}")
|
||||
if (delegate := getattr(module, name, None)) is None:
|
||||
raise ValueError(f"{component} has no validator {name!r}")
|
||||
return delegate(config)
|
||||
|
||||
return validator
|
||||
|
||||
+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 = 1017
|
||||
CONST_PY_MAX_CONF = 1024
|
||||
|
||||
|
||||
@lint_content_check(include=["esphome/const.py"])
|
||||
|
||||
@@ -30,6 +30,7 @@ from esphome.core.entity_helpers import (
|
||||
finalize_entity_strings,
|
||||
get_base_entity_object_id,
|
||||
lazy_load_validator,
|
||||
mqtt_component_class,
|
||||
register_device_class,
|
||||
register_icon,
|
||||
register_unit_of_measurement,
|
||||
@@ -1268,9 +1269,7 @@ def test_lazy_load_validator_defers_import() -> None:
|
||||
original = CORE.loaded_integrations
|
||||
CORE.loaded_integrations = set()
|
||||
try:
|
||||
with patch(
|
||||
"esphome.core.entity_helpers.importlib.import_module"
|
||||
) as import_mock:
|
||||
with patch("esphome.core.entity_helpers.import_module") as import_mock:
|
||||
assert validator(config) is config
|
||||
import_mock.assert_not_called()
|
||||
|
||||
@@ -1282,3 +1281,47 @@ def test_lazy_load_validator_defers_import() -> None:
|
||||
delegate.assert_called_once_with(config)
|
||||
finally:
|
||||
CORE.loaded_integrations = original
|
||||
|
||||
|
||||
def test_lazy_load_validator_rejects_unknown_component() -> None:
|
||||
"""A typo in the component name fails at schema construction."""
|
||||
with pytest.raises(ValueError, match="no_such_component"):
|
||||
lazy_load_validator("no_such_component", "validate_binary_sensor")
|
||||
|
||||
|
||||
def test_lazy_load_validator_names_missing_hook() -> None:
|
||||
"""A missing hook raises a clear error naming the component and hook."""
|
||||
validator = lazy_load_validator("zigbee", "no_such_hook")
|
||||
|
||||
original = CORE.loaded_integrations
|
||||
CORE.loaded_integrations = {"zigbee"}
|
||||
try:
|
||||
with (
|
||||
patch("esphome.core.entity_helpers.import_module") as import_mock,
|
||||
pytest.raises(ValueError, match="no_such_hook"),
|
||||
):
|
||||
del import_mock.return_value.no_such_hook
|
||||
validator({})
|
||||
finally:
|
||||
CORE.loaded_integrations = original
|
||||
|
||||
|
||||
def test_integration_class_handles_match_owning_definitions() -> None:
|
||||
"""The cheap class handles must stay string-equal to the integrations'
|
||||
own declarations, or use_id/declare_id resolution silently drifts."""
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.components.zigbee.const import ZigbeeComponent
|
||||
from esphome.components.zigbee.zigbee_zephyr import ZigbeeBinarySensor
|
||||
from esphome.core import entity_helpers
|
||||
|
||||
mqtt_handle = mqtt_component_class("MQTTBinarySensorComponent")
|
||||
assert str(mqtt_handle) == str(mqtt.MQTTBinarySensorComponent)
|
||||
assert mqtt_handle.inherits_from(mqtt.MQTTComponent)
|
||||
assert mqtt.MQTTBinarySensorComponent.inherits_from(entity_helpers._MQTTComponent)
|
||||
|
||||
assert str(entity_helpers._WebServer) == str(web_server.WebServer)
|
||||
assert entity_helpers._WebServer.inherits_from(web_server.WebServer)
|
||||
assert web_server.WebServer.inherits_from(entity_helpers._WebServer)
|
||||
|
||||
assert str(entity_helpers._ZigbeeComponent) == str(ZigbeeComponent)
|
||||
assert str(entity_helpers._ZigbeeBinarySensor) == str(ZigbeeBinarySensor)
|
||||
|
||||
Reference in New Issue
Block a user