[schema] Unit-type number-with-unit validators; leave dimensions untyped

Address esphbot review: validators that accept a number optionally carrying a
unit (percentage, possibly_negative_percentage, temperature, temperature_delta,
color_temperature, percentage_int, validate_bytes) are dumped as their base
numeric type plus the canonical unit (e.g. {type: float, unit: "%"}), like
float_with_unit, so the editor accepts both the bare-number and the unit-string
form instead of rejecting one. cv.dimensions accepts a "WIDTHxHEIGHT" string or
a [w, h] list, so it is left untyped rather than misrepresented as a string.
This commit is contained in:
Jesse Hills
2026-09-14 13:52:22 +12:00
parent b40c497d3e
commit 59044484e2
2 changed files with 48 additions and 15 deletions
+16 -11
View File
@@ -152,17 +152,22 @@ _CV_STRING_VALIDATORS = (
cv.time_of_day,
cv.directory,
cv.file_,
cv.dimensions,
cv.none,
)
_CV_INTEGER_VALIDATORS = (cv.hex_int, cv.percentage_int, cv.mqtt_qos)
_CV_FLOAT_VALIDATORS = (
cv.percentage,
cv.possibly_negative_percentage,
cv.temperature,
cv.temperature_delta,
cv.color_temperature,
)
_CV_INTEGER_VALIDATORS = (cv.hex_int, cv.mqtt_qos)
# Validators that accept a number optionally carrying a unit suffix (e.g.
# "50%", "25°C", "10kB"). Dumped as their base numeric type with the canonical
# unit attached, mirroring float_with_unit, so editors accept both the
# bare-number and the unit-string form instead of rejecting one of them.
_CV_UNIT_VALIDATORS = {
cv.percentage: ("float", "%"),
cv.possibly_negative_percentage: ("float", "%"),
cv.temperature: ("float", "°C"),
cv.temperature_delta: ("float", "°C"),
cv.color_temperature: ("float", "mireds"),
cv.percentage_int: ("integer", "%"),
cv.validate_bytes: ("integer", "B"),
}
_CV_TIME_VALIDATORS = (
cv.update_interval,
cv.time_period_str_unit,
@@ -1051,8 +1056,8 @@ def convert(schema, config_var, path):
schema in _CV_STRING_VALIDATORS
):
config_var[S_TYPE] = "string"
elif schema in _CV_FLOAT_VALIDATORS:
config_var[S_TYPE] = "float"
elif callable(schema) and schema in _CV_UNIT_VALIDATORS:
config_var[S_TYPE], config_var["unit"] = _CV_UNIT_VALIDATORS[schema]
elif schema in _CV_TIME_VALIDATORS:
config_var[S_TYPE] = "time"
elif schema in _CV_LAMBDA_VALIDATORS:
+32 -4
View File
@@ -376,9 +376,6 @@ def _convert(validator: object) -> dict:
(cv.directory, "string"),
(cv.mqtt_qos, "integer"),
(cv.hex_int, "integer"),
(cv.percentage, "float"),
(cv.temperature, "float"),
(cv.color_temperature, "float"),
(cv.update_interval, "time"),
(cv.time_period_str_colon, "time"),
(cv.lambda_, "lambda"),
@@ -389,6 +386,35 @@ def test_convert_types_scalar_cv_validators(validator: object, expected: str) ->
assert _convert(validator).get("type") == expected
@pytest.mark.parametrize(
("validator", "expected_type", "expected_unit"),
[
(cv.percentage, "float", "%"),
(cv.possibly_negative_percentage, "float", "%"),
(cv.temperature, "float", "°C"),
(cv.temperature_delta, "float", "°C"),
(cv.color_temperature, "float", "mireds"),
(cv.percentage_int, "integer", "%"),
(cv.validate_bytes, "integer", "B"),
],
)
def test_convert_types_unit_validators(
validator: object, expected_type: str, expected_unit: str
) -> None:
# Number-with-unit validators accept both a bare number and a unit-suffixed
# string, so they carry their base numeric type plus the canonical unit.
entry = _convert(validator)
assert entry.get("type") == expected_type
assert entry.get("unit") == expected_unit
def test_convert_dimensions_left_untyped() -> None:
# cv.dimensions accepts either a "WIDTHxHEIGHT" string or a [w, h] list;
# a single scalar type would reject one form, so it is left untyped.
entry = _convert(cv.dimensions)
assert "type" not in entry
def test_convert_entity_category_is_enum() -> None:
entry = _convert(cv.entity_category)
assert entry["type"] == "enum"
@@ -497,7 +523,9 @@ def test_cv_types_end_to_end(full_schema_dir: Path) -> None:
climate = json.loads((full_schema_dir / "climate.json").read_text())["climate"]
visual = climate["schemas"]["_CLIMATE_SCHEMA"]["schema"]["config_vars"]["visual"]
assert visual["schema"]["config_vars"]["min_temperature"]["type"] == "float"
min_temp = visual["schema"]["config_vars"]["min_temperature"]
assert min_temp["type"] == "float"
assert min_temp["unit"] == "°C"
# message_type references both hex_uint8_t and uint8_t; shrink() must spread
# it to integer instead of tripping the single-extends assertion.