mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
[config_validation] Add a visibility UI-hint kwarg (#16267)
This commit is contained in:
@@ -345,7 +345,14 @@ TIME_SCHEMA = cv.Schema(
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("15min"))
|
||||
).extend(
|
||||
# ``visibility=ADVANCED`` flags the inherited ``update_interval``
|
||||
# field for visual editors — the 15min default is correct for
|
||||
# essentially every user, so editors should keep it tucked under
|
||||
# "advanced" so it doesn't crowd the form. Validation is
|
||||
# unaffected; YAML can override as before.
|
||||
cv.polling_component_schema("15min", visibility=cv.Visibility.ADVANCED)
|
||||
)
|
||||
|
||||
|
||||
def _emit_dst_rule_fields(prefix, rule):
|
||||
|
||||
@@ -89,6 +89,7 @@ from esphome.core import (
|
||||
TimePeriodNanoseconds,
|
||||
TimePeriodSeconds,
|
||||
)
|
||||
from esphome.enum import StrEnum
|
||||
from esphome.expression import SUBSTITUTION_VARIABLE_PROG as VARIABLE_PROG
|
||||
from esphome.helpers import add_class_to_obj, docs_url, list_starts_with
|
||||
from esphome.schema_extractors import (
|
||||
@@ -281,6 +282,54 @@ RESERVED_IDS = [
|
||||
]
|
||||
|
||||
|
||||
class Visibility(StrEnum):
|
||||
"""Schema-driven UI hint for visual editors.
|
||||
|
||||
The values describe how a schema-aware editor (e.g. the
|
||||
device-builder dashboard catalog via
|
||||
``script/build_language_schema.py``) should render the field.
|
||||
They do NOT affect validation — the YAML still accepts the key
|
||||
the same way. ESPHome itself ignores the value at runtime;
|
||||
consumers downstream of the schema dump act on it.
|
||||
|
||||
A field with no ``visibility`` set (the default) renders on the
|
||||
editor's main form. The two values below are points along a
|
||||
single axis of "how prominently to surface this":
|
||||
|
||||
- ``ADVANCED`` — render under the editor's "advanced settings"
|
||||
disclosure. Use for fields whose default is right for ~all
|
||||
users (e.g. ``update_interval`` on time platforms — 15 min is
|
||||
universally correct, but power users can still tune the YAML
|
||||
directly).
|
||||
- ``YAML_ONLY`` — never render in a visual editor. Use for
|
||||
knobs that are dangerous to expose in a UI even as advanced
|
||||
(``setup_priority`` is the canonical example — casual UI
|
||||
tweaks can break boot). The YAML escape hatch stays
|
||||
available for the rare power-user override.
|
||||
|
||||
The single-axis shape encodes "yaml-only is strictly stronger
|
||||
than advanced" at the type level — there's no way to ask for
|
||||
both at once, and no way to set a contradictory state like
|
||||
"advanced=False, yaml_only=True".
|
||||
|
||||
Per-field; the dumper walks recursively into nested schemas
|
||||
and emits each field's setting independently. Cascading
|
||||
semantics — "a stricter parent makes its descendants at-least
|
||||
as strict" — belong on the consumer side: the schema marker
|
||||
is faithfully what the field author wrote, and a consumer that
|
||||
cares about effective visibility walks the parent chain and
|
||||
takes the strictest setting. ``YAML_ONLY`` is strictly stronger
|
||||
than ``ADVANCED``, which is strictly stronger than no setting.
|
||||
Inner fields can declare their own visibility; an inner
|
||||
``YAML_ONLY`` under an ``ADVANCED`` parent stays ``YAML_ONLY``,
|
||||
and the consumer's cascade keeps siblings under the parent at
|
||||
``ADVANCED`` regardless of their own (less-strict) setting.
|
||||
"""
|
||||
|
||||
ADVANCED = "advanced"
|
||||
YAML_ONLY = "yaml_only"
|
||||
|
||||
|
||||
class Optional(vol.Optional):
|
||||
"""Mark a field as optional and optionally define a default for the field.
|
||||
|
||||
@@ -295,22 +344,45 @@ class Optional(vol.Optional):
|
||||
In ESPHome, all configuration defaults should be defined with the Optional class
|
||||
during config validation - specifically *not* in the C++ code or the code generation
|
||||
phase.
|
||||
|
||||
See :class:`Visibility` for the ``visibility`` kwarg — a UI
|
||||
hint for schema-driven editors that doesn't affect validation.
|
||||
"""
|
||||
|
||||
def __init__(self, key, default=UNDEFINED):
|
||||
def __init__(
|
||||
self,
|
||||
key,
|
||||
default=UNDEFINED,
|
||||
*,
|
||||
visibility: Visibility | None = None,
|
||||
):
|
||||
super().__init__(key, default=default)
|
||||
self.visibility: Visibility | None = visibility
|
||||
|
||||
|
||||
class Required(vol.Required):
|
||||
"""Define a field to be required to be set. The validated configuration is guaranteed
|
||||
to contain this key.
|
||||
|
||||
All required values should be acceessed with the `config[CONF_<KEY>]` syntax in code
|
||||
All required values should be accessed with the `config[CONF_<KEY>]` syntax in code
|
||||
- *not* the `config.get(CONF_<KEY>)` syntax.
|
||||
|
||||
See :class:`Visibility` for the ``visibility`` kwarg — a UI
|
||||
hint for schema-driven editors that doesn't affect validation.
|
||||
Required fields rarely need it (a required field by definition
|
||||
needs the user's attention) but the kwarg is exposed for
|
||||
symmetry so consumers can apply uniform logic across key markers.
|
||||
"""
|
||||
|
||||
def __init__(self, key, msg=None):
|
||||
def __init__(
|
||||
self,
|
||||
key,
|
||||
msg=None,
|
||||
*,
|
||||
visibility: Visibility | None = None,
|
||||
):
|
||||
super().__init__(key, msg=msg)
|
||||
self.visibility: Visibility | None = visibility
|
||||
|
||||
|
||||
class FinalExternalInvalid(Invalid):
|
||||
@@ -2162,16 +2234,45 @@ ENTITY_BASE_SCHEMA = Schema(
|
||||
|
||||
ENTITY_BASE_SCHEMA.add_extra(_entity_base_validator)
|
||||
|
||||
COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_})
|
||||
COMPONENT_SCHEMA = Schema(
|
||||
{
|
||||
# ``setup_priority`` controls the relative order in which
|
||||
# components are brought up at boot. Wrong values can break
|
||||
# the boot sequence in subtle ways (e.g. an i2c device set
|
||||
# to higher priority than the bus). Mark it ``YAML_ONLY`` so
|
||||
# visual editors never render it — the YAML escape hatch
|
||||
# stays available for the rare component author who really
|
||||
# needs to override the default.
|
||||
Optional(CONF_SETUP_PRIORITY, visibility=Visibility.YAML_ONLY): float_,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def polling_component_schema(default_update_interval):
|
||||
def polling_component_schema(
|
||||
default_update_interval, *, visibility: Visibility | None = None
|
||||
):
|
||||
"""Validate that this component represents a PollingComponent with a configurable
|
||||
update_interval.
|
||||
|
||||
:param default_update_interval: The default update interval to set for the integration.
|
||||
:param visibility: When set, propagate to the inherited
|
||||
``update_interval`` field's :class:`Visibility` UI hint. Set
|
||||
this for components whose default cadence is already correct
|
||||
for ~all users (e.g. time platforms — pass
|
||||
``Visibility.ADVANCED``).
|
||||
|
||||
Only honoured on the optional-default branch. When
|
||||
``default_update_interval`` is ``None`` the field becomes
|
||||
``Required`` (the component has no sensible default cadence and
|
||||
needs the user to choose), and hiding a Required field behind
|
||||
an advanced disclosure would be a UX hazard — collapsed-by-default
|
||||
editors could let the user submit without realising the form has
|
||||
an unfilled required field. The kwarg is silently ignored on that
|
||||
path so callers can pass it unconditionally.
|
||||
"""
|
||||
if default_update_interval is None:
|
||||
# Required → don't honour ``visibility``.
|
||||
# See the docstring for the UX rationale.
|
||||
return COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
Required(CONF_UPDATE_INTERVAL): update_interval,
|
||||
@@ -2181,7 +2282,9 @@ def polling_component_schema(default_update_interval):
|
||||
return COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
Optional(
|
||||
CONF_UPDATE_INTERVAL, default=default_update_interval
|
||||
CONF_UPDATE_INTERVAL,
|
||||
default=default_update_interval,
|
||||
visibility=visibility,
|
||||
): update_interval,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user