Normalize marker-wrapped callable keys in the schema dump (#18218)

This commit is contained in:
J. Nick Koston
2026-08-10 10:05:37 -05:00
committed by GitHub
parent 02fa18b74f
commit d9567b2974
2 changed files with 61 additions and 2 deletions
+18 -2
View File
@@ -1134,13 +1134,29 @@ def convert_keys(converted, schema, path):
else:
converted["key"] = "String"
key_string_match = re.search(
r"<function (\w*) at \w*>", str(k), re.IGNORECASE
r"<function ([^ ]+) at \w+>", 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"<function ([^ ]+) at \w+>", 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
@@ -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.<locals>.validator")
assert "at 0x" not in converted["key_type"]
assert set(converted["schema"]["config_vars"]) == {"string"}
# ---------------------------------------------------------------------------
# Regression tests for the lvgl schema dump.
#