From d9567b2974f2ab6ff149589bae5289cf3ae376c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Aug 2026 10:05:37 -0500 Subject: [PATCH] Normalize marker-wrapped callable keys in the schema dump (#18218) --- script/build_language_schema.py | 20 +++++++++- tests/script/test_build_language_schema.py | 43 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/script/build_language_schema.py b/script/build_language_schema.py index f6dcf00851c..2b64cb02564 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -1134,13 +1134,29 @@ def convert_keys(converted, schema, path): else: converted["key"] = "String" key_string_match = re.search( - r"", str(k), re.IGNORECASE + r"", str(k), re.IGNORECASE ) if key_string_match: converted["key_type"] = key_string_match.group(1) else: converted["key_type"] = str(k) + # A marker-wrapped callable key (e.g. script.execute's + # ``cv.Optional(validate_parameter_name)``) is a wildcard matcher; + # ``str(marker)`` is the function repr, whose heap address would + # churn the dump every build. Normalize like the bare-callable + # branch above: record the validator name in ``key_type`` and file + # the config var under ``string``. + key_name = str(k) + if isinstance(k, vol.Marker) and callable(k.schema): + key_string_match = re.search( + r"", key_name, re.IGNORECASE + ) + result["key_type"] = ( + key_string_match.group(1) if key_string_match else key_name + ) + key_name = "string" + # ``cv.OnlyWith`` / ``cv.OnlyWithout`` expose ``default`` as # a property that returns ``vol.UNDEFINED`` when the gating # component isn't loaded — and at schema-generation time @@ -1220,7 +1236,7 @@ def convert_keys(converted, schema, path): for base_k, base_v in get_overridden_config(k, converted).items(): if base_k in result and base_v == result[base_k]: result.pop(base_k) - converted["schema"][S_CONFIG_VARS][str(k)] = result + converted["schema"][S_CONFIG_VARS][key_name] = result if "key" in converted and converted["key"] == "String": config_vars = converted["schema"]["config_vars"] assert len(config_vars) == 1 diff --git a/tests/script/test_build_language_schema.py b/tests/script/test_build_language_schema.py index 8bbaa2773aa..f3d4bbcba65 100644 --- a/tests/script/test_build_language_schema.py +++ b/tests/script/test_build_language_schema.py @@ -3,11 +3,13 @@ from __future__ import annotations import ast +from collections.abc import Callable import importlib.util import json from pathlib import Path import subprocess import sys +from typing import Any import pytest @@ -205,6 +207,47 @@ def test_convert_keys_no_marker_for_non_sensitive_field() -> None: assert "sensitive_source" not in entry +def _wildcard_validator(value: Any) -> Any: + return value + + +def test_convert_keys_marker_wrapped_callable_key_normalizes() -> None: + converted: dict = {} + _bls.convert_keys(converted, {cv.Optional(_wildcard_validator): cv.string}, "/root") + + config_vars = converted["schema"]["config_vars"] + assert set(config_vars) == {"string"} + assert config_vars["string"]["key"] == "Optional" + assert config_vars["string"]["key_type"] == "_wildcard_validator" + + +def test_convert_keys_marker_wrapped_callable_beside_fixed_keys() -> None: + converted: dict = {} + _bls.convert_keys( + converted, + {cv.Required("id"): cv.string, cv.Optional(_wildcard_validator): cv.string}, + "/root", + ) + + assert set(converted["schema"]["config_vars"]) == {"id", "string"} + + +def test_convert_keys_bare_callable_dotted_qualname() -> None: + def make_validator() -> Callable[[Any], Any]: + def validator(value: Any) -> Any: + return value + + return validator + + converted: dict = {} + _bls.convert_keys(converted, {make_validator(): cv.string}, "/root") + + assert converted["key"] == "String" + assert converted["key_type"].endswith("make_validator..validator") + assert "at 0x" not in converted["key_type"] + assert set(converted["schema"]["config_vars"]) == {"string"} + + # --------------------------------------------------------------------------- # Regression tests for the lvgl schema dump. #