From 06ac207c22a8359c3d10f90eac593e60bf9ab78d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 13 Sep 2026 18:08:49 +0200 Subject: [PATCH] Use probatio directly in core, auth, helpers and util (#182107) --- homeassistant/auth/mfa_modules/__init__.py | 22 +- .../auth/mfa_modules/insecure_example.py | 18 +- homeassistant/auth/mfa_modules/notify.py | 26 +- homeassistant/auth/mfa_modules/totp.py | 12 +- homeassistant/auth/permissions/__init__.py | 4 +- homeassistant/auth/permissions/entities.py | 28 +- homeassistant/auth/providers/__init__.py | 20 +- homeassistant/auth/providers/command_line.py | 18 +- homeassistant/auth/providers/homeassistant.py | 12 +- .../auth/providers/insecure_example.py | 18 +- .../auth/providers/trusted_networks.py | 22 +- homeassistant/bootstrap.py | 6 +- homeassistant/config.py | 57 +- homeassistant/config_entries.py | 4 +- homeassistant/core.py | 6 +- homeassistant/core_config.py | 108 +-- homeassistant/data_entry_flow.py | 36 +- homeassistant/helpers/automation.py | 8 +- homeassistant/helpers/check_config.py | 16 +- homeassistant/helpers/collection.py | 22 +- homeassistant/helpers/condition.py | 60 +- .../helpers/config_entry_oauth2_flow.py | 10 +- homeassistant/helpers/config_validation.py | 715 +++++++++--------- homeassistant/helpers/data_entry_flow.py | 13 +- homeassistant/helpers/entity.py | 4 +- homeassistant/helpers/entity_registry.py | 8 +- homeassistant/helpers/entityfilter.py | 36 +- homeassistant/helpers/http.py | 4 +- homeassistant/helpers/intent.py | 40 +- homeassistant/helpers/llm.py | 39 +- .../helpers/schema_config_entry_flow.py | 20 +- homeassistant/helpers/script.py | 32 +- homeassistant/helpers/selector.py | 561 +++++++------- homeassistant/helpers/service.py | 64 +- .../helpers/template/extensions/areas.py | 4 +- .../helpers/template/extensions/devices.py | 4 +- .../helpers/template/extensions/labels.py | 4 +- homeassistant/helpers/template/helpers.py | 6 +- homeassistant/helpers/trigger.py | 76 +- .../helpers/trigger_template_entity.py | 32 +- homeassistant/helpers/typing.py | 6 +- homeassistant/loader.py | 4 +- homeassistant/util/unit_system.py | 8 +- 43 files changed, 1143 insertions(+), 1070 deletions(-) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index da1e52bf98ab..786527563ba3 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -4,8 +4,8 @@ import logging import types from typing import Any -import voluptuous as vol -from voluptuous.humanize import humanize_error +import probatio +from probatio.humanize import humanize_error from homeassistant import data_entry_flow, requirements from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE @@ -18,14 +18,14 @@ from homeassistant.util.hass_dict import HassKey MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry() -MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema( +MULTI_FACTOR_AUTH_MODULE_SCHEMA = probatio.Schema( { - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, + probatio.Required(CONF_TYPE): str, + probatio.Optional(CONF_NAME): str, # Specify ID if you have two mfa auth module for same type. - vol.Optional(CONF_ID): str, + probatio.Optional(CONF_ID): str, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) DATA_REQS: HassKey[set[str]] = HassKey("mfa_auth_module_reqs_processed") @@ -65,8 +65,8 @@ class MultiFactorAuthModule: # Implement by extending class @property - def input_schema(self) -> vol.Schema: - """Return a voluptuous schema to define mfa auth module's input.""" + def input_schema(self) -> probatio.Schema: + """Return a schema to define mfa auth module's input.""" raise NotImplementedError async def async_setup_flow(self, user_id: str) -> SetupFlow[Any]: @@ -101,7 +101,7 @@ class SetupFlow[_MultiFactorAuthModuleT: MultiFactorAuthModule = MultiFactorAuth def __init__( self, auth_module: _MultiFactorAuthModuleT, - setup_schema: vol.Schema, + setup_schema: probatio.Schema, user_id: str, ) -> None: """Initialize the setup flow.""" @@ -137,7 +137,7 @@ async def auth_mfa_module_from_config( try: config = module.CONFIG_SCHEMA(config) - except vol.Invalid as err: + except probatio.Invalid as err: _LOGGER.error( "Invalid configuration for multi-factor module %s: %s", module_name, diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index be635685a2cc..15b6c55d3c46 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -2,7 +2,7 @@ from typing import Any, override -import voluptuous as vol +import probatio from homeassistant.core import HomeAssistant @@ -15,11 +15,13 @@ from . import ( CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( { - vol.Required("data"): [ - vol.Schema({vol.Required("user_id"): str, vol.Required("pin"): str}) + probatio.Required("data"): [ + probatio.Schema( + {probatio.Required("user_id"): str, probatio.Required("pin"): str} + ) ] }, - extra=vol.PREVENT_EXTRA, + extra=probatio.PREVENT_EXTRA, ) @@ -36,14 +38,14 @@ class InsecureExampleModule(MultiFactorAuthModule): @property @override - def input_schema(self) -> vol.Schema: + def input_schema(self) -> probatio.Schema: """Validate login flow input data.""" - return vol.Schema({vol.Required("pin"): str}) + return probatio.Schema({probatio.Required("pin"): str}) @property - def setup_schema(self) -> vol.Schema: + def setup_schema(self) -> probatio.Schema: """Validate async_setup_user input data.""" - return vol.Schema({vol.Required("pin"): str}) + return probatio.Schema({probatio.Required("pin"): str}) @override async def async_setup_flow(self, user_id: str) -> SetupFlow: diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index 30aa0a31e3f3..1475040097c3 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -8,7 +8,7 @@ import logging from typing import Any, cast, override import attr -import voluptuous as vol +import probatio from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE from homeassistant.core import HomeAssistant, callback @@ -30,11 +30,13 @@ CONF_MESSAGE = "message" CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( { - vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_MESSAGE, default="{} is your Home Assistant login code"): str, + probatio.Optional(CONF_INCLUDE): probatio.All(cv.ensure_list, [cv.string]), + probatio.Optional(CONF_EXCLUDE): probatio.All(cv.ensure_list, [cv.string]), + probatio.Optional( + CONF_MESSAGE, default="{} is your Home Assistant login code" + ): str, }, - extra=vol.PREVENT_EXTRA, + extra=probatio.PREVENT_EXTRA, ) STORAGE_VERSION = 1 @@ -108,9 +110,9 @@ class NotifyAuthModule(MultiFactorAuthModule): @property @override - def input_schema(self) -> vol.Schema: + def input_schema(self) -> probatio.Schema: """Validate login flow input data.""" - return vol.Schema({vol.Required(INPUT_FIELD_CODE): str}) + return probatio.Schema({probatio.Required(INPUT_FIELD_CODE): str}) async def _async_load(self) -> None: """Load stored data.""" @@ -277,7 +279,7 @@ class NotifySetupFlow(SetupFlow[NotifyAuthModule]): def __init__( self, auth_module: NotifyAuthModule, - setup_schema: vol.Schema, + setup_schema: probatio.Schema, user_id: str, available_notify_services: list[str], ) -> None: @@ -308,10 +310,12 @@ class NotifySetupFlow(SetupFlow[NotifyAuthModule]): if not self._available_notify_services: return self.async_abort(reason="no_available_service") - schema = vol.Schema( + schema = probatio.Schema( { - vol.Required("notify_service"): vol.In(self._available_notify_services), - vol.Optional("target"): str, + probatio.Required("notify_service"): probatio.In( + self._available_notify_services + ), + probatio.Optional("target"): str, } ) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index e5963b911f8c..0ddfe11d261a 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -4,7 +4,7 @@ import asyncio from io import BytesIO from typing import Any, cast, override -import voluptuous as vol +import probatio from homeassistant.auth.models import User from homeassistant.core import HomeAssistant @@ -20,7 +20,7 @@ from . import ( REQUIREMENTS = ["pyotp==2.9.0", "PyQRCode==1.2.1"] -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=probatio.PREVENT_EXTRA) STORAGE_VERSION = 1 STORAGE_KEY = "auth_module.totp" @@ -88,9 +88,9 @@ class TotpAuthModule(MultiFactorAuthModule): @property @override - def input_schema(self) -> vol.Schema: + def input_schema(self) -> probatio.Schema: """Validate login flow input data.""" - return vol.Schema({vol.Required(INPUT_FIELD_CODE): str}) + return probatio.Schema({probatio.Required(INPUT_FIELD_CODE): str}) async def _async_load(self) -> None: """Load stored data.""" @@ -163,7 +163,7 @@ class TotpAuthModule(MultiFactorAuthModule): await self._async_load() # user_input has been validate in caller - # set INPUT_FIELD_CODE as vol.Required is not user friendly + # set INPUT_FIELD_CODE as probatio.Required is not user friendly return await self.hass.async_add_executor_job( self._validate_2fa, user_id, user_input.get(INPUT_FIELD_CODE, "") ) @@ -189,7 +189,7 @@ class TotpSetupFlow(SetupFlow[TotpAuthModule]): _image: str def __init__( - self, auth_module: TotpAuthModule, setup_schema: vol.Schema, user: User + self, auth_module: TotpAuthModule, setup_schema: probatio.Schema, user: User ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user.id) diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index b525423691fc..aeab7ff8193d 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -3,7 +3,7 @@ from collections.abc import Callable, Iterable from typing import TYPE_CHECKING, override -import voluptuous as vol +import probatio from .const import CAT_ENTITIES from .entities import ENTITY_POLICY_SCHEMA, compile_entities @@ -15,7 +15,7 @@ from .util import test_all if TYPE_CHECKING: from ..models import User -POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) +POLICY_SCHEMA = probatio.Schema({probatio.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) __all__ = [ "POLICY_SCHEMA", diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index 0c9f3eac5b98..6c081d272ae7 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -3,7 +3,7 @@ from collections import OrderedDict from collections.abc import Callable -import voluptuous as vol +import probatio from homeassistant.helpers import device_registry as dr @@ -12,13 +12,13 @@ from .models import PermissionLookup from .types import CategoryType, SubCategoryDict, ValueType from .util import SubCatLookupType, compile_policy, lookup_all -SINGLE_ENTITY_SCHEMA = vol.Any( +SINGLE_ENTITY_SCHEMA = probatio.Any( True, - vol.Schema( + probatio.Schema( { - vol.Optional(POLICY_READ): True, - vol.Optional(POLICY_CONTROL): True, - vol.Optional(POLICY_EDIT): True, + probatio.Optional(POLICY_READ): True, + probatio.Optional(POLICY_CONTROL): True, + probatio.Optional(POLICY_EDIT): True, } ), ) @@ -28,17 +28,17 @@ ENTITY_AREAS = "area_ids" ENTITY_DEVICE_IDS = "device_ids" ENTITY_ENTITY_IDS = "entity_ids" -ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({str: SINGLE_ENTITY_SCHEMA})) +ENTITY_VALUES_SCHEMA = probatio.Any(True, probatio.Schema({str: SINGLE_ENTITY_SCHEMA})) -ENTITY_POLICY_SCHEMA = vol.Any( +ENTITY_POLICY_SCHEMA = probatio.Any( True, - vol.Schema( + probatio.Schema( { - vol.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA, - vol.Optional(ENTITY_AREAS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_DEVICE_IDS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA, - vol.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA, + probatio.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA, + probatio.Optional(ENTITY_AREAS): ENTITY_VALUES_SCHEMA, + probatio.Optional(ENTITY_DEVICE_IDS): ENTITY_VALUES_SCHEMA, + probatio.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA, + probatio.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA, } ), ) diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index d1e9512f34f0..85dceec452be 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -5,8 +5,8 @@ import logging import types from typing import Any -import voluptuous as vol -from voluptuous.humanize import humanize_error +import probatio +from probatio.humanize import humanize_error from homeassistant import requirements from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE @@ -34,14 +34,14 @@ DATA_REQS: HassKey[set[str]] = HassKey("auth_prov_reqs_processed") AUTH_PROVIDERS: Registry[str, type[AuthProvider]] = Registry() -AUTH_PROVIDER_SCHEMA = vol.Schema( +AUTH_PROVIDER_SCHEMA = probatio.Schema( { - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, + probatio.Required(CONF_TYPE): str, + probatio.Optional(CONF_NAME): str, # Specify ID if you have two auth providers for same type. - vol.Optional(CONF_ID): str, + probatio.Optional(CONF_ID): str, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) @@ -148,7 +148,7 @@ async def auth_provider_from_config( try: config = module.CONFIG_SCHEMA(config) - except vol.Invalid as err: + except probatio.Invalid as err: _LOGGER.error( "Invalid configuration for auth provider %s: %s", provider_name, @@ -237,8 +237,8 @@ class LoginFlow[_AuthProviderT: AuthProvider = AuthProvider]( return self.async_show_form( step_id="select_mfa_module", - data_schema=vol.Schema( - {"multi_factor_auth_module": vol.In(self.available_mfa_modules)} + data_schema=probatio.Schema( + {"multi_factor_auth_module": probatio.In(self.available_mfa_modules)} ), errors=errors, ) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index f20e34547cf5..484b2a8367c2 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -6,7 +6,7 @@ import logging import os from typing import Any, override -import voluptuous as vol +import probatio from homeassistant.const import CONF_COMMAND from homeassistant.exceptions import HomeAssistantError @@ -19,13 +19,15 @@ CONF_META = "meta" CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( { - vol.Required(CONF_COMMAND): vol.All( + probatio.Required(CONF_COMMAND): probatio.All( str, os.path.normpath, msg="must be an absolute path" ), - vol.Optional(CONF_ARGS, default=None): vol.Any(vol.DefaultTo(list), [str]), - vol.Optional(CONF_META, default=False): bool, + probatio.Optional(CONF_ARGS, default=None): probatio.Any( + probatio.DefaultTo(list), [str] + ), + probatio.Optional(CONF_META, default=False): bool, }, - extra=vol.PREVENT_EXTRA, + extra=probatio.PREVENT_EXTRA, ) _LOGGER = logging.getLogger(__name__) @@ -161,10 +163,10 @@ class CommandLineLoginFlow(LoginFlow[CommandLineAuthProvider]): return self.async_show_form( step_id="init", - data_schema=vol.Schema( + data_schema=probatio.Schema( { - vol.Required("username"): str, - vol.Required("password"): str, + probatio.Required("username"): str, + probatio.Required("password"): str, } ), errors=errors, diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 43134737e061..c1a7653ab9e3 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -6,7 +6,7 @@ from collections.abc import Mapping from typing import Any, cast, override import bcrypt -import voluptuous as vol +import probatio from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback @@ -23,12 +23,12 @@ STORAGE_KEY = "auth_provider.homeassistant" def _disallow_id(conf: dict[str, Any]) -> dict[str, Any]: """Disallow ID in config.""" if CONF_ID in conf: - raise vol.Invalid("ID is not allowed for the homeassistant auth provider.") + raise probatio.Invalid("ID is not allowed for the homeassistant auth provider.") return conf -CONFIG_SCHEMA = vol.All(AUTH_PROVIDER_SCHEMA, _disallow_id) +CONFIG_SCHEMA = probatio.All(AUTH_PROVIDER_SCHEMA, _disallow_id) @callback @@ -376,10 +376,10 @@ class HassLoginFlow(LoginFlow[HassAuthProvider]): return self.async_show_form( step_id="init", - data_schema=vol.Schema( + data_schema=probatio.Schema( { - vol.Required("username"): str, - vol.Required("password"): str, + probatio.Required("username"): str, + probatio.Required("password"): str, } ), errors=errors, diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 5c35fd7e0e4a..ba2397314f38 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -4,7 +4,7 @@ from collections.abc import Mapping import hmac from typing import override -import voluptuous as vol +import probatio from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -12,17 +12,17 @@ from homeassistant.exceptions import HomeAssistantError from ..models import AuthFlowContext, AuthFlowResult, Credentials, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow -USER_SCHEMA = vol.Schema( +USER_SCHEMA = probatio.Schema( { - vol.Required("username"): str, - vol.Required("password"): str, - vol.Optional("name"): str, + probatio.Required("username"): str, + probatio.Required("password"): str, + probatio.Optional("name"): str, } ) CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( - {vol.Required("users"): [USER_SCHEMA]}, extra=vol.PREVENT_EXTRA + {probatio.Required("users"): [USER_SCHEMA]}, extra=probatio.PREVENT_EXTRA ) @@ -120,10 +120,10 @@ class ExampleLoginFlow(LoginFlow[ExampleAuthProvider]): return self.async_show_form( step_id="init", - data_schema=vol.Schema( + data_schema=probatio.Schema( { - vol.Required("username"): str, - vol.Required("password"): str, + probatio.Required("username"): str, + probatio.Required("password"): str, } ), errors=errors, diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index c63935a13079..2940e95ce78f 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -15,7 +15,7 @@ from ipaddress import ( ) from typing import Any, cast, override -import voluptuous as vol +import probatio from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -42,24 +42,26 @@ CONF_ALLOW_BYPASS_LOGIN = "allow_bypass_login" CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( { - vol.Required(CONF_TRUSTED_NETWORKS): vol.All(cv.ensure_list, [ip_network]), - vol.Optional(CONF_TRUSTED_USERS, default={}): vol.Schema( + probatio.Required(CONF_TRUSTED_NETWORKS): probatio.All( + cv.ensure_list, [ip_network] + ), + probatio.Optional(CONF_TRUSTED_USERS, default={}): probatio.Schema( # we only validate the format of user_id or group_id { - ip_network: vol.All( + ip_network: probatio.All( cv.ensure_list, [ - vol.Or( + probatio.Or( cv.uuid4_hex, - vol.Schema({vol.Required(CONF_GROUP): str}), + probatio.Schema({probatio.Required(CONF_GROUP): str}), ) ], ) } ), - vol.Optional(CONF_ALLOW_BYPASS_LOGIN, default=False): cv.boolean, + probatio.Optional(CONF_ALLOW_BYPASS_LOGIN, default=False): cv.boolean, }, - extra=vol.PREVENT_EXTRA, + extra=probatio.PREVENT_EXTRA, ) @@ -256,7 +258,7 @@ class TrustedNetworksLoginFlow(LoginFlow[TrustedNetworksAuthProvider]): return self.async_show_form( step_id="init", - data_schema=vol.Schema( - {vol.Required("user"): vol.In(self._available_users)} + data_schema=probatio.Schema( + {probatio.Required("user"): probatio.In(self._available_users)} ), ) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0c606c38d080..bb07500f82ef 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,7 +20,7 @@ from typing import TYPE_CHECKING, Any, override # _frozen_importlib._DeadlockError: deadlock detected by # _ModuleLock('cryptography.hazmat.backends.openssl.backend') import cryptography.hazmat.backends.openssl.backend # noqa: F401 -import voluptuous as vol +import probatio import yarl from . import ( @@ -555,7 +555,7 @@ async def async_from_config_dict( try: await async_process_ha_core_config(hass, core_config) - except vol.Invalid as config_err: + except probatio.Invalid as config_err: conf_util.async_log_schema_error(config_err, core.DOMAIN, core_config, hass) async_notify_setup_error(hass, core.DOMAIN) return None @@ -692,7 +692,7 @@ def _log_file_disabled_reason() -> str | None: try: if cv.boolean(disable_log_file): return LOG_FILE_DISABLED_REASON_ENVIRONMENT - except vol.Invalid: + except probatio.Invalid: _LOGGER.warning( "Ignoring invalid %s value: %s. Expected a boolean value: " "1/0, true/false, yes/no, on/off, or enable/disable", diff --git a/homeassistant/config.py b/homeassistant/config.py index edcd6ec85fac..96e3a4f7c117 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -16,9 +16,8 @@ from types import ModuleType from typing import TYPE_CHECKING, Any, Literal, overload from awesomeversion import AwesomeVersion -from probatio import Undefined -import voluptuous as vol -from voluptuous.humanize import MAX_VALIDATION_ERROR_ITEM_LENGTH +import probatio +from probatio.humanize import MAX_VALIDATION_ERROR_ITEM_LENGTH from yaml.error import MarkedYAMLError from .const import CONF_PACKAGES, CONF_PLATFORM, __version__ @@ -233,7 +232,7 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> dict: for key in config: try: cv.domain_key(key) - except vol.Invalid as exc: + except probatio.Invalid as exc: suffix = "" if annotation := find_annotation(config, exc.path): suffix = f" at {_relpath(hass, annotation[0])}, line {annotation[1]}" @@ -245,7 +244,7 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> dict: core_config = config.get(HOMEASSISTANT_DOMAIN, {}) try: await merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {})) - except vol.Invalid as exc: + except probatio.Invalid as exc: suffix = "" if annotation := find_annotation( config, [HOMEASSISTANT_DOMAIN, CONF_PACKAGES, *exc.path] @@ -342,7 +341,7 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: @callback def async_log_schema_error( - exc: vol.Invalid, + exc: probatio.Invalid, domain: str, config: dict, hass: HomeAssistant, @@ -355,14 +354,14 @@ def async_log_schema_error( @callback def async_log_config_validator_error( - exc: vol.Invalid | HomeAssistantError, + exc: probatio.Invalid | HomeAssistantError, domain: str, config: dict, hass: HomeAssistant, link: str | None = None, ) -> None: """Log an error from a custom config validator.""" - if isinstance(exc, vol.Invalid): + if isinstance(exc, probatio.Invalid): async_log_schema_error(exc, domain, config, hass, link) return @@ -448,16 +447,16 @@ def _relpath(hass: HomeAssistant, path: str) -> str: def stringify_invalid( hass: HomeAssistant, - exc: vol.Invalid, + exc: probatio.Invalid, domain: str, config: dict, link: str | None, max_sub_error_length: int, ) -> str: - """Stringify voluptuous.Invalid. + """Stringify probatio.Invalid. This is an alternative to the custom __str__ implemented in - voluptuous.error.Invalid. The modifications are: + probatio.error.Invalid. The modifications are: - Format the path delimited by -> instead of @data[] - Prefix with domain, file and line of the error - Suffix with a link to the documentation @@ -490,7 +489,7 @@ def stringify_invalid( f"{message_suffix}" ) # This function is an alternative to the stringification done by - # vol.Invalid.__str__, so we need to call Exception.__str__ here + # probatio.Invalid.__str__, so we need to call Exception.__str__ here # instead of str(exc) output = Exception.__str__(exc) if error_type := exc.error_type: @@ -508,7 +507,7 @@ def stringify_invalid( def humanize_error( hass: HomeAssistant, - validation_error: vol.Invalid, + validation_error: probatio.Invalid, domain: str, config: dict, link: str | None, @@ -516,10 +515,10 @@ def humanize_error( ) -> str: """Provide a more helpful + complete validation error message. - This is a modified version of voluptuous.error.Invalid.__str__, + This is a modified version of probatio.error.Invalid.__str__, the modifications make some minor changes to the formatting. """ - if isinstance(validation_error, vol.MultipleInvalid): + if isinstance(validation_error, probatio.MultipleInvalid): return "\n".join( sorted( humanize_error( @@ -564,7 +563,7 @@ def format_homeassistant_error( @callback def format_schema_error( hass: HomeAssistant, - exc: vol.Invalid, + exc: probatio.Invalid, domain: str, config: dict, link: str | None = None, @@ -588,12 +587,12 @@ def _log_pkg_error( def _identify_config_schema(module: ComponentProtocol) -> str | None: """Extract the schema and identify list or dict based.""" - if not isinstance(module.CONFIG_SCHEMA, vol.Schema): + if not isinstance(module.CONFIG_SCHEMA, probatio.Schema): return None # type: ignore[unreachable] schema = module.CONFIG_SCHEMA.schema - if isinstance(schema, vol.All): + if isinstance(schema, probatio.All): for subschema in schema.validators: if isinstance(subschema, dict): schema = subschema @@ -609,7 +608,7 @@ def _identify_config_schema(module: ComponentProtocol) -> str | None: _LOGGER.exception("Unexpected error identifying config schema") return None - if hasattr(key, "default") and not isinstance(key.default, Undefined): + if hasattr(key, "default") and not isinstance(key.default, probatio.Undefined): default_value = module.CONFIG_SCHEMA({module.DOMAIN: key.default()})[ module.DOMAIN ] @@ -671,7 +670,7 @@ async def merge_packages_config( """Merge packages into the top-level configuration. Ignores packages that cannot be setup. Mutates config. Raises - vol.Invalid if whole package config is invalid. + probatio.Invalid if whole package config is invalid. """ _PACKAGES_CONFIG_SCHEMA(packages) @@ -680,7 +679,7 @@ async def merge_packages_config( for pack_name, pack_conf in packages.items(): try: _validate_package_definition(pack_name, pack_conf) - except vol.Invalid as exc: + except probatio.Invalid as exc: _log_pkg_error( hass, pack_name, @@ -697,7 +696,7 @@ async def merge_packages_config( continue try: domain = cv.domain_key(comp_name) - except vol.Invalid: + except probatio.Invalid: _log_pkg_error( hass, pack_name, comp_name, config, f"Invalid domain '{comp_name}'" ) @@ -819,7 +818,7 @@ def _get_log_message_and_stack_print_pref( # If no pre defined log_message is set, we generate an enriched error # message, so we can notify about it during setup show_stack_trace = False - if isinstance(exception, vol.Invalid): + if isinstance(exception, probatio.Invalid): log_message = format_schema_error( hass, exception, platform_path, platform_config, link ) @@ -1025,7 +1024,7 @@ def extract_platform_integrations( for key, domain_config in config.items(): try: domain = cv.domain_key(key) - except vol.Invalid: + except probatio.Invalid: continue if domain not in domains: continue @@ -1050,7 +1049,7 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: """ domain_configs = [] for key in config: - with suppress(vol.Invalid): + with suppress(probatio.Invalid): if cv.domain_key(key) != domain: continue domain_configs.append(key) @@ -1096,7 +1095,7 @@ async def _async_load_and_validate_platform_integration( # Validate platform specific schema try: return platform.PLATFORM_SCHEMA(p_integration.config) # type: ignore[no-any-return] - except vol.Invalid as exc: + except probatio.Invalid as exc: exc_info = ConfigExceptionInfo( exc, ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR, @@ -1179,7 +1178,7 @@ async def async_process_component_config( return IntegrationConfigInfo( await config_validator.async_validate_config(hass, config), [] ) - except (vol.Invalid, HomeAssistantError) as exc: + except (probatio.Invalid, HomeAssistantError) as exc: exc_info = ConfigExceptionInfo( exc, ConfigErrorTranslationKey.CONFIG_VALIDATION_ERR, @@ -1206,7 +1205,7 @@ async def async_process_component_config( return IntegrationConfigInfo( await cv.async_validate(hass, component.CONFIG_SCHEMA, config), [] ) - except vol.Invalid as exc: + except probatio.Invalid as exc: exc_info = ConfigExceptionInfo( exc, ConfigErrorTranslationKey.CONFIG_VALIDATION_ERR, @@ -1243,7 +1242,7 @@ async def async_process_component_config( p_validated = await cv.async_validate( hass, component_platform_schema, p_config ) - except vol.Invalid as exc: + except probatio.Invalid as exc: exc_info = ConfigExceptionInfo( exc, ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR, diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 89077df7488d..e39a6e505c55 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -24,8 +24,8 @@ from types import MappingProxyType from typing import TYPE_CHECKING, Any, Self, TypedDict, cast, override from async_interrupt import interrupt +import probatio from propcache.api import cached_property -import voluptuous as vol from . import data_entry_flow, loader from .const import ( @@ -3618,7 +3618,7 @@ class ConfigFlow(ConfigEntryBaseFlow): self, *, step_id: str | None = None, - data_schema: vol.Schema | None = None, + data_schema: probatio.Schema | None = None, errors: dict[str, str] | None = None, description_placeholders: Mapping[str, str] | None = None, last_step: bool | None = None, diff --git a/homeassistant/core.py b/homeassistant/core.py index 5bc5f94c85e3..34fd164c47dc 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -41,8 +41,8 @@ from typing import ( override, ) +import probatio from propcache.api import cached_property, under_cached_property -import voluptuous as vol from . import util from .const import ( @@ -2692,7 +2692,7 @@ class ServiceRegistry: [ServiceCall], Coroutine[Any, Any, ServiceResponse] | ServiceResponse | None, ], - schema: vol.Schema | None = None, + schema: probatio.Schema | None = None, supports_response: SupportsResponse = SupportsResponse.NONE, ) -> None: """Register a service. @@ -2924,7 +2924,7 @@ class ServiceRegistry: if handler.schema: try: processed_data: dict[str, Any] = handler.schema(service_data) - except vol.Invalid: + except probatio.Invalid: _LOGGER.debug( "Invalid data for service call %s.%s: %s", domain, diff --git a/homeassistant/core_config.py b/homeassistant/core_config.py index 82413efcc721..93609b2cbc32 100644 --- a/homeassistant/core_config.py +++ b/homeassistant/core_config.py @@ -10,7 +10,7 @@ import pathlib from typing import TYPE_CHECKING, Any, Final, override from urllib.parse import urlparse -import voluptuous as vol +import probatio from webrtc_models import RTCConfiguration, RTCIceServer import yarl @@ -112,7 +112,7 @@ def _no_duplicate_auth_provider( for config in configs: key = (config[CONF_TYPE], config.get(CONF_ID)) if key in config_keys: - raise vol.Invalid( + raise probatio.Invalid( f"Duplicate auth provider {config[CONF_TYPE]} found. " "Please add unique IDs " "if you want to have the same auth provider twice" @@ -135,7 +135,7 @@ def _no_duplicate_auth_mfa_module( for config in configs: key = config.get(CONF_ID, config[CONF_TYPE]) if key in config_keys: - raise vol.Invalid( + raise probatio.Invalid( f"Duplicate mfa module {config[CONF_TYPE]} found. " "Please add unique IDs " "if you want to have the same mfa module twice" @@ -158,29 +158,31 @@ def _filter_bad_internal_external_urls(conf: dict) -> dict: # Schema for all packages element -_PACKAGES_CONFIG_SCHEMA = vol.Schema({cv.string: vol.Any(dict, list)}) +_PACKAGES_CONFIG_SCHEMA = probatio.Schema({cv.string: probatio.Any(dict, list)}) # Schema for individual package definition -_PACKAGE_DEFINITION_SCHEMA = vol.Schema({cv.string: vol.Any(dict, list, None)}) - -_CUSTOMIZE_DICT_SCHEMA = vol.Schema( - { - vol.Optional(ATTR_FRIENDLY_NAME): cv.string, - vol.Optional(ATTR_HIDDEN): cv.boolean, - vol.Optional(ATTR_ASSUMED_STATE): cv.boolean, - }, - extra=vol.ALLOW_EXTRA, +_PACKAGE_DEFINITION_SCHEMA = probatio.Schema( + {cv.string: probatio.Any(dict, list, None)} ) -_CUSTOMIZE_CONFIG_SCHEMA = vol.Schema( +_CUSTOMIZE_DICT_SCHEMA = probatio.Schema( { - vol.Optional(CONF_CUSTOMIZE, default={}): vol.Schema( + probatio.Optional(ATTR_FRIENDLY_NAME): cv.string, + probatio.Optional(ATTR_HIDDEN): cv.boolean, + probatio.Optional(ATTR_ASSUMED_STATE): cv.boolean, + }, + extra=probatio.ALLOW_EXTRA, +) + +_CUSTOMIZE_CONFIG_SCHEMA = probatio.Schema( + { + probatio.Optional(CONF_CUSTOMIZE, default={}): probatio.Schema( {cv.entity_id: _CUSTOMIZE_DICT_SCHEMA} ), - vol.Optional(CONF_CUSTOMIZE_DOMAIN, default={}): vol.Schema( + probatio.Optional(CONF_CUSTOMIZE_DOMAIN, default={}): probatio.Schema( {cv.string: _CUSTOMIZE_DICT_SCHEMA} ), - vol.Optional(CONF_CUSTOMIZE_GLOB, default={}): vol.Schema( + probatio.Optional(CONF_CUSTOMIZE_GLOB, default={}): probatio.Schema( {cv.string: _CUSTOMIZE_DICT_SCHEMA} ), } @@ -243,8 +245,8 @@ def _raise_issue_if_no_country(hass: HomeAssistant, country: str | None) -> None def _validate_currency(data: Any) -> Any: try: return cv.currency(data) - except vol.InInvalid: - with suppress(vol.InInvalid): + except probatio.InInvalid: + with suppress(probatio.InInvalid): return cv.historic_currency(data) raise @@ -255,43 +257,43 @@ def validate_stun_or_turn_url(value: Any) -> str: url = urlparse(url_in) if url.scheme not in ("stun", "stuns", "turn", "turns"): - raise vol.Invalid("invalid url") + raise probatio.Invalid("invalid url") return url_in -CORE_CONFIG_SCHEMA = vol.All( +CORE_CONFIG_SCHEMA = probatio.All( _CUSTOMIZE_CONFIG_SCHEMA.extend( { - CONF_NAME: vol.Coerce(str), + CONF_NAME: probatio.Coerce(str), CONF_LATITUDE: cv.latitude, CONF_LONGITUDE: cv.longitude, - CONF_ELEVATION: vol.Coerce(int), + CONF_ELEVATION: probatio.Coerce(int), CONF_RADIUS: cv.positive_int, - vol.Remove(CONF_TEMPERATURE_UNIT): cv.temperature_unit, - CONF_UNIT_SYSTEM: vol.Any( + probatio.Remove(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + CONF_UNIT_SYSTEM: probatio.Any( _CONF_UNIT_SYSTEM_METRIC, _CONF_UNIT_SYSTEM_US_CUSTOMARY, _CONF_UNIT_SYSTEM_IMPERIAL, ), CONF_TIME_ZONE: cv.time_zone, - vol.Optional(CONF_INTERNAL_URL): cv.url, - vol.Optional(CONF_EXTERNAL_URL): cv.url, - vol.Optional(CONF_ALLOWLIST_EXTERNAL_DIRS): vol.All( - cv.ensure_list, [vol.IsDir()] + probatio.Optional(CONF_INTERNAL_URL): cv.url, + probatio.Optional(CONF_EXTERNAL_URL): cv.url, + probatio.Optional(CONF_ALLOWLIST_EXTERNAL_DIRS): probatio.All( + cv.ensure_list, [probatio.IsDir()] ), - vol.Optional(LEGACY_CONF_WHITELIST_EXTERNAL_DIRS): vol.All( - cv.ensure_list, [vol.IsDir()] + probatio.Optional(LEGACY_CONF_WHITELIST_EXTERNAL_DIRS): probatio.All( + cv.ensure_list, [probatio.IsDir()] ), - vol.Optional(CONF_ALLOWLIST_EXTERNAL_URLS): vol.All( + probatio.Optional(CONF_ALLOWLIST_EXTERNAL_URLS): probatio.All( cv.ensure_list, [cv.url] ), - vol.Optional(CONF_PACKAGES, default={}): _PACKAGES_CONFIG_SCHEMA, - vol.Optional(CONF_AUTH_PROVIDERS): vol.All( + probatio.Optional(CONF_PACKAGES, default={}): _PACKAGES_CONFIG_SCHEMA, + probatio.Optional(CONF_AUTH_PROVIDERS): probatio.All( cv.ensure_list, [ auth_providers.AUTH_PROVIDER_SCHEMA.extend( { - CONF_TYPE: vol.NotIn( + CONF_TYPE: probatio.NotIn( ["insecure_example"], ( "The insecure_example auth provider" @@ -303,12 +305,12 @@ CORE_CONFIG_SCHEMA = vol.All( ], _no_duplicate_auth_provider, ), - vol.Optional(CONF_AUTH_MFA_MODULES): vol.All( + probatio.Optional(CONF_AUTH_MFA_MODULES): probatio.All( cv.ensure_list, [ auth_mfa_modules.MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( { - CONF_TYPE: vol.NotIn( + CONF_TYPE: probatio.NotIn( ["insecure_example"], "The insecure_example mfa module is for testing only.", ) @@ -317,24 +319,26 @@ CORE_CONFIG_SCHEMA = vol.All( ], _no_duplicate_auth_mfa_module, ), - vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()), - vol.Remove(CONF_LEGACY_TEMPLATES): cv.boolean, - vol.Optional(CONF_CURRENCY): _validate_currency, - vol.Optional(CONF_COUNTRY): cv.country, - vol.Optional(CONF_LANGUAGE): cv.language, - vol.Optional(CONF_DEBUG): cv.boolean, - vol.Optional(CONF_WEBRTC): vol.Schema( + probatio.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys( + probatio.IsDir() + ), + probatio.Remove(CONF_LEGACY_TEMPLATES): cv.boolean, + probatio.Optional(CONF_CURRENCY): _validate_currency, + probatio.Optional(CONF_COUNTRY): cv.country, + probatio.Optional(CONF_LANGUAGE): cv.language, + probatio.Optional(CONF_DEBUG): cv.boolean, + probatio.Optional(CONF_WEBRTC): probatio.Schema( { - vol.Required(CONF_ICE_SERVERS): vol.All( + probatio.Required(CONF_ICE_SERVERS): probatio.All( cv.ensure_list, [ - vol.Schema( + probatio.Schema( { - vol.Required(CONF_URL): vol.All( + probatio.Required(CONF_URL): probatio.All( cv.ensure_list, [validate_stun_or_turn_url] ), - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_CREDENTIAL): cv.string, + probatio.Optional(CONF_USERNAME): cv.string, + probatio.Optional(CONF_CREDENTIAL): cv.string, } ) ], @@ -352,7 +356,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non This method is a coroutine. """ - # CORE_CONFIG_SCHEMA is not async safe since it uses vol.IsDir + # CORE_CONFIG_SCHEMA is not async safe since it uses probatio.IsDir # so we need to run it in an executor job. config = await hass.async_add_executor_job(CORE_CONFIG_SCHEMA, config) @@ -469,7 +473,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non try: pkg_cust = _CUSTOMIZE_CONFIG_SCHEMA(pkg_cust) - except vol.Invalid: + except probatio.Invalid: _LOGGER.warning("Package %s contains invalid customize", name) continue @@ -884,7 +888,7 @@ class Config: "language" in owner_store.data and "language" in owner_store.data["language"] ): - with suppress(vol.InInvalid): + with suppress(probatio.InInvalid): data["language"] = cv.language( owner_store.data["language"]["language"] ) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index deb8d5ae2226..adcc791fb071 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -12,7 +12,7 @@ import logging from types import MappingProxyType from typing import Any, Generic, Required, TypedDict, TypeVar, cast -import voluptuous as vol +import probatio from .core import HomeAssistant, callback from .exceptions import HomeAssistantError @@ -87,7 +87,7 @@ class UnknownStep(FlowError): """Unknown step specified.""" -class InvalidData(vol.Invalid): +class InvalidData(probatio.Invalid): """Invalid data provided.""" def __init__( @@ -130,7 +130,7 @@ class FlowResult(TypedDict, Generic[_FlowContextT, _HandlerT], total=False): """Typed result dict.""" context: _FlowContextT - data_schema: vol.Schema | None + data_schema: probatio.Schema | None data: Mapping[str, Any] description_placeholders: Mapping[str, str] | None description: str | None @@ -155,8 +155,8 @@ class FlowResult(TypedDict, Generic[_FlowContextT, _HandlerT], total=False): def _map_error_to_schema_errors( schema_errors: dict[str, Any], - error: vol.Invalid, - data_schema: vol.Schema, + error: probatio.Invalid, + data_schema: probatio.Schema, ) -> None: """Map an error to the correct position in the schema_errors. @@ -171,7 +171,7 @@ def _map_error_to_schema_errors( if len(error_path) > 1: raise ValueError("Nested schemas are not supported") - # path_part can also be vol.Marker, but we need a string key + # path_part can also be probatio.Marker, but we need a string key path_part_str = str(path_part) schema_errors[path_part_str] = error.error_message @@ -355,12 +355,12 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]): if ( data_schema := cur_step.get("data_schema") ) is not None and user_input is not None: - data_schema = cast(vol.Schema, data_schema) + data_schema = cast(probatio.Schema, data_schema) try: user_input = data_schema(user_input) - except vol.Invalid as ex: + except probatio.Invalid as ex: raised_errors = [ex] - if isinstance(ex, vol.MultipleInvalid): + if isinstance(ex, probatio.MultipleInvalid): raised_errors = ex.errors schema_errors: dict[str, Any] = {} @@ -666,8 +666,8 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]): return True def add_suggested_values_to_schema( - self, data_schema: vol.Schema, suggested_values: Mapping[str, Any] | None - ) -> vol.Schema: + self, data_schema: probatio.Schema, suggested_values: Mapping[str, Any] | None + ) -> probatio.Schema: """Make a copy of the schema, populated with suggested values. For each schema marker matching items in `suggested_values`, @@ -694,20 +694,20 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]): if ( suggested_values and key in suggested_values - and isinstance(key, vol.Marker) + and isinstance(key, probatio.Marker) ): # Copy the marker to not modify the flow schema new_key = copy.copy(key) new_key.description = {"suggested_value": suggested_values[key.schema]} schema[new_key] = val - return vol.Schema(schema) + return probatio.Schema(schema) @callback def async_show_form( self, *, step_id: str | None = None, - data_schema: vol.Schema | None = None, + data_schema: probatio.Schema | None = None, errors: dict[str, str] | None = None, description_placeholders: Mapping[str, str] | None = None, last_step: bool | None = None, @@ -891,7 +891,7 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]): type=FlowResultType.MENU, flow_id=self.flow_id, handler=self.handler, - data_schema=vol.Schema({"next_step_id": vol.In(menu_options)}), + data_schema=probatio.Schema({"next_step_id": probatio.In(menu_options)}), menu_options=menu_options, description_placeholders=description_placeholders, ) @@ -939,14 +939,14 @@ class SectionConfig(TypedDict, total=False): class section: """Data entry flow section.""" - CONFIG_SCHEMA = vol.Schema( + CONFIG_SCHEMA = probatio.Schema( { - vol.Optional("collapsed", default=False): bool, + probatio.Optional("collapsed", default=False): bool, }, ) def __init__( - self, schema: vol.Schema, options: SectionConfig | None = None + self, schema: probatio.Schema, options: SectionConfig | None = None ) -> None: """Initialize.""" self.schema = schema diff --git a/homeassistant/helpers/automation.py b/homeassistant/helpers/automation.py index 4b1199e48a9c..98e4c8b59e40 100644 --- a/homeassistant/helpers/automation.py +++ b/homeassistant/helpers/automation.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from enum import Enum from typing import Any, Final, Self -import voluptuous as vol +import probatio from homeassistant.const import CONF_OPTIONS from homeassistant.core import HomeAssistant, split_entity_id @@ -78,7 +78,7 @@ def get_relative_description_key(domain: str, key: str) -> str: def move_top_level_schema_fields_to_options( - config: ConfigType, options_schema_dict: dict[vol.Marker, Any] + config: ConfigType, options_schema_dict: dict[probatio.Marker, Any] ) -> ConfigType: """Move top-level fields to options. @@ -102,7 +102,7 @@ def move_top_level_schema_fields_to_options( def move_options_fields_to_top_level( - config: ConfigType, base_schema: vol.Schema + config: ConfigType, base_schema: probatio.Schema ) -> ConfigType: """Move options fields to top-level. @@ -128,7 +128,7 @@ def move_options_fields_to_top_level( try: new_config = base_schema(new_config) - except vol.Invalid: + except probatio.Invalid: return config new_config.update(options) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index c7f302a41f69..fecbf8424d78 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import NamedTuple, Self from annotatedyaml import loader as yaml_loader -import voluptuous as vol +import probatio from homeassistant import loader from homeassistant.config import ( # type: ignore[attr-defined] @@ -114,13 +114,13 @@ async def async_check_ha_config_file( # noqa: C901 result.add_warning(message, domain, pack_config) def _comp_error( - ex: vol.Invalid | HomeAssistantError, + ex: probatio.Invalid | HomeAssistantError, domain: str, component_config: ConfigType, config_to_attach: ConfigType, ) -> None: """Handle errors from components.""" - if isinstance(ex, vol.Invalid): + if isinstance(ex, probatio.Invalid): message = format_schema_error(hass, ex, domain, component_config) else: message = format_homeassistant_error(hass, ex, domain, component_config) @@ -173,7 +173,7 @@ async def async_check_ha_config_file( # noqa: C901 await merge_packages_config( hass, config, core_config.get(CONF_PACKAGES, {}), _pack_error ) - except vol.Invalid as err: + except probatio.Invalid as err: result.add_error( format_schema_error(hass, err, HOMEASSISTANT_DOMAIN, core_config), HOMEASSISTANT_DOMAIN, @@ -224,7 +224,7 @@ async def async_check_ha_config_file( # noqa: C901 await config_validator.async_validate_config(hass, config) )[domain] continue - except (vol.Invalid, HomeAssistantError) as ex: + except (probatio.Invalid, HomeAssistantError) as ex: _comp_error(ex, domain, config, config[domain]) continue except Exception as err: @@ -245,7 +245,7 @@ async def async_check_ha_config_file( # noqa: C901 # Don't fail if the validator removed the domain from the config if domain in validated_config: result[domain] = validated_config[domain] - except vol.Invalid as ex: + except probatio.Invalid as ex: _comp_error(ex, domain, config, config[domain]) continue @@ -265,7 +265,7 @@ async def async_check_ha_config_file( # noqa: C901 p_validated = await cv.async_validate( hass, component_platform_schema, p_config ) - except vol.Invalid as ex: + except probatio.Invalid as ex: _comp_error(ex, domain, p_config, p_config) continue @@ -305,7 +305,7 @@ async def async_check_ha_config_file( # noqa: C901 if platform_schema is not None: try: p_validated = platform_schema(p_validated) - except vol.Invalid as ex: + except probatio.Invalid as ex: _comp_error(ex, f"{domain}.{p_name}", p_config, p_config) continue diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 3a8c8186f78c..07d089b11592 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -11,8 +11,8 @@ import logging from operator import attrgetter from typing import Any, TypedDict, override -import voluptuous as vol -from voluptuous.humanize import humanize_error +import probatio +from probatio.humanize import humanize_error from homeassistant.components import websocket_api from homeassistant.const import CONF_ID @@ -590,7 +590,7 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: f"{self.api_prefix}/list", list_handler, websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( - {vol.Required("type"): f"{self.api_prefix}/list"} + {probatio.Required("type"): f"{self.api_prefix}/list"} ), ) @@ -603,7 +603,7 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( { **self.create_schema, - vol.Required("type"): f"{self.api_prefix}/create", + probatio.Required("type"): f"{self.api_prefix}/create", } ), ) @@ -613,7 +613,7 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: f"{self.api_prefix}/subscribe", subscribe_handler, websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( - {vol.Required("type"): f"{self.api_prefix}/subscribe"} + {probatio.Required("type"): f"{self.api_prefix}/subscribe"} ), ) @@ -626,8 +626,8 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( { **self.update_schema, - vol.Required("type"): f"{self.api_prefix}/update", - vol.Required(self.item_id_key): str, + probatio.Required("type"): f"{self.api_prefix}/update", + probatio.Required(self.item_id_key): str, } ), ) @@ -640,8 +640,8 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: ), websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( { - vol.Required("type"): f"{self.api_prefix}/delete", - vol.Required(self.item_id_key): str, + probatio.Required("type"): f"{self.api_prefix}/delete", + probatio.Required(self.item_id_key): str, } ), ) @@ -663,7 +663,7 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: data.pop("type") item = await self.storage_collection.async_create_item(data) connection.send_result(msg["id"], item) - except vol.Invalid as err: + except probatio.Invalid as err: connection.send_error( msg["id"], websocket_api.ERR_INVALID_FORMAT, @@ -740,7 +740,7 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: websocket_api.ERR_NOT_FOUND, f"Unable to find {self.item_id_key} {item_id}", ) - except vol.Invalid as err: + except probatio.Invalid as err: connection.send_error( msg["id"], websocket_api.ERR_INVALID_FORMAT, diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index b373d5dd4a6f..dae7d54c82fd 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -26,7 +26,7 @@ from typing import ( override, ) -import voluptuous as vol +import probatio from homeassistant.const import ( CONF_ABOVE, @@ -162,33 +162,33 @@ CONDITIONS: HassKey[dict[str, str]] = HassKey("conditions") # Basic schemas to sanity check the condition descriptions, # full validation is done by hassfest.conditions -_FIELD_DESCRIPTION_SCHEMA = vol.Schema( +_FIELD_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Optional(CONF_SELECTOR): selector.validate_selector, + probatio.Optional(CONF_SELECTOR): selector.validate_selector, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) -_CONDITION_DESCRIPTION_SCHEMA = vol.Schema( +_CONDITION_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Optional("target"): TargetSelector.CONFIG_SCHEMA, - vol.Optional("fields"): vol.Schema({str: _FIELD_DESCRIPTION_SCHEMA}), + probatio.Optional("target"): TargetSelector.CONFIG_SCHEMA, + probatio.Optional("fields"): probatio.Schema({str: _FIELD_DESCRIPTION_SCHEMA}), }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) def starts_with_dot(key: str) -> str: """Check if key starts with dot.""" if not key.startswith("."): - raise vol.Invalid("Key does not start with .") + raise probatio.Invalid("Key does not start with .") return key -_CONDITIONS_DESCRIPTION_SCHEMA = vol.Schema( +_CONDITIONS_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Remove(vol.All(str, starts_with_dot)): object, - cv.underscore_slug: vol.Any(None, _CONDITION_DESCRIPTION_SCHEMA), + probatio.Remove(probatio.All(str, starts_with_dot)): object, + cv.underscore_slug: probatio.Any(None, _CONDITION_DESCRIPTION_SCHEMA), } ) @@ -265,16 +265,16 @@ async def _register_condition_platform( _LOGGER.exception("Error while notifying condition platform listener") -_CONDITION_BASE_SCHEMA = vol.Schema( +_CONDITION_BASE_SCHEMA = probatio.Schema( { **cv.CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): str, + probatio.Required(CONF_CONDITION): str, } ) _CONDITION_SCHEMA = _CONDITION_BASE_SCHEMA.extend( { - vol.Optional(CONF_OPTIONS): object, - vol.Optional(CONF_TARGET): cv.TARGET_FIELDS, + probatio.Optional(CONF_OPTIONS): object, + probatio.Optional(CONF_TARGET): cv.TARGET_FIELDS, } ) @@ -436,14 +436,14 @@ ATTR_BEHAVIOR: Final = "behavior" BEHAVIOR_ANY: Final = "any" BEHAVIOR_ALL: Final = "all" -ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL = vol.Schema( +ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL = probatio.Schema( { - vol.Required(CONF_TARGET): cv.TARGET_FIELDS, - vol.Required(CONF_OPTIONS, default={}): { - vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In( + probatio.Required(CONF_TARGET): cv.TARGET_FIELDS, + probatio.Required(CONF_OPTIONS, default={}): { + probatio.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): probatio.In( [BEHAVIOR_ANY, BEHAVIOR_ALL] ), - vol.Optional(CONF_FOR): cv.positive_time_period, + probatio.Optional(CONF_FOR): cv.positive_time_period, }, } ) @@ -537,7 +537,7 @@ class EntityConditionBase(Condition): _excluded_states: Final[frozenset[str]] = frozenset( {STATE_UNAVAILABLE, STATE_UNKNOWN} ) - _schema: vol.Schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL + _schema: probatio.Schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL # When True, indirect target expansion (via device/area/floor) skips # entities with an entity_category. _primary_entities_only: ClassVar[bool] = True @@ -942,8 +942,8 @@ def make_entity_state_condition( NUMERICAL_CONDITION_SCHEMA = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL.extend( { - vol.Required(CONF_OPTIONS): { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required(CONF_OPTIONS): { + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig(mode=NumericThresholdMode.IS) ), }, @@ -1065,12 +1065,12 @@ def make_entity_numerical_condition( def _make_numerical_condition_with_unit_schema( unit_converter: type[BaseUnitConverter], -) -> vol.Schema: +) -> probatio.Schema: """Factory for numerical condition schema with unit option.""" return ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL.extend( { - vol.Required(CONF_OPTIONS): { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required(CONF_OPTIONS): { + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig( mode=NumericThresholdMode.IS, unit_of_measurement=list(unit_converter.VALID_UNITS), @@ -1746,7 +1746,7 @@ def state( for_period = cv.positive_time_period(render_complex(for_period, variables)) except TemplateError as ex: raise ConditionErrorMessage("state", f"template error: {ex}") from ex - except vol.Invalid as ex: + except probatio.Invalid as ex: raise ConditionErrorMessage("state", f"schema error: {ex}") from ex duration = dt_util.utcnow() - cast(timedelta, for_period) @@ -2014,7 +2014,7 @@ async def async_validate_condition_config( platform_domain, condition_key ) if not (condition_class := condition_descriptors.get(relative_condition_key)): - raise vol.Invalid(f"Invalid condition '{condition_key}' specified") + raise probatio.Invalid(f"Invalid condition '{condition_key}' specified") return await condition_class.async_validate_complete_config(hass, config) config = move_options_fields_to_top_level(config, _CONDITION_BASE_SCHEMA) @@ -2257,7 +2257,7 @@ def _load_conditions_file(integration: Integration) -> dict[str, Any]: "Unable to find conditions.yaml for the %s integration", integration.domain ) return {} - except (HomeAssistantError, vol.Invalid) as ex: + except (HomeAssistantError, probatio.Invalid) as ex: _LOGGER.warning( "Unable to parse conditions.yaml for the %s integration: %s", integration.domain, diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 95926ea7b764..04d5c6313bb0 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -23,7 +23,7 @@ from aiohttp import ClientError, ClientResponseError, client, hdrs, web from habluetooth import BluetoothServiceInfoBleak import jwt from multidict import CIMultiDict -import voluptuous as vol +import probatio from yarl import URL from homeassistant import config_entries @@ -548,11 +548,13 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): return self.async_show_form( step_id="pick_implementation", - data_schema=vol.Schema( + data_schema=probatio.Schema( { - vol.Required( + probatio.Required( "implementation", default=list(implementations)[0] - ): vol.In({key: impl.name for key, impl in implementations.items()}) + ): probatio.In( + {key: impl.name for key, impl in implementations.items()} + ) } ), ) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index b68ffa82f1ac..ae5763a27e7c 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,4 +1,4 @@ -"""Helpers for config validation using voluptuous.""" +"""Helpers for config validation using probatio.""" from collections.abc import Callable, Hashable, Mapping import contextlib @@ -23,8 +23,7 @@ from typing import TYPE_CHECKING, Any, cast, overload from urllib.parse import urlparse from uuid import UUID -from probatio import UNSUPPORTED, to_field_list -import voluptuous as vol +import probatio from homeassistant.const import ( ATTR_AREA_ID, @@ -181,30 +180,32 @@ CONFIGURATION_URL_PROTOCOL_SCHEMA_LIST = frozenset( ) # Home Assistant types -byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) -small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)) -positive_int = vol.All(vol.Coerce(int), vol.Range(min=0)) -positive_float = vol.All(vol.Coerce(float), vol.Range(min=0)) -latitude = vol.All( - vol.Coerce(float), vol.Range(min=-90, max=90), msg="invalid latitude" +byte = probatio.All(probatio.Coerce(int), probatio.Range(min=0, max=255)) +small_float = probatio.All(probatio.Coerce(float), probatio.Range(min=0, max=1)) +positive_int = probatio.All(probatio.Coerce(int), probatio.Range(min=0)) +positive_float = probatio.All(probatio.Coerce(float), probatio.Range(min=0)) +latitude = probatio.All( + probatio.Coerce(float), probatio.Range(min=-90, max=90), msg="invalid latitude" ) -longitude = vol.All( - vol.Coerce(float), vol.Range(min=-180, max=180), msg="invalid longitude" +longitude = probatio.All( + probatio.Coerce(float), probatio.Range(min=-180, max=180), msg="invalid longitude" ) -gps = vol.ExactSequence([latitude, longitude]) -sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)) -port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) +gps = probatio.ExactSequence([latitude, longitude]) +sun_event = probatio.All( + probatio.Lower, probatio.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE) +) +port = probatio.All(probatio.Coerce(int), probatio.Range(min=1, max=65535)) def path(value: Any) -> str: """Validate it's a safe path.""" if not isinstance(value, str): - raise vol.Invalid("Expected a string") + raise probatio.Invalid("Expected a string") try: raise_if_invalid_path(value) except ValueError as err: - raise vol.Invalid("Invalid path") from err + raise probatio.Invalid("Invalid path") from err return value @@ -218,12 +219,12 @@ def has_at_least_one_key(*keys: Any) -> Callable[[dict], dict]: def validate(obj: dict) -> dict: """Test keys exist in dict.""" if not isinstance(obj, dict): - raise vol.Invalid("expected dictionary") + raise probatio.Invalid("expected dictionary") if not key_set.isdisjoint(obj): return obj expected = ", ".join(str(k) for k in keys) - raise vol.Invalid(f"must contain at least one of {expected}.") + raise probatio.Invalid(f"must contain at least one of {expected}.") return validate @@ -234,11 +235,11 @@ def has_at_most_one_key(*keys: Any) -> Callable[[dict], dict]: def validate(obj: dict) -> dict: """Test zero keys exist or one key exists in dict.""" if not isinstance(obj, dict): - raise vol.Invalid("expected dictionary") + raise probatio.Invalid("expected dictionary") if len(set(keys) & set(obj)) > 1: expected = ", ".join(str(k) for k in keys) - raise vol.Invalid(f"must contain at most one of {expected}.") + raise probatio.Invalid(f"must contain at most one of {expected}.") return obj return validate @@ -257,7 +258,7 @@ def boolean(value: Any) -> bool: elif isinstance(value, Number): # type ignore: https://github.com/python/mypy/issues/3186 return value != 0 # type: ignore[comparison-overlap] - raise vol.Invalid(f"invalid boolean value {value}") + raise probatio.Invalid(f"invalid boolean value {value}") def whitespace(value: Any) -> str: @@ -265,7 +266,7 @@ def whitespace(value: Any) -> str: if isinstance(value, str) and (value == "" or value.isspace()): return value - raise vol.Invalid(f"contains non-whitespace: {value}") + raise probatio.Invalid(f"contains non-whitespace: {value}") @not_async_friendly @@ -275,7 +276,7 @@ def isdevice(value: Any) -> str: os.stat(value) return str(value) except OSError as err: - raise vol.Invalid(f"No device at {value} found") from err + raise probatio.Invalid(f"No device at {value} found") from err def matches_regex(regex: str) -> Callable[[Any], str]: @@ -285,10 +286,10 @@ def matches_regex(regex: str) -> Callable[[Any], str]: def validator(value: Any) -> str: """Validate that value matches the given regex.""" if not isinstance(value, str): - raise vol.Invalid(f"not a string value: {value}") + raise probatio.Invalid(f"not a string value: {value}") if not compiled.match(value): - raise vol.Invalid( + raise probatio.Invalid( f"value {value} does not match regular expression {compiled.pattern}" ) @@ -302,11 +303,13 @@ def is_regex(value: Any) -> re.Pattern[Any]: try: r = re.compile(value) except TypeError as err: - raise vol.Invalid( + raise probatio.Invalid( f"value {value} is of the wrong type for a regular expression" ) from err except re.error as err: - raise vol.Invalid(f"value {value} is not a valid regular expression") from err + raise probatio.Invalid( + f"value {value} is not a valid regular expression" + ) from err return r @@ -314,13 +317,13 @@ def is_regex(value: Any) -> re.Pattern[Any]: def isfile(value: Any) -> str: """Validate that the value is an existing file.""" if value is None: - raise vol.Invalid("None is not file") + raise probatio.Invalid("None is not file") file_in = os.path.expanduser(str(value)) if not os.path.isfile(file_in): - raise vol.Invalid("not a file") + raise probatio.Invalid("not a file") if not os.access(file_in, os.R_OK): - raise vol.Invalid("file not readable") + raise probatio.Invalid("file not readable") return file_in @@ -328,13 +331,13 @@ def isfile(value: Any) -> str: def isdir(value: Any) -> str: """Validate that the value is an existing dir.""" if value is None: - raise vol.Invalid("not a directory") + raise probatio.Invalid("not a directory") dir_in = os.path.expanduser(str(value)) if not os.path.isdir(dir_in): - raise vol.Invalid("not a directory") + raise probatio.Invalid("not a directory") if not os.access(dir_in, os.R_OK): - raise vol.Invalid("directory not readable") + raise probatio.Invalid("directory not readable") return dir_in @@ -369,33 +372,35 @@ def entity_id(value: Any) -> str: if valid_entity_id(str_value): return str_value - raise vol.Invalid(f"Entity ID {value} is an invalid entity ID") + raise probatio.Invalid(f"Entity ID {value} is an invalid entity ID") def strict_entity_id(value: Any) -> str: """Validate Entity ID, strictly.""" if not isinstance(value, str): - raise vol.Invalid(f"Entity ID {value} is not a string") + raise probatio.Invalid(f"Entity ID {value} is not a string") if valid_entity_id(value): return value - raise vol.Invalid(f"Entity ID {value} is not a valid entity ID") + raise probatio.Invalid(f"Entity ID {value} is not a valid entity ID") def entity_id_or_uuid(value: Any) -> str: """Validate Entity specified by entity_id or uuid.""" - with contextlib.suppress(vol.Invalid): + with contextlib.suppress(probatio.Invalid): return entity_id(value) - with contextlib.suppress(vol.Invalid): + with contextlib.suppress(probatio.Invalid): return fake_uuid4_hex(value) - raise vol.Invalid(f"Entity {value} is neither a valid entity ID nor a valid UUID") + raise probatio.Invalid( + f"Entity {value} is neither a valid entity ID nor a valid UUID" + ) def _entity_ids(value: str | list, allow_uuid: bool) -> list[str]: """Help validate entity IDs or UUIDs.""" if value is None: - raise vol.Invalid("Entity IDs cannot be None") + raise probatio.Invalid("Entity IDs cannot be None") if isinstance(value, str): value = [ent_id.strip() for ent_id in value.split(",")] @@ -413,13 +418,14 @@ def entity_ids_or_uuids(value: str | list) -> list[str]: return _entity_ids(value, True) -comp_entity_ids = vol.Any( - vol.All(vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), entity_ids +comp_entity_ids = probatio.Any( + probatio.All(probatio.Lower, probatio.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), + entity_ids, ) -comp_entity_ids_or_uuids = vol.Any( - vol.All(vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), +comp_entity_ids_or_uuids = probatio.Any( + probatio.All(probatio.Lower, probatio.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), entity_ids_or_uuids, ) @@ -439,12 +445,12 @@ def domain_key(config_key: Any) -> str: """ if not isinstance(config_key, str): - raise vol.Invalid("invalid domain", path=[config_key]) + raise probatio.Invalid("invalid domain", path=[config_key]) parts = config_key.partition(" ") _domain = parts[0] if parts[2].strip(" ") else config_key if not _domain or _domain.strip(" ") != _domain: - raise vol.Invalid("invalid domain", path=[config_key]) + raise probatio.Invalid("invalid domain", path=[config_key]) return _domain @@ -457,7 +463,7 @@ def entity_domain(domain: str | list[str]) -> Callable[[Any], str]: """Test if entity domain is domain.""" validated = ent_domain(value) if len(validated) != 1: - raise vol.Invalid(f"Expected exactly 1 entity, got {len(validated)}") + raise probatio.Invalid(f"Expected exactly 1 entity, got {len(validated)}") return validated[0] return validate @@ -480,7 +486,7 @@ def entities_domain(domain: str | list[str]) -> Callable[[str | list], list[str] values = entity_ids(values) for ent_id in values: if check_invalid(split_entity_id(ent_id)[0]): - raise vol.Invalid( + raise probatio.Invalid( f"Entity ID '{ent_id}' does not belong to domain '{domain}'" ) return values @@ -488,9 +494,9 @@ def entities_domain(domain: str | list[str]) -> Callable[[str | list], list[str] return validate -def enum(enumClass: type[Enum]) -> vol.All: +def enum(enumClass: type[Enum]) -> probatio.All: """Create validator for specified enum.""" - return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__) + return probatio.All(probatio.In(enumClass.__members__), enumClass.__getitem__) def icon(value: Any) -> str: @@ -500,7 +506,7 @@ def icon(value: Any) -> str: if ":" in str_value: return str_value - raise vol.Invalid('Icons should be specified in the form "prefix:name"') + raise probatio.Invalid('Icons should be specified in the form "prefix:name"') _COLOR_HEX = re.compile(r"^#[0-9A-F]{6}$", re.IGNORECASE) @@ -511,22 +517,22 @@ def color_hex(value: Any) -> str: str_value = str(value) if not _COLOR_HEX.match(str_value): - raise vol.Invalid("Color should be in the format #RRGGBB") + raise probatio.Invalid("Color should be in the format #RRGGBB") return str_value _TIME_PERIOD_DICT_KEYS = ("days", "hours", "minutes", "seconds", "milliseconds") -time_period_dict = vol.All( +time_period_dict = probatio.All( dict, - vol.Schema( + probatio.Schema( { - "days": vol.Coerce(float), - "hours": vol.Coerce(float), - "minutes": vol.Coerce(float), - "seconds": vol.Coerce(float), - "milliseconds": vol.Coerce(float), + "days": probatio.Coerce(float), + "hours": probatio.Coerce(float), + "minutes": probatio.Coerce(float), + "seconds": probatio.Coerce(float), + "milliseconds": probatio.Coerce(float), } ), has_at_least_one_key(*_TIME_PERIOD_DICT_KEYS), @@ -542,10 +548,10 @@ def time(value: Any) -> time_sys: try: time_val = dt_util.parse_time(value) except TypeError as err: - raise vol.Invalid("Not a parseable type") from err + raise probatio.Invalid("Not a parseable type") from err if time_val is None: - raise vol.Invalid(f"Invalid time specified: {value}") + raise probatio.Invalid(f"Invalid time specified: {value}") return time_val @@ -558,10 +564,10 @@ def date(value: Any) -> date_sys: try: date_val = dt_util.parse_date(value) except TypeError as err: - raise vol.Invalid("Not a parseable type") from err + raise probatio.Invalid("Not a parseable type") from err if date_val is None: - raise vol.Invalid("Could not parse date") + raise probatio.Invalid("Could not parse date") return date_val @@ -569,9 +575,9 @@ def date(value: Any) -> date_sys: def time_period_str(value: str) -> timedelta: """Validate and transform time offset.""" if isinstance(value, int): # type: ignore[unreachable] - raise vol.Invalid("Make sure you wrap time values in quotes") + raise probatio.Invalid("Make sure you wrap time values in quotes") if not isinstance(value, str): - raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) + raise probatio.Invalid(TIME_PERIOD_ERROR.format(value)) negative_offset = False if value.startswith("-"): @@ -582,7 +588,7 @@ def time_period_str(value: str) -> timedelta: parsed = value.split(":") if len(parsed) not in (2, 3): - raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) + raise probatio.Invalid(TIME_PERIOD_ERROR.format(value)) try: hour = int(parsed[0]) minute = int(parsed[1]) @@ -591,7 +597,7 @@ def time_period_str(value: str) -> timedelta: except IndexError: second = 0 except ValueError as err: - raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) from err + raise probatio.Invalid(TIME_PERIOD_ERROR.format(value)) from err offset = timedelta(hours=hour, minutes=minute, seconds=second) @@ -606,10 +612,12 @@ def time_period_seconds(value: float | str) -> timedelta: try: return timedelta(seconds=float(value)) except (ValueError, TypeError) as err: - raise vol.Invalid(f"Expected seconds, got {value}") from err + raise probatio.Invalid(f"Expected seconds, got {value}") from err -time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict) +time_period = probatio.Any( + time_period_str, time_period_seconds, timedelta, time_period_dict +) def match_all[_T](value: _T) -> _T: @@ -620,12 +628,12 @@ def match_all[_T](value: _T) -> _T: def positive_timedelta(value: timedelta) -> timedelta: """Validate timedelta is positive.""" if value < timedelta(0): - raise vol.Invalid("Time period should be positive") + raise probatio.Invalid("Time period should be positive") return value -positive_time_period_dict = vol.All(time_period_dict, positive_timedelta) -positive_time_period = vol.All(time_period, positive_timedelta) +positive_time_period_dict = probatio.All(time_period_dict, positive_timedelta) +positive_time_period = probatio.All(time_period, positive_timedelta) def remove_falsy[_T](value: list[_T]) -> list[_T]: @@ -640,18 +648,18 @@ def service(value: Any) -> str: if valid_entity_id(str_value): return str_value - raise vol.Invalid(f"Service {value} does not match format .") + raise probatio.Invalid(f"Service {value} does not match format .") def slug(value: Any) -> str: """Validate value is a valid slug.""" if value is None: - raise vol.Invalid("Slug should not be None") + raise probatio.Invalid("Slug should not be None") str_value = str(value) slg = util_slugify(str_value) if str_value == slg: return str_value - raise vol.Invalid(f"invalid slug {value} (try {slg})") + raise probatio.Invalid(f"invalid slug {value} (try {slg})") def underscore_slug(value: Any) -> str: @@ -666,15 +674,15 @@ def schema_with_slug_keys( ) -> Callable: """Ensure dicts have slugs as keys. - Replacement of vol.Schema({cv.slug: value_schema}) to prevent misleading - "Extra keys" errors from voluptuous. + Replacement of probatio.Schema({cv.slug: value_schema}) to prevent misleading + "Extra keys" errors from probatio. """ - schema = vol.Schema({str: value_schema}) + schema = probatio.Schema({str: value_schema}) def verify(value: dict) -> dict: """Validate all keys are slugs and then the value_schema.""" if not isinstance(value, dict): - raise vol.Invalid("expected dictionary") + raise probatio.Invalid("expected dictionary") for key in value: slug_validator(key) @@ -687,17 +695,17 @@ def schema_with_slug_keys( def slugify(value: Any) -> str: """Coerce a value to a slug.""" if value is None: - raise vol.Invalid("Slug should not be None") + raise probatio.Invalid("Slug should not be None") slg = util_slugify(str(value)) if slg: return slg - raise vol.Invalid(f"Unable to slugify {value}") + raise probatio.Invalid(f"Unable to slugify {value}") def string(value: Any) -> str: """Coerce value to string, except for None, list or dict.""" if value is None: - raise vol.Invalid("string value is None") + raise probatio.Invalid("string value is None") # This is expected to be the most common case, so check it first. if type(value) is str or type(value) is NodeStrClass or isinstance(value, str): @@ -707,7 +715,7 @@ def string(value: Any) -> str: value = value.render_result elif isinstance(value, (list, dict)): - raise vol.Invalid("value should be a string") + raise probatio.Invalid("value should be a string") return str(value) @@ -717,7 +725,7 @@ def string_with_no_html(value: Any) -> str: value = string(value) regex = re.compile(r"<[a-z].*?>", re.IGNORECASE) if regex.search(value): - raise vol.Invalid("the string should not contain HTML") + raise probatio.Invalid("the string should not contain HTML") return str(value) @@ -728,44 +736,44 @@ def temperature_unit(value: Any) -> UnitOfTemperature: return UnitOfTemperature.CELSIUS if value == "F": return UnitOfTemperature.FAHRENHEIT - raise vol.Invalid("invalid temperature unit (expected C or F)") + raise probatio.Invalid("invalid temperature unit (expected C or F)") def template(value: Any) -> template_helper.Template: """Validate a jinja2 template.""" if value is None: - raise vol.Invalid("template value is None") + raise probatio.Invalid("template value is None") if isinstance(value, (list, dict, template_helper.Template)): - raise vol.Invalid("template value should be a string") + raise probatio.Invalid("template value should be a string") if not (hass := _async_get_hass_or_none()): - raise vol.Invalid("Validates schema outside the event loop") + raise probatio.Invalid("Validates schema outside the event loop") template_value = template_helper.Template(str(value), hass) try: template_value.ensure_valid() except TemplateError as ex: - raise vol.Invalid(f"invalid template ({ex})") from ex + raise probatio.Invalid(f"invalid template ({ex})") from ex return template_value def dynamic_template(value: Any) -> template_helper.Template: """Validate a dynamic (non static) jinja2 template.""" if value is None: - raise vol.Invalid("template value is None") + raise probatio.Invalid("template value is None") if isinstance(value, (list, dict, template_helper.Template)): - raise vol.Invalid("template value should be a string") + raise probatio.Invalid("template value should be a string") if not template_helper.is_template_string(str(value)): - raise vol.Invalid("template value does not contain a dynamic template") + raise probatio.Invalid("template value does not contain a dynamic template") if not (hass := _async_get_hass_or_none()): - raise vol.Invalid("Validates schema outside the event loop") + raise probatio.Invalid("Validates schema outside the event loop") template_value = template_helper.Template(str(value), hass) try: template_value.ensure_valid() except TemplateError as ex: - raise vol.Invalid(f"invalid template ({ex})") from ex + raise probatio.Invalid(f"invalid template ({ex})") from ex return template_value @@ -790,20 +798,20 @@ def template_complex(value: Any) -> Any: def _positive_time_period_template_complex(value: Any) -> Any: """Do basic validation of a positive time period expressed as a templated dict.""" if not isinstance(value, dict) or not value: - raise vol.Invalid("template should be a dict") + raise probatio.Invalid("template should be a dict") for key, element in value.items(): if not isinstance(key, str): - raise vol.Invalid("key should be a string") + raise probatio.Invalid("key should be a string") if not template_helper.is_template_string(key): - vol.In(_TIME_PERIOD_DICT_KEYS)(key) + probatio.In(_TIME_PERIOD_DICT_KEYS)(key) if not isinstance(element, str) or ( isinstance(element, str) and not template_helper.is_template_string(element) ): - vol.All(vol.Coerce(float), vol.Range(min=0))(element) + probatio.All(probatio.Coerce(float), probatio.Range(min=0))(element) return template_complex(value) -positive_time_period_template = vol.Any( +positive_time_period_template = probatio.Any( positive_time_period, dynamic_template, _positive_time_period_template_complex ) @@ -819,7 +827,7 @@ def datetime(value: Any) -> datetime_sys: date_val = None if date_val is None: - raise vol.Invalid(f"Invalid datetime specified: {value}") + raise probatio.Invalid(f"Invalid datetime specified: {value}") return date_val @@ -828,13 +836,13 @@ def time_zone(value: str) -> str: """Validate timezone.""" if dt_util.get_time_zone(value) is not None: return value - raise vol.Invalid( + raise probatio.Invalid( "Invalid time zone passed in. Valid options can be found here: " "http://en.wikipedia.org/wiki/List_of_tz_database_time_zones" ) -weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)]) +weekdays = probatio.All(ensure_list, [probatio.In(WEEKDAYS)]) def socket_timeout(value: Any | None) -> object: @@ -849,8 +857,8 @@ def socket_timeout(value: Any | None) -> object: if float_value > 0.0: return float_value except Exception as err: - raise vol.Invalid(f"Invalid socket timeout: {err}") from err - raise vol.Invalid("Invalid socket timeout value. float > 0.0 required.") + raise probatio.Invalid(f"Invalid socket timeout: {err}") from err + raise probatio.Invalid("Invalid socket timeout value. float > 0.0 required.") def url( @@ -862,13 +870,13 @@ def url( parsed = urlparse(url_in) if parsed.scheme not in _schema_list: - raise vol.Invalid("invalid url") + raise probatio.Invalid("invalid url") try: _port = parsed.port except ValueError as err: - raise vol.Invalid("invalid url") from err - return cast(str, vol.Schema(vol.Url())(url_in)) + raise probatio.Invalid("invalid url") from err + return cast(str, probatio.Schema(probatio.Url())(url_in)) def configuration_url(value: Any) -> str: @@ -881,7 +889,7 @@ def url_no_path(value: Any) -> str: url_in = url(value) if urlparse(url_in).path not in ("", "/"): - raise vol.Invalid("url is not allowed to have a path component") + raise probatio.Invalid("url is not allowed to have a path component") return url_in @@ -890,7 +898,7 @@ def x10_address(value: str) -> str: """Validate an x10 address.""" regex = re.compile(r"([A-Pa-p]{1})(?:[2-9]|1[0-6]?)$") if not regex.match(value): - raise vol.Invalid("Invalid X10 Address") + raise probatio.Invalid("Invalid X10 Address") return str(value).lower() @@ -899,11 +907,13 @@ def uuid4_hex(value: Any) -> str: try: result = UUID(value, version=4) except (ValueError, AttributeError, TypeError) as error: - raise vol.Invalid("Invalid Version4 UUID", error_message=str(error)) from error + raise probatio.Invalid( + "Invalid Version4 UUID", error_message=str(error) + ) from error if result.hex != value.lower(): # UUID() will create a uuid4 if input is invalid - raise vol.Invalid("Invalid Version4 UUID") + raise probatio.Invalid("Invalid Version4 UUID") return result.hex @@ -915,9 +925,9 @@ def fake_uuid4_hex(value: Any) -> str: """Validate a fake v4 UUID generated by random_uuid_hex.""" try: if not _FAKE_UUID_4_HEX.match(value): - raise vol.Invalid("Invalid UUID") + raise probatio.Invalid("Invalid UUID") except TypeError as exc: - raise vol.Invalid("Invalid UUID") from exc + raise probatio.Invalid("Invalid UUID") from exc return cast(str, value) # Pattern.match throws if input is not a string @@ -938,11 +948,11 @@ class multi_select: def __call__(self, selected: list) -> list: """Validate input.""" if not isinstance(selected, list): - raise vol.Invalid("Not a list") + raise probatio.Invalid("Not a list") for value in selected: if value not in self.options: - raise vol.Invalid(f"{value} is not a valid option") + raise probatio.Invalid(f"{value} is not a valid option") return selected @@ -995,7 +1005,7 @@ def _deprecated_or_removed( arguments = (key, near, option_status) if raise_if_present: - raise vol.Invalid(warning % arguments) + raise probatio.Invalid(warning % arguments) get_integration_logger(__name__).log(level, warning, *arguments) value = config[key] @@ -1078,7 +1088,7 @@ def renamed( if old_key in value: if new_key in value: - raise vol.Invalid( + raise probatio.Invalid( f"Cannot specify both '{old_key}' and" f" '{new_key}'. Please use '{new_key}' only." ) @@ -1115,7 +1125,7 @@ def key_value_schemas( def key_value_validator(value: Any) -> dict[Hashable, Any]: if not isinstance(value, dict): - raise vol.Invalid("Expected a dictionary") + raise probatio.Invalid("Expected a dictionary") key_value = value.get(key) @@ -1123,7 +1133,7 @@ def key_value_schemas( return cast(dict[Hashable, Any], value_schemas[key_value](value)) if default_schema: - with contextlib.suppress(vol.Invalid): + with contextlib.suppress(probatio.Invalid): return cast(dict[Hashable, Any], default_schema(value)) if list_alternatives: @@ -1133,7 +1143,7 @@ def key_value_schemas( else: # mypy does not understand that default_description is not None here alternatives = default_description # type: ignore[assignment] - raise vol.Invalid( + raise probatio.Invalid( f"Unexpected value for {key}: '{key_value}'. Expected {alternatives}" ) @@ -1151,9 +1161,9 @@ def key_dependency[_KT: Hashable, _VT]( def validator(value: dict[_KT, _VT]) -> dict[_KT, _VT]: """Test dependencies.""" if not isinstance(value, dict): - raise vol.Invalid("key dependencies require a dict") + raise probatio.Invalid("key dependencies require a dict") if key in value and dependency not in value: - raise vol.Invalid( + raise probatio.Invalid( f'dependency violation - key "{key}" requires ' f'key "{dependency}" to exist' ) @@ -1164,12 +1174,12 @@ def key_dependency[_KT: Hashable, _VT]( def custom_serializer(schema: Any) -> Any: - """Serialize additional types for voluptuous_serialize.""" + """Serialize additional types for to_field_list.""" return _custom_serializer(schema, allow_section=True) def _custom_serializer(schema: Any, *, allow_section: bool) -> Any: - """Serialize additional types for voluptuous_serialize.""" + """Serialize additional types for to_field_list.""" from homeassistant import data_entry_flow # noqa: PLC0415 from . import selector # noqa: PLC0415 @@ -1188,7 +1198,7 @@ def _custom_serializer(schema: Any, *, allow_section: bool) -> Any: raise ValueError("Nesting expandable sections is not supported") return { "type": "expandable", - "schema": to_field_list( + "schema": probatio.to_field_list( schema.schema, custom_serializer=functools.partial( _custom_serializer, allow_section=False @@ -1203,7 +1213,7 @@ def _custom_serializer(schema: Any, *, allow_section: bool) -> Any: if isinstance(schema, selector.Selector): return schema.serialize() - return UNSUPPORTED + return probatio.UNSUPPORTED # Schemas @@ -1296,42 +1306,42 @@ def platform_only_config_schema(domain: str) -> Callable[[dict], dict]: ) -PLATFORM_SCHEMA = vol.Schema( +PLATFORM_SCHEMA = probatio.Schema( { - vol.Required(CONF_PLATFORM): string, - vol.Optional(CONF_ENTITY_NAMESPACE): string, - vol.Optional(CONF_SCAN_INTERVAL): time_period, + probatio.Required(CONF_PLATFORM): string, + probatio.Optional(CONF_ENTITY_NAMESPACE): string, + probatio.Optional(CONF_SCAN_INTERVAL): time_period, } ) -PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) +PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=probatio.ALLOW_EXTRA) TARGET_FIELDS: VolDictType = { - vol.Optional(ATTR_ENTITY_ID): vol.All(ensure_list, [strict_entity_id]), - vol.Optional(ATTR_DEVICE_ID): vol.All(ensure_list, [str]), - vol.Optional(ATTR_AREA_ID): vol.All(ensure_list, [str]), - vol.Optional(ATTR_FLOOR_ID): vol.All(ensure_list, [str]), - vol.Optional(ATTR_LABEL_ID): vol.All(ensure_list, [str]), + probatio.Optional(ATTR_ENTITY_ID): probatio.All(ensure_list, [strict_entity_id]), + probatio.Optional(ATTR_DEVICE_ID): probatio.All(ensure_list, [str]), + probatio.Optional(ATTR_AREA_ID): probatio.All(ensure_list, [str]), + probatio.Optional(ATTR_FLOOR_ID): probatio.All(ensure_list, [str]), + probatio.Optional(ATTR_LABEL_ID): probatio.All(ensure_list, [str]), } ENTITY_SERVICE_FIELDS: VolDictType = { - vol.Optional(ATTR_ENTITY_ID): comp_entity_ids, - vol.Optional(ATTR_DEVICE_ID): vol.Any( + probatio.Optional(ATTR_ENTITY_ID): comp_entity_ids, + probatio.Optional(ATTR_DEVICE_ID): probatio.Any( ENTITY_MATCH_NONE, - vol.All(ensure_list, [str]), + probatio.All(ensure_list, [str]), ), - vol.Optional(ATTR_AREA_ID): vol.Any( + probatio.Optional(ATTR_AREA_ID): probatio.Any( ENTITY_MATCH_NONE, - vol.All(ensure_list, [str]), + probatio.All(ensure_list, [str]), ), - vol.Optional(ATTR_FLOOR_ID): vol.Any( + probatio.Optional(ATTR_FLOOR_ID): probatio.Any( ENTITY_MATCH_NONE, - vol.All(ensure_list, [str]), + probatio.All(ensure_list, [str]), ), - vol.Optional(ATTR_LABEL_ID): vol.Any( + probatio.Optional(ATTR_LABEL_ID): probatio.Any( ENTITY_MATCH_NONE, - vol.All(ensure_list, [str]), + probatio.All(ensure_list, [str]), ), } @@ -1339,7 +1349,7 @@ TARGET_SERVICE_FIELDS: VolDictType = { # Same as ENTITY_SERVICE_FIELDS but supports specifying entity # by entity registry ID. **ENTITY_SERVICE_FIELDS, - vol.Optional(ATTR_ENTITY_ID): comp_entity_ids_or_uuids, + probatio.Optional(ATTR_ENTITY_ID): comp_entity_ids_or_uuids, } _TARGET_SERVICE_FIELDS_TEMPLATED: VolDictType = { @@ -1350,30 +1360,30 @@ _TARGET_SERVICE_FIELDS_TEMPLATED: VolDictType = { # # The schema supports templates as it is meant to be used in the initial validation # before templates are automatically rendered by the core logic. - vol.Optional(ATTR_ENTITY_ID): vol.Any( + probatio.Optional(ATTR_ENTITY_ID): probatio.Any( comp_entity_ids_or_uuids, dynamic_template, - vol.All(list, template_complex), + probatio.All(list, template_complex), ), - vol.Optional(ATTR_DEVICE_ID): vol.Any( + probatio.Optional(ATTR_DEVICE_ID): probatio.Any( ENTITY_MATCH_NONE, dynamic_template, - vol.All(ensure_list, [vol.Any(dynamic_template, str)]), + probatio.All(ensure_list, [probatio.Any(dynamic_template, str)]), ), - vol.Optional(ATTR_AREA_ID): vol.Any( + probatio.Optional(ATTR_AREA_ID): probatio.Any( ENTITY_MATCH_NONE, dynamic_template, - vol.All(ensure_list, [vol.Any(dynamic_template, str)]), + probatio.All(ensure_list, [probatio.Any(dynamic_template, str)]), ), - vol.Optional(ATTR_FLOOR_ID): vol.Any( + probatio.Optional(ATTR_FLOOR_ID): probatio.Any( ENTITY_MATCH_NONE, dynamic_template, - vol.All(ensure_list, [vol.Any(dynamic_template, str)]), + probatio.All(ensure_list, [probatio.Any(dynamic_template, str)]), ), - vol.Optional(ATTR_LABEL_ID): vol.Any( + probatio.Optional(ATTR_LABEL_ID): probatio.Any( ENTITY_MATCH_NONE, dynamic_template, - vol.All(ensure_list, [vol.Any(dynamic_template, str)]), + probatio.All(ensure_list, [probatio.Any(dynamic_template, str)]), ), } @@ -1385,15 +1395,15 @@ def is_entity_service_schema(validator: VolSchemaType) -> bool: The validator must be either of: - A validator returned by cv._make_entity_service_schema - - A validator returned by cv._make_entity_service_schema, wrapped in a vol.Schema - - A validator returned by cv._make_entity_service_schema, wrapped in a vol.All + - A validator returned by cv._make_entity_service_schema, wrapped in a probatio.Schema + - A validator returned by cv._make_entity_service_schema, wrapped in a probatio.All Nesting is allowed. """ if hasattr(validator, "_entity_service_schema"): return True - if isinstance(validator, (vol.All)): + if isinstance(validator, (probatio.All)): return any(is_entity_service_schema(val) for val in validator.validators) - if isinstance(validator, (vol.Schema)): + if isinstance(validator, (probatio.Schema)): return is_entity_service_schema(validator.schema) return False @@ -1401,11 +1411,11 @@ def is_entity_service_schema(validator: VolSchemaType) -> bool: def _make_entity_service_schema(schema: dict, extra: int) -> VolSchemaType: """Create an entity service schema.""" - validator = vol.All( - vol.Schema( + validator = probatio.All( + probatio.Schema( { # The frontend stores data here. Don't use in core. - vol.Remove("metadata"): dict, + probatio.Remove("metadata"): dict, **schema, **ENTITY_SERVICE_FIELDS, }, @@ -1414,20 +1424,20 @@ def _make_entity_service_schema(schema: dict, extra: int) -> VolSchemaType: _HAS_ENTITY_SERVICE_FIELD, ) setattr(validator, "_entity_service_schema", True) # noqa: B010 - # Wrap in a vol.Schema so the vol.All compiles its sub-validators once, - # instead of re-wrapping them in a new vol.Schema on every validation as a - # top-level vol.All does. - return vol.Schema(validator) + # Wrap in a probatio.Schema so the probatio.All compiles its sub-validators once, + # instead of re-wrapping them in a new probatio.Schema on every validation as a + # top-level probatio.All does. + return probatio.Schema(validator) -BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, vol.PREVENT_EXTRA) +BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, probatio.PREVENT_EXTRA) def make_entity_service_schema( - schema: dict | None, *, extra: int = vol.PREVENT_EXTRA + schema: dict | None, *, extra: int = probatio.PREVENT_EXTRA ) -> VolSchemaType: """Create an entity service schema.""" - if not schema and extra == vol.PREVENT_EXTRA: + if not schema and extra == probatio.PREVENT_EXTRA: # If the schema is empty and we don't allow extra keys, we can return # the base schema and avoid compiling a new schema which is the case # for ~50% of services. @@ -1435,11 +1445,11 @@ def make_entity_service_schema( return _make_entity_service_schema(schema or {}, extra) -SCRIPT_CONVERSATION_RESPONSE_SCHEMA = vol.Any(template, None) +SCRIPT_CONVERSATION_RESPONSE_SCHEMA = probatio.Any(template, None) -SCRIPT_VARIABLES_SCHEMA = vol.All( - vol.Schema({str: template_complex}), +SCRIPT_VARIABLES_SCHEMA = probatio.All( + probatio.Schema({str: template_complex}), # pylint: disable-next=unnecessary-lambda lambda val: script_variables_helper.ScriptVariables(val), ) @@ -1448,31 +1458,33 @@ SCRIPT_VARIABLES_SCHEMA = vol.All( def script_action(value: Any) -> dict: """Validate a script action.""" if not isinstance(value, dict): - raise vol.Invalid("expected dictionary") + raise probatio.Invalid("expected dictionary") try: action = determine_script_action(value) except ValueError as err: - raise vol.Invalid(str(err)) from err + raise probatio.Invalid(str(err)) from err return ACTION_TYPE_SCHEMAS[action](value) -SCRIPT_SCHEMA = vol.All(ensure_list, [script_action]) +SCRIPT_SCHEMA = probatio.All(ensure_list, [script_action]) SCRIPT_ACTION_BASE_SCHEMA: VolDictType = { - vol.Optional(CONF_ALIAS): string, - vol.Remove(CONF_NOTE): str, # Is only used in frontend - vol.Optional(CONF_CONTINUE_ON_ERROR): boolean, - vol.Optional(CONF_ENABLED): vol.Any(boolean, template), + probatio.Optional(CONF_ALIAS): string, + probatio.Remove(CONF_NOTE): str, # Is only used in frontend + probatio.Optional(CONF_CONTINUE_ON_ERROR): boolean, + probatio.Optional(CONF_ENABLED): probatio.Any(boolean, template), } -EVENT_SCHEMA = vol.Schema( +EVENT_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_EVENT): string, - vol.Optional(CONF_EVENT_DATA): vol.All(dict, template_complex), - vol.Optional(CONF_EVENT_DATA_TEMPLATE): vol.All(dict, template_complex), + probatio.Required(CONF_EVENT): string, + probatio.Optional(CONF_EVENT_DATA): probatio.All(dict, template_complex), + probatio.Optional(CONF_EVENT_DATA_TEMPLATE): probatio.All( + dict, template_complex + ), } ) @@ -1486,7 +1498,7 @@ def _backward_compat_service_schema(value: Any | None) -> Any: # `service` has been renamed to `action` if CONF_SERVICE in value: if CONF_ACTION in value: - raise vol.Invalid( + raise probatio.Invalid( "Cannot specify both 'service' and 'action'. Please use 'action' only." ) value[CONF_ACTION] = value.pop(CONF_SERVICE) @@ -1494,56 +1506,56 @@ def _backward_compat_service_schema(value: Any | None) -> Any: return value -SERVICE_SCHEMA = vol.All( +SERVICE_SCHEMA = probatio.All( _backward_compat_service_schema, - vol.Schema( + probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Exclusive(CONF_ACTION, "service name"): vol.Any( + probatio.Exclusive(CONF_ACTION, "service name"): probatio.Any( service, dynamic_template ), - vol.Exclusive(CONF_SERVICE_TEMPLATE, "service name"): vol.Any( + probatio.Exclusive(CONF_SERVICE_TEMPLATE, "service name"): probatio.Any( service, dynamic_template ), - vol.Optional(CONF_SERVICE_DATA): vol.Any( - template, vol.All(dict, template_complex) + probatio.Optional(CONF_SERVICE_DATA): probatio.Any( + template, probatio.All(dict, template_complex) ), - vol.Optional(CONF_SERVICE_DATA_TEMPLATE): vol.Any( - template, vol.All(dict, template_complex) + probatio.Optional(CONF_SERVICE_DATA_TEMPLATE): probatio.Any( + template, probatio.All(dict, template_complex) ), - vol.Optional(CONF_ENTITY_ID): comp_entity_ids, - vol.Optional(CONF_TARGET): vol.Any( + probatio.Optional(CONF_ENTITY_ID): comp_entity_ids, + probatio.Optional(CONF_TARGET): probatio.Any( _TARGET_SERVICE_FIELDS_TEMPLATED, dynamic_template ), - vol.Optional(CONF_RESPONSE_VARIABLE): str, + probatio.Optional(CONF_RESPONSE_VARIABLE): str, # The frontend stores data here. Don't use in core. - vol.Remove("metadata"): dict, + probatio.Remove("metadata"): dict, } ), has_at_least_one_key(CONF_ACTION, CONF_SERVICE_TEMPLATE), ) -NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any( - vol.Coerce(float), - vol.All(str, entity_domain(["input_number", "number", "sensor", "zone"])), +NUMERIC_STATE_THRESHOLD_SCHEMA = probatio.Any( + probatio.Coerce(float), + probatio.All(str, entity_domain(["input_number", "number", "sensor", "zone"])), ) CONDITION_BASE_SCHEMA: VolDictType = { - vol.Optional(CONF_ALIAS): string, - vol.Remove(CONF_NOTE): str, # Is only used in frontend - vol.Optional(CONF_ENABLED): vol.Any(boolean, template), + probatio.Optional(CONF_ALIAS): string, + probatio.Remove(CONF_NOTE): str, # Is only used in frontend + probatio.Optional(CONF_ENABLED): probatio.Any(boolean, template), } -NUMERIC_STATE_CONDITION_SCHEMA = vol.All( - vol.Schema( +NUMERIC_STATE_CONDITION_SCHEMA = probatio.All( + probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "numeric_state", - vol.Required(CONF_ENTITY_ID): entity_ids_or_uuids, - vol.Optional(CONF_ATTRIBUTE): str, + probatio.Required(CONF_CONDITION): "numeric_state", + probatio.Required(CONF_ENTITY_ID): entity_ids_or_uuids, + probatio.Optional(CONF_ATTRIBUTE): str, CONF_BELOW: NUMERIC_STATE_THRESHOLD_SCHEMA, CONF_ABOVE: NUMERIC_STATE_THRESHOLD_SCHEMA, - vol.Optional(CONF_VALUE_TEMPLATE): template, + probatio.Optional(CONF_VALUE_TEMPLATE): template, } ), has_at_least_one_key(CONF_BELOW, CONF_ABOVE), @@ -1555,26 +1567,26 @@ INPUT_ENTITY_ID = re.compile( STATE_CONDITION_BASE_SCHEMA = { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "state", - vol.Required(CONF_ENTITY_ID): entity_ids_or_uuids, - vol.Optional(CONF_MATCH, default=ENTITY_MATCH_ALL): vol.All( - vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_ANY) + probatio.Required(CONF_CONDITION): "state", + probatio.Required(CONF_ENTITY_ID): entity_ids_or_uuids, + probatio.Optional(CONF_MATCH, default=ENTITY_MATCH_ALL): probatio.All( + probatio.Lower, probatio.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_ANY) ), - vol.Optional(CONF_ATTRIBUTE): str, - vol.Optional(CONF_FOR): positive_time_period_template, + probatio.Optional(CONF_ATTRIBUTE): str, + probatio.Optional(CONF_FOR): positive_time_period_template, } -STATE_CONDITION_STATE_SCHEMA = vol.Schema( +STATE_CONDITION_STATE_SCHEMA = probatio.Schema( { **STATE_CONDITION_BASE_SCHEMA, - vol.Required(CONF_STATE): vol.Any(str, [str]), + probatio.Required(CONF_STATE): probatio.Any(str, [str]), } ) -STATE_CONDITION_ATTRIBUTE_SCHEMA = vol.Schema( +STATE_CONDITION_ATTRIBUTE_SCHEMA = probatio.Schema( { **STATE_CONDITION_BASE_SCHEMA, - vol.Required(CONF_STATE): match_all, + probatio.Required(CONF_STATE): match_all, } ) @@ -1582,7 +1594,7 @@ STATE_CONDITION_ATTRIBUTE_SCHEMA = vol.Schema( def STATE_CONDITION_SCHEMA(value: Any) -> dict[str, Any]: """Validate a state condition.""" if not isinstance(value, dict): - raise vol.Invalid("Expected a dictionary") + raise probatio.Invalid("Expected a dictionary") if CONF_ATTRIBUTE in value: validated: dict[str, Any] = STATE_CONDITION_ATTRIBUTE_SCHEMA(value) @@ -1596,58 +1608,62 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict[str, Any]: # single current state. It therefore can't track an attribute, multiple # states, or a state resolved from another entity. if CONF_ATTRIBUTE in validated: - raise vol.Invalid("Cannot use 'for' with an attribute") + raise probatio.Invalid("Cannot use 'for' with an attribute") state = validated[CONF_STATE] # A single-element list is just that one state; unwrap it so the # input-entity check below also rejects `state: [input_select.x]`. if isinstance(state, list): if len(state) != 1: - raise vol.Invalid("Cannot use 'for' with a list of states") + raise probatio.Invalid("Cannot use 'for' with a list of states") state = state[0] if INPUT_ENTITY_ID.match(state): - raise vol.Invalid("Cannot use 'for' with a state referencing an entity") + raise probatio.Invalid( + "Cannot use 'for' with a state referencing an entity" + ) return validated -TEMPLATE_CONDITION_SCHEMA = vol.Schema( +TEMPLATE_CONDITION_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "template", - vol.Required(CONF_VALUE_TEMPLATE): template, + probatio.Required(CONF_CONDITION): "template", + probatio.Required(CONF_VALUE_TEMPLATE): template, } ) -TIME_CONDITION_SCHEMA = vol.All( - vol.Schema( +TIME_CONDITION_SCHEMA = probatio.All( + probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "time", - vol.Optional("before"): vol.Any( - time, vol.All(str, entity_domain(["input_datetime", "time", "sensor"])) + probatio.Required(CONF_CONDITION): "time", + probatio.Optional("before"): probatio.Any( + time, + probatio.All(str, entity_domain(["input_datetime", "time", "sensor"])), ), - vol.Optional("after"): vol.Any( - time, vol.All(str, entity_domain(["input_datetime", "time", "sensor"])) + probatio.Optional("after"): probatio.Any( + time, + probatio.All(str, entity_domain(["input_datetime", "time", "sensor"])), ), - vol.Optional("weekday"): weekdays, + probatio.Optional("weekday"): weekdays, } ), has_at_least_one_key("before", "after", "weekday"), ) -TRIGGER_CONDITION_SCHEMA = vol.Schema( +TRIGGER_CONDITION_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "trigger", - vol.Required(CONF_ID): vol.All(ensure_list, [string]), + probatio.Required(CONF_CONDITION): "trigger", + probatio.Required(CONF_ID): probatio.All(ensure_list, [string]), } ) -AND_CONDITION_SCHEMA = vol.Schema( +AND_CONDITION_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "and", - vol.Required(CONF_CONDITIONS): vol.All( + probatio.Required(CONF_CONDITION): "and", + probatio.Required(CONF_CONDITIONS): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1655,10 +1671,10 @@ AND_CONDITION_SCHEMA = vol.Schema( } ) -AND_CONDITION_SHORTHAND_SCHEMA = vol.Schema( +AND_CONDITION_SHORTHAND_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required("and"): vol.All( + probatio.Required("and"): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1666,11 +1682,11 @@ AND_CONDITION_SHORTHAND_SCHEMA = vol.Schema( } ) -OR_CONDITION_SCHEMA = vol.Schema( +OR_CONDITION_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "or", - vol.Required(CONF_CONDITIONS): vol.All( + probatio.Required(CONF_CONDITION): "or", + probatio.Required(CONF_CONDITIONS): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1678,10 +1694,10 @@ OR_CONDITION_SCHEMA = vol.Schema( } ) -OR_CONDITION_SHORTHAND_SCHEMA = vol.Schema( +OR_CONDITION_SHORTHAND_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required("or"): vol.All( + probatio.Required("or"): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1689,11 +1705,11 @@ OR_CONDITION_SHORTHAND_SCHEMA = vol.Schema( } ) -NOT_CONDITION_SCHEMA = vol.Schema( +NOT_CONDITION_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "not", - vol.Required(CONF_CONDITIONS): vol.All( + probatio.Required(CONF_CONDITION): "not", + probatio.Required(CONF_CONDITIONS): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1701,10 +1717,10 @@ NOT_CONDITION_SCHEMA = vol.Schema( } ) -NOT_CONDITION_SHORTHAND_SCHEMA = vol.Schema( +NOT_CONDITION_SHORTHAND_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required("not"): vol.All( + probatio.Required("not"): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1712,17 +1728,19 @@ NOT_CONDITION_SHORTHAND_SCHEMA = vol.Schema( } ) -DEVICE_CONDITION_BASE_SCHEMA = vol.Schema( +DEVICE_CONDITION_BASE_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): "device", - vol.Required(CONF_DEVICE_ID): str, - vol.Required(CONF_DOMAIN): str, - vol.Remove("metadata"): dict, + probatio.Required(CONF_CONDITION): "device", + probatio.Required(CONF_DEVICE_ID): str, + probatio.Required(CONF_DOMAIN): str, + probatio.Remove("metadata"): dict, } ) -DEVICE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) +DEVICE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend( + {}, extra=probatio.ALLOW_EXTRA +) def expand_condition_shorthand(value: Any | None) -> Any: @@ -1743,7 +1761,7 @@ def expand_condition_shorthand(value: Any | None) -> Any: CONF_CONDITIONS: value[key], **{k: value[k] for k in value if k != key}, } - except vol.MultipleInvalid: + except probatio.MultipleInvalid: pass if isinstance(value.get(CONF_CONDITION), list): @@ -1754,13 +1772,13 @@ def expand_condition_shorthand(value: Any | None) -> Any: CONF_CONDITIONS: value[CONF_CONDITION], **{k: value[k] for k in value if k != CONF_CONDITION}, } - except vol.MultipleInvalid: + except probatio.MultipleInvalid: pass return value -dynamic_template_condition = vol.All( +dynamic_template_condition = probatio.All( # Wrap a shorthand template condition in a template condition dynamic_template, lambda config: { @@ -1769,10 +1787,10 @@ dynamic_template_condition = vol.All( }, ) -CONDITION_SHORTHAND_SCHEMA = vol.Schema( +CONDITION_SHORTHAND_SCHEMA = probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): vol.All( + probatio.Required(CONF_CONDITION): probatio.All( ensure_list, # pylint: disable-next=unnecessary-lambda [lambda value: CONDITION_SCHEMA(value)], @@ -1796,19 +1814,21 @@ BUILT_IN_CONDITIONS: ValueSchemas = { # This is first round of validation, we don't want to mutate the config here already, # just ensure basics as condition type and alias are there. def _base_condition_validator(value: Any) -> Any: - vol.Schema( + probatio.Schema( { **CONDITION_BASE_SCHEMA, - vol.Required(CONF_CONDITION): vol.All(str, vol.NotIn(BUILT_IN_CONDITIONS)), + probatio.Required(CONF_CONDITION): probatio.All( + str, probatio.NotIn(BUILT_IN_CONDITIONS) + ), }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, )(value) return value -CONDITION_SCHEMA: vol.Schema = vol.Schema( - vol.Any( - vol.All( +CONDITION_SCHEMA: probatio.Schema = probatio.Schema( + probatio.Any( + probatio.All( expand_condition_shorthand, key_value_schemas( CONF_CONDITION, @@ -1822,12 +1842,12 @@ CONDITION_SCHEMA: vol.Schema = vol.Schema( ) ) -CONDITIONS_SCHEMA = vol.All(ensure_list, [CONDITION_SCHEMA]) +CONDITIONS_SCHEMA = probatio.All(ensure_list, [CONDITION_SCHEMA]) -dynamic_template_condition_action = vol.All( +dynamic_template_condition_action = probatio.All( # Wrap a shorthand template condition action in a template condition - vol.Schema( - {**CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): dynamic_template} + probatio.Schema( + {**CONDITION_BASE_SCHEMA, probatio.Required(CONF_CONDITION): dynamic_template} ), lambda config: { **config, @@ -1837,13 +1857,13 @@ dynamic_template_condition_action = vol.All( ) -CONDITION_ACTION_SCHEMA: vol.Schema = vol.Schema( - vol.All( +CONDITION_ACTION_SCHEMA: probatio.Schema = probatio.Schema( + probatio.All( expand_condition_shorthand, key_value_schemas( CONF_CONDITION, BUILT_IN_CONDITIONS, - vol.Any( + probatio.Any( dynamic_template_condition_action, _base_condition_validator, ), @@ -1868,31 +1888,33 @@ def _trigger_pre_validator(value: Any | None) -> Any: if CONF_TRIGGER in value: if CONF_PLATFORM in value: - raise vol.Invalid( + raise probatio.Invalid( "Cannot specify both 'platform' and 'trigger'." " Please use 'trigger' only." ) value = dict(value) value[CONF_PLATFORM] = value.pop(CONF_TRIGGER) elif CONF_PLATFORM not in value: - raise vol.Invalid("required key not provided", [CONF_TRIGGER]) + raise probatio.Invalid("required key not provided", [CONF_TRIGGER]) return value -TRIGGER_BASE_SCHEMA = vol.Schema( +TRIGGER_BASE_SCHEMA = probatio.Schema( { - vol.Optional(CONF_ALIAS): str, - vol.Required(CONF_PLATFORM): str, - vol.Optional(CONF_ID): str, - vol.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, - vol.Optional(CONF_ENABLED): vol.Any(boolean, template), - vol.Remove(CONF_NOTE): str, # Is only used in frontend + probatio.Optional(CONF_ALIAS): str, + probatio.Required(CONF_PLATFORM): str, + probatio.Optional(CONF_ID): str, + probatio.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, + probatio.Optional(CONF_ENABLED): probatio.Any(boolean, template), + probatio.Remove(CONF_NOTE): str, # Is only used in frontend } ) -_base_trigger_validator_schema = TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) +_base_trigger_validator_schema = TRIGGER_BASE_SCHEMA.extend( + {}, extra=probatio.ALLOW_EXTRA +) def _base_trigger_list_flatten(triggers: list[Any]) -> list[Any]: @@ -1915,108 +1937,110 @@ def _base_trigger_validator(value: Any) -> Any: return value -TRIGGER_SCHEMA = vol.All( +TRIGGER_SCHEMA = probatio.All( ensure_list, _base_trigger_list_flatten, - [vol.All(_trigger_pre_validator, _base_trigger_validator)], + [probatio.All(_trigger_pre_validator, _base_trigger_validator)], ) -_SCRIPT_DELAY_SCHEMA = vol.Schema( +_SCRIPT_DELAY_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_DELAY): positive_time_period_template, + probatio.Required(CONF_DELAY): positive_time_period_template, } ) -_SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema( +_SCRIPT_WAIT_TEMPLATE_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_WAIT_TEMPLATE): template, - vol.Optional(CONF_TIMEOUT): positive_time_period_template, - vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, + probatio.Required(CONF_WAIT_TEMPLATE): template, + probatio.Optional(CONF_TIMEOUT): positive_time_period_template, + probatio.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, } ) -DEVICE_ACTION_BASE_SCHEMA = vol.Schema( +DEVICE_ACTION_BASE_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_DEVICE_ID): string, - vol.Required(CONF_DOMAIN): str, - vol.Remove("metadata"): dict, + probatio.Required(CONF_DEVICE_ID): string, + probatio.Required(CONF_DOMAIN): str, + probatio.Remove("metadata"): dict, } ) -DEVICE_ACTION_SCHEMA = DEVICE_ACTION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) +DEVICE_ACTION_SCHEMA = DEVICE_ACTION_BASE_SCHEMA.extend({}, extra=probatio.ALLOW_EXTRA) -_SCRIPT_SCENE_SCHEMA = vol.Schema( - {**SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_SCENE): entity_domain("scene")} +_SCRIPT_SCENE_SCHEMA = probatio.Schema( + {**SCRIPT_ACTION_BASE_SCHEMA, probatio.Required(CONF_SCENE): entity_domain("scene")} ) -_SCRIPT_REPEAT_SCHEMA = vol.Schema( +_SCRIPT_REPEAT_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_REPEAT): vol.All( + probatio.Required(CONF_REPEAT): probatio.All( { - vol.Exclusive(CONF_COUNT, "repeat"): vol.Any(vol.Coerce(int), template), - vol.Exclusive(CONF_FOR_EACH, "repeat"): vol.Any( - dynamic_template, vol.All(list, template_complex) + probatio.Exclusive(CONF_COUNT, "repeat"): probatio.Any( + probatio.Coerce(int), template ), - vol.Exclusive(CONF_WHILE, "repeat"): CONDITIONS_SCHEMA, - vol.Exclusive(CONF_UNTIL, "repeat"): CONDITIONS_SCHEMA, - vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, + probatio.Exclusive(CONF_FOR_EACH, "repeat"): probatio.Any( + dynamic_template, probatio.All(list, template_complex) + ), + probatio.Exclusive(CONF_WHILE, "repeat"): CONDITIONS_SCHEMA, + probatio.Exclusive(CONF_UNTIL, "repeat"): CONDITIONS_SCHEMA, + probatio.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, }, has_at_least_one_key(CONF_COUNT, CONF_FOR_EACH, CONF_WHILE, CONF_UNTIL), ), } ) -_SCRIPT_CHOOSE_SCHEMA = vol.Schema( +_SCRIPT_CHOOSE_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_CHOOSE): vol.All( + probatio.Required(CONF_CHOOSE): probatio.All( ensure_list, [ { - vol.Optional(CONF_ALIAS): string, - vol.Remove(CONF_NOTE): str, # Is only used in frontend - vol.Required(CONF_CONDITIONS): CONDITIONS_SCHEMA, - vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, + probatio.Optional(CONF_ALIAS): string, + probatio.Remove(CONF_NOTE): str, # Is only used in frontend + probatio.Required(CONF_CONDITIONS): CONDITIONS_SCHEMA, + probatio.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, } ], ), - vol.Optional(CONF_DEFAULT): SCRIPT_SCHEMA, + probatio.Optional(CONF_DEFAULT): SCRIPT_SCHEMA, } ) -_SCRIPT_WAIT_FOR_TRIGGER_SCHEMA = vol.Schema( +_SCRIPT_WAIT_FOR_TRIGGER_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_WAIT_FOR_TRIGGER): TRIGGER_SCHEMA, - vol.Optional(CONF_TIMEOUT): positive_time_period_template, - vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, + probatio.Required(CONF_WAIT_FOR_TRIGGER): TRIGGER_SCHEMA, + probatio.Optional(CONF_TIMEOUT): positive_time_period_template, + probatio.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, } ) -_SCRIPT_IF_SCHEMA = vol.Schema( +_SCRIPT_IF_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_IF): CONDITIONS_SCHEMA, - vol.Required(CONF_THEN): SCRIPT_SCHEMA, - vol.Optional(CONF_ELSE): SCRIPT_SCHEMA, + probatio.Required(CONF_IF): CONDITIONS_SCHEMA, + probatio.Required(CONF_THEN): SCRIPT_SCHEMA, + probatio.Optional(CONF_ELSE): SCRIPT_SCHEMA, } ) -_SCRIPT_SET_SCHEMA = vol.Schema( +_SCRIPT_SET_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, + probatio.Required(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, } ) -_SCRIPT_SET_CONVERSATION_RESPONSE_SCHEMA = vol.Schema( +_SCRIPT_SET_CONVERSATION_RESPONSE_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required( + probatio.Required( CONF_SET_CONVERSATION_RESPONSE ): SCRIPT_CONVERSATION_RESPONSE_SCHEMA, } @@ -2026,32 +2050,32 @@ _SCRIPT_SET_CONVERSATION_RESPONSE_SCHEMA = vol.Schema( def _stop_action_check_error_response(config: dict) -> dict: """Validate that error stop actions don't have a response variable.""" if config.get(CONF_ERROR) and CONF_RESPONSE_VARIABLE in config: - raise vol.Invalid("not allowed to add a response to an error stop action") + raise probatio.Invalid("not allowed to add a response to an error stop action") return config -_SCRIPT_STOP_SCHEMA = vol.All( - vol.Schema( +_SCRIPT_STOP_SCHEMA = probatio.All( + probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_STOP): vol.Any(None, string), - vol.Optional(CONF_ERROR): boolean, - vol.Optional(CONF_RESPONSE_VARIABLE): str, + probatio.Required(CONF_STOP): probatio.Any(None, string), + probatio.Optional(CONF_ERROR): boolean, + probatio.Optional(CONF_RESPONSE_VARIABLE): str, } ), _stop_action_check_error_response, ) -_SCRIPT_SEQUENCE_SCHEMA = vol.Schema( +_SCRIPT_SEQUENCE_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, # The frontend stores data here. Don't use in core. - vol.Remove("metadata"): dict, - vol.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, + probatio.Remove("metadata"): dict, + probatio.Required(CONF_SEQUENCE): SCRIPT_SCHEMA, } ) -_parallel_sequence_action = vol.All( +_parallel_sequence_action = probatio.All( # Wrap a shorthand sequences in a parallel action SCRIPT_SCHEMA, lambda config: { @@ -2059,11 +2083,12 @@ _parallel_sequence_action = vol.All( }, ) -_SCRIPT_PARALLEL_SCHEMA = vol.Schema( +_SCRIPT_PARALLEL_SCHEMA = probatio.Schema( { **SCRIPT_ACTION_BASE_SCHEMA, - vol.Required(CONF_PARALLEL): vol.All( - ensure_list, [vol.Any(_SCRIPT_SEQUENCE_SCHEMA, _parallel_sequence_action)] + probatio.Required(CONF_PARALLEL): probatio.All( + ensure_list, + [probatio.Any(_SCRIPT_SEQUENCE_SCHEMA, _parallel_sequence_action)], ), } ) @@ -2147,17 +2172,17 @@ ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { } -currency = vol.In( +currency = probatio.In( currencies.ACTIVE_CURRENCIES, msg="invalid ISO 4217 formatted currency" ) -historic_currency = vol.In( +historic_currency = probatio.In( currencies.HISTORIC_CURRENCIES, msg="invalid ISO 4217 formatted historic currency" ) -country = vol.In(COUNTRIES, msg="invalid ISO 3166 formatted country") +country = probatio.In(COUNTRIES, msg="invalid ISO 3166 formatted country") -language = vol.In(LANGUAGES, msg="invalid RFC 5646 formatted language") +language = probatio.In(LANGUAGES, msg="invalid RFC 5646 formatted language") async def async_validate( diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 62b16a00175b..c4a9f7cd6b1c 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -4,8 +4,7 @@ from http import HTTPStatus from typing import Any, Generic, TypeVar from aiohttp import web -from probatio import to_field_list -import voluptuous as vol +import probatio from homeassistant import data_entry_flow from homeassistant.components.http import HomeAssistantView @@ -51,7 +50,7 @@ class _BaseFlowManagerView(HomeAssistantView, Generic[_FlowManagerT, _FlowResult if (schema := result["data_schema"]) is None: data["data_schema"] = [] else: - data["data_schema"] = to_field_list( + data["data_schema"] = probatio.to_field_list( schema, custom_serializer=cv.custom_serializer ) return data @@ -61,11 +60,11 @@ class FlowManagerIndexView(_BaseFlowManagerView[_FlowManagerT, _FlowResultT]): """View to create config flows.""" @RequestDataValidator( - vol.Schema( + probatio.Schema( { - vol.Required("handler"): str, + probatio.Required("handler"): str, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: @@ -113,7 +112,7 @@ class FlowManagerResourceView(_BaseFlowManagerView[_FlowManagerT, _FlowResultT]) return self.json(result) - @RequestDataValidator(vol.Schema(dict), allow_empty=True) + @RequestDataValidator(probatio.Schema(dict), allow_empty=True) async def post( self, request: web.Request, data: dict[str, Any], flow_id: str ) -> web.Response: diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 673862c85814..e2ca36c91aed 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -26,8 +26,8 @@ from typing import ( override, ) +import probatio from propcache.api import cached_property -import voluptuous as vol from homeassistant.const import ( DEVICE_DEFAULT_NAME, @@ -209,7 +209,7 @@ def get_unit_of_measurement(hass: HomeAssistant, entity_id: str) -> str | None: return entry.unit_of_measurement -ENTITY_CATEGORIES_SCHEMA: Final = vol.Coerce(EntityCategory) +ENTITY_CATEGORIES_SCHEMA: Final = probatio.Coerce(EntityCategory) class EntityInfo(TypedDict): diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 111fb3c4a75f..3192962dcbbe 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -19,7 +19,7 @@ import time from typing import TYPE_CHECKING, Any, Literal, NotRequired, TypedDict, override import attr -import voluptuous as vol +import probatio from homeassistant.const import ( EVENT_HOMEASSISTANT_START, @@ -2768,13 +2768,13 @@ async def async_migrate_entries( def async_validate_entity_id(registry: EntityRegistry, entity_id_or_uuid: str) -> str: """Validate and resolve an entity id or UUID to an entity id. - Raises vol.Invalid if the entity or UUID is invalid, or if the UUID is not + Raises probatio.Invalid if the entity or UUID is invalid, or if the UUID is not associated with an entity registry item. """ if valid_entity_id(entity_id_or_uuid): return entity_id_or_uuid if (entry := registry.entities.get_entry(entity_id_or_uuid)) is None: - raise vol.Invalid(f"Unknown entity registry entry {entity_id_or_uuid}") + raise probatio.Invalid(f"Unknown entity registry entry {entity_id_or_uuid}") return entry.entity_id @@ -2801,7 +2801,7 @@ def async_validate_entity_ids( """Validate and resolve a list of entity ids or UUIDs to a list of entity ids. Returns a list with UUID resolved to entity_ids. - Raises vol.Invalid if any item is invalid, or if any a UUID is not associated with + Raises probatio.Invalid if any item is invalid, or if any a UUID is not associated with an entity registry item. """ diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index eb09ce526d91..a0d343a78e65 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -6,7 +6,7 @@ from functools import lru_cache, partial import operator import re -import voluptuous as vol +import probatio from homeassistant.const import ( CONF_DOMAINS, @@ -77,26 +77,26 @@ def convert_filter(config: dict[str, list[str]]) -> EntityFilter: return EntityFilter(config) -BASE_FILTER_SCHEMA = vol.Schema( +BASE_FILTER_SCHEMA = probatio.Schema( { - vol.Optional(CONF_EXCLUDE_DOMAINS, default=[]): vol.All( + probatio.Optional(CONF_EXCLUDE_DOMAINS, default=[]): probatio.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_EXCLUDE_ENTITY_GLOBS, default=[]): vol.All( + probatio.Optional(CONF_EXCLUDE_ENTITY_GLOBS, default=[]): probatio.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_EXCLUDE_ENTITIES, default=[]): cv.entity_ids, - vol.Optional(CONF_INCLUDE_DOMAINS, default=[]): vol.All( + probatio.Optional(CONF_EXCLUDE_ENTITIES, default=[]): cv.entity_ids, + probatio.Optional(CONF_INCLUDE_DOMAINS, default=[]): probatio.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_INCLUDE_ENTITY_GLOBS, default=[]): vol.All( + probatio.Optional(CONF_INCLUDE_ENTITY_GLOBS, default=[]): probatio.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_INCLUDE_ENTITIES, default=[]): cv.entity_ids, + probatio.Optional(CONF_INCLUDE_ENTITIES, default=[]): cv.entity_ids, } ) -FILTER_SCHEMA = vol.All(BASE_FILTER_SCHEMA, convert_filter) +FILTER_SCHEMA = probatio.All(BASE_FILTER_SCHEMA, convert_filter) def convert_include_exclude_filter( @@ -117,28 +117,30 @@ def convert_include_exclude_filter( ) -INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER = vol.Schema( +INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER = probatio.Schema( { - vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_ENTITY_GLOBS, default=[]): vol.All( + probatio.Optional(CONF_DOMAINS, default=[]): probatio.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, + probatio.Optional(CONF_ENTITY_GLOBS, default=[]): probatio.All( + cv.ensure_list, [cv.string] + ), + probatio.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, } ) -INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA = vol.Schema( +INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA = probatio.Schema( { - vol.Optional( + probatio.Optional( CONF_INCLUDE, default=INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER({}) ): INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER, - vol.Optional( + probatio.Optional( CONF_EXCLUDE, default=INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER({}) ): INCLUDE_EXCLUDE_FILTER_SCHEMA_INNER, } ) -INCLUDE_EXCLUDE_FILTER_SCHEMA = vol.All( +INCLUDE_EXCLUDE_FILTER_SCHEMA = probatio.All( INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA, convert_include_exclude_filter ) diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index a380cec1ae31..b5592ede4898 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -16,7 +16,7 @@ from aiohttp.web_exceptions import ( HTTPUnauthorized, ) from aiohttp.web_urldispatcher import AbstractResource, AbstractRoute -import voluptuous as vol +import probatio from homeassistant import exceptions from homeassistant.const import CONTENT_TYPE_JSON @@ -92,7 +92,7 @@ def request_handler_factory( result = await handler(request, **request.match_info) else: result = handler(request, **request.match_info) - except vol.Invalid as err: + except probatio.Invalid as err: raise HTTPBadRequest from err except exceptions.ServiceNotFound as err: raise HTTPInternalServerError from err diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 31d2bf26a69c..2f1120174790 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -10,8 +10,8 @@ from itertools import groupby import logging from typing import Any, override +import probatio from propcache.api import cached_property -import voluptuous as vol from homeassistant.components.homeassistant.exposed_entities import async_should_expose from homeassistant.const import ATTR_ENTITY_ID, EntityStateAttribute @@ -56,7 +56,7 @@ INTENT_RESPOND = "HassRespond" INTENT_BROADCAST = "HassBroadcast" INTENT_GET_TEMPERATURE = "HassClimateGetTemperature" -SLOT_SCHEMA = vol.Schema({}, extra=vol.ALLOW_EXTRA) +SLOT_SCHEMA = probatio.Schema({}, extra=probatio.ALLOW_EXTRA) DATA_KEY: HassKey[dict[str, IntentHandler]] = HassKey("intent") @@ -138,7 +138,7 @@ async def async_handle( try: _LOGGER.info("Triggering intent handler %s", handler) result = await handler.async_handle(intent) - except vol.Invalid as err: + except probatio.Invalid as err: _LOGGER.warning("Received invalid slot info for %s: %s", intent_type, err) raise InvalidSlotInfo(f"Received invalid slot info for {intent_type}") from err except IntentError: @@ -843,15 +843,15 @@ class IntentHandler: return self._slot_schema(slots) # type: ignore[no-any-return] @cached_property - def _slot_schema(self) -> vol.Schema: + def _slot_schema(self) -> probatio.Schema: """Create validation schema for slots.""" assert self.slot_schema is not None - return vol.Schema( + return probatio.Schema( { key: SLOT_SCHEMA.extend({"value": validator}) for key, validator in self.slot_schema.items() }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) async def async_handle(self, intent_obj: Intent) -> IntentResponse: @@ -868,7 +868,7 @@ def non_empty_string(value: Any) -> str: """Coerce value to string and fail if string is empty or whitespace.""" value_str = cv.string(value) if not value_str.strip(): - raise vol.Invalid("string value is empty") + raise probatio.Invalid("string value is empty") return value_str @@ -883,7 +883,7 @@ class IntentSlotInfo: description: str | None = None """Human readable description of the slot.""" - value_schema: VolSchemaType | Callable[[Any], Any] = vol.Any + value_schema: VolSchemaType | Callable[[Any], Any] = probatio.Any """Validator for the slot.""" @@ -947,16 +947,20 @@ class DynamicServiceIntentHandler(IntentHandler): def slot_schema(self) -> dict: """Return a slot schema.""" domain_validator = ( - vol.In(list(self.required_domains)) if self.required_domains else cv.string + probatio.In(list(self.required_domains)) + if self.required_domains + else cv.string ) slot_schema = { - vol.Any("name", "area", "floor"): non_empty_string, - vol.Optional("domain"): vol.All(cv.ensure_list, [domain_validator]), + probatio.Any("name", "area", "floor"): non_empty_string, + probatio.Optional("domain"): probatio.All( + cv.ensure_list, [domain_validator] + ), } if self.device_classes: - # The typical way to match enums is with vol.Coerce, but we build a + # The typical way to match enums is with probatio.Coerce, but we build a # flat list to make the API simpler to describe programmatically - flattened_device_classes = vol.In( + flattened_device_classes = probatio.In( [ device_class.value for device_class_enum in self.device_classes @@ -965,7 +969,7 @@ class DynamicServiceIntentHandler(IntentHandler): ) slot_schema.update( { - vol.Optional("device_class"): vol.All( + probatio.Optional("device_class"): probatio.All( cv.ensure_list, [flattened_device_classes], ) @@ -974,15 +978,15 @@ class DynamicServiceIntentHandler(IntentHandler): slot_schema.update( { - vol.Optional("preferred_area_id"): cv.string, - vol.Optional("preferred_floor_id"): cv.string, + probatio.Optional("preferred_area_id"): cv.string, + probatio.Optional("preferred_floor_id"): cv.string, } ) if self.required_slots: slot_schema.update( { - vol.Required( + probatio.Required( key, description=slot_info.description ): slot_info.value_schema for key, slot_info in self.required_slots.items() @@ -992,7 +996,7 @@ class DynamicServiceIntentHandler(IntentHandler): if self.optional_slots: slot_schema.update( { - vol.Optional( + probatio.Optional( key, description=slot_info.description ): slot_info.value_schema for key, slot_info in self.optional_slots.items() diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index 1fe67f966a7c..07b69a8f57a6 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -5,9 +5,8 @@ from collections.abc import Callable from dataclasses import dataclass, field as dc_field from typing import Any, override -from probatio import UNSUPPORTED, to_openapi +import probatio import slugify as unicode_slug -import voluptuous as vol from homeassistant.const import ( ATTR_DOMAIN, @@ -34,7 +33,7 @@ from .deprecation import deprecated_function from .singleton import singleton ACTION_PARAMETERS_CACHE: HassKey[ - dict[str, dict[str, tuple[str | None, vol.Schema]]] + dict[str, dict[str, tuple[str | None, probatio.Schema]]] ] = HassKey("llm_action_parameters_cache") APIS_CACHE: HassKey[dict[str, API]] = HassKey("llm_apis") @@ -160,7 +159,7 @@ class Tool: name: str description: str | None = None - parameters: vol.Schema = vol.Schema({}) + parameters: probatio.Schema = probatio.Schema({}) @abstractmethod async def async_call( @@ -247,7 +246,7 @@ class IntentTool(Tool): extra_slots.add(field) del slot_schema[field] - self.parameters = vol.Schema(slot_schema) + self.parameters = probatio.Schema(slot_schema) if extra_slots: self.extra_slots = extra_slots @@ -416,7 +415,7 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 return {"type": "boolean"} if not isinstance(schema, selector.Selector): - return UNSUPPORTED + return probatio.UNSUPPORTED if isinstance(schema, selector.BackupLocationSelector): return {"type": "string", "pattern": "^(?:\\/backup|\\w+)$"} @@ -434,10 +433,10 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 } if isinstance(schema, selector.ConditionSelector): - return to_openapi(cv.CONDITIONS_SCHEMA) + return probatio.to_openapi(cv.CONDITIONS_SCHEMA) if isinstance(schema, selector.ConstantSelector): - return to_openapi(vol.Schema(schema.config["value"])) + return probatio.to_openapi(probatio.Schema(schema.config["value"])) result: dict[str, Any] if isinstance(schema, selector.ColorTempSelector): @@ -464,7 +463,7 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 return {"type": "string", "format": "date-time"} if isinstance(schema, selector.DurationSelector): - return to_openapi(cv.time_period_dict) + return probatio.to_openapi(cv.time_period_dict) if isinstance(schema, selector.EntitySelector): if schema.config.get("multiple"): @@ -478,10 +477,10 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 return {"type": "string", "format": "RFC 5646"} if isinstance(schema, selector.LocationSelector): - return to_openapi(schema.DATA_SCHEMA) + return probatio.to_openapi(schema.DATA_SCHEMA) if isinstance(schema, selector.MediaSelector): - item_schema = to_openapi(schema.DATA_SCHEMA) + item_schema = probatio.to_openapi(schema.DATA_SCHEMA) # Media selector allows multiple when configured if schema.config.get("multiple"): return { @@ -504,7 +503,7 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 properties = {} required = [] for field, field_schema in fields.items(): - properties[field] = to_openapi( + properties[field] = probatio.to_openapi( selector.selector(field_schema["selector"]), custom_serializer=selector_serializer, ) @@ -536,7 +535,7 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 return {"type": "string", "enum": options} if isinstance(schema, selector.TargetSelector): - return to_openapi(cv.TARGET_FIELDS) + return probatio.to_openapi(cv.TARGET_FIELDS) if isinstance(schema, selector.TemplateSelector): return {"type": "string", "format": "jinja2"} @@ -555,10 +554,10 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901 def _get_cached_action_parameters( hass: HomeAssistant, domain: str, action: str -) -> tuple[str | None, vol.Schema]: +) -> tuple[str | None, probatio.Schema]: """Get action description and schema.""" description = None - parameters = vol.Schema({}) + parameters = probatio.Schema({}) parameters_cache = hass.data.get(ACTION_PARAMETERS_CACHE) @@ -591,24 +590,24 @@ def _get_cached_action_parameters( hass, domain, action ): description = action_desc.get("description") - schema: dict[vol.Marker, Any] = {} + schema: dict[probatio.Marker, Any] = {} fields = action_desc.get("fields", {}) for field, config in fields.items(): field_description = config.get("description") if not field_description: field_description = config.get("name") - key: vol.Marker + key: probatio.Marker if config.get("required"): - key = vol.Required(field, description=field_description) + key = probatio.Required(field, description=field_description) else: - key = vol.Optional(field, description=field_description) + key = probatio.Optional(field, description=field_description) if "selector" in config: schema[key] = selector.selector(config["selector"]) else: schema[key] = cv.string - parameters = vol.Schema(schema) + parameters = probatio.Schema(schema) parameters_cache.setdefault(domain, {})[action] = (description, parameters) diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index 0d424b4d8cea..c4de641e873e 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -7,7 +7,7 @@ from dataclasses import dataclass import types from typing import Any, cast, override -import voluptuous as vol +import probatio from homeassistant.config_entries import ( ConfigEntry, @@ -37,11 +37,13 @@ class SchemaFlowFormStep(SchemaFlowStep): """Define a config or options flow form step.""" schema: ( - vol.Schema - | Callable[[SchemaCommonFlowHandler], Coroutine[Any, Any, vol.Schema | None]] + probatio.Schema + | Callable[ + [SchemaCommonFlowHandler], Coroutine[Any, Any, probatio.Schema | None] + ] | None ) = None - """Optional voluptuous schema, or function which returns a schema or None, for + """Optional schema, or function which returns a schema or None, for requesting and validating user input. - If a function is specified, the function will be passed the current @@ -168,10 +170,12 @@ class SchemaCommonFlowHandler: return form_step.options return await form_step.options(self) - async def _get_schema(self, form_step: SchemaFlowFormStep) -> vol.Schema | None: + async def _get_schema( + self, form_step: SchemaFlowFormStep + ) -> probatio.Schema | None: if form_step.schema is None: return None - if isinstance(form_step.schema, vol.Schema): + if isinstance(form_step.schema, probatio.Schema): return form_step.schema return await form_step.schema(self) @@ -203,13 +207,13 @@ class SchemaCommonFlowHandler: self, values: dict[str, Any], user_input: dict[str, Any], - data_schema: vol.Schema | None, + data_schema: probatio.Schema | None, ) -> None: values.update(user_input) if data_schema and data_schema.schema: for key in data_schema.schema: if ( - isinstance(key, vol.Optional) + isinstance(key, probatio.Optional) and key not in user_input and not ( # don't remove read_only keys diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 1da929f2291f..a70d8d594305 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -12,8 +12,8 @@ import logging from typing import Any, Literal, TypedDict, cast, overload, override import async_interrupt +import probatio from propcache.api import cached_property -import voluptuous as vol from homeassistant import exceptions from homeassistant.components import scene @@ -285,21 +285,23 @@ class trace_action: def make_script_schema( - schema: Mapping[Any, Any], default_script_mode: str, extra: int = vol.PREVENT_EXTRA -) -> vol.Schema: + schema: Mapping[Any, Any], + default_script_mode: str, + extra: int = probatio.PREVENT_EXTRA, +) -> probatio.Schema: """Make a schema for a component that uses the script helper.""" - return vol.Schema( + return probatio.Schema( { **schema, - vol.Optional(CONF_MODE, default=default_script_mode): vol.In( + probatio.Optional(CONF_MODE, default=default_script_mode): probatio.In( SCRIPT_MODE_CHOICES ), - vol.Optional(CONF_MAX, default=DEFAULT_MAX): vol.All( - vol.Coerce(int), vol.Range(min=2) - ), - vol.Optional(CONF_MAX_EXCEEDED, default=DEFAULT_MAX_EXCEEDED): vol.All( - vol.Upper, vol.In(_MAX_EXCEEDED_CHOICES) + probatio.Optional(CONF_MAX, default=DEFAULT_MAX): probatio.All( + probatio.Coerce(int), probatio.Range(min=2) ), + probatio.Optional( + CONF_MAX_EXCEEDED, default=DEFAULT_MAX_EXCEEDED + ): probatio.All(probatio.Upper, probatio.In(_MAX_EXCEEDED_CHOICES)), }, extra=extra, ) @@ -620,7 +622,7 @@ class _ScriptRun: if isinstance( exception, ( - vol.Invalid, + probatio.Invalid, exceptions.TemplateError, exceptions.ServiceNotFound, exceptions.InvalidEntityFormatError, @@ -643,7 +645,7 @@ class _ScriptRun: error = str(exception) level = logging.ERROR - if isinstance(exception, vol.Invalid): + if isinstance(exception, probatio.Invalid): error_desc = "Invalid data" elif isinstance(exception, exceptions.TemplateError): @@ -1063,12 +1065,12 @@ class _ScriptRun: params[CONF_DOMAIN], params[CONF_SERVICE] ) if supports_response == SupportsResponse.ONLY and not return_response: - raise vol.Invalid( + raise probatio.Invalid( f"Script requires '{CONF_RESPONSE_VARIABLE}' for response data " f"for service call {params[CONF_DOMAIN]}.{params[CONF_SERVICE]}" ) if supports_response == SupportsResponse.NONE and return_response: - raise vol.Invalid( + raise probatio.Invalid( f"Script does not support '{CONF_RESPONSE_VARIABLE}' for service " f"'{params[CONF_DOMAIN]}.{params[CONF_SERVICE]}'" " which does not support response data." @@ -1186,7 +1188,7 @@ class _ScriptRun: return cv.positive_time_period( # type: ignore[no-any-return] template.render_complex(self._action[key], self._variables) ) - except (exceptions.TemplateError, vol.Invalid) as ex: + except (exceptions.TemplateError, probatio.Invalid) as ex: self._log( "Error rendering %s %s template: %s", self._script.name, diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 88b690cb5bf1..f9af2eb1d718 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -8,7 +8,7 @@ import importlib from typing import TYPE_CHECKING, Any, Literal, Required, TypedDict, cast, override from uuid import UUID -import voluptuous as vol +import probatio from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT, Platform from homeassistant.core import split_entity_id, valid_entity_id @@ -27,15 +27,17 @@ if TYPE_CHECKING: def _get_selector_type_and_class(config: Any) -> tuple[str, type[Selector]]: """Get selector type and class.""" if not isinstance(config, dict): - raise vol.Invalid("Expected a dictionary") + raise probatio.Invalid("Expected a dictionary") if len(config) != 1: - raise vol.Invalid(f"Only one type can be specified. Found {', '.join(config)}") + raise probatio.Invalid( + f"Only one type can be specified. Found {', '.join(config)}" + ) selector_type: str = list(config)[0] if (selector_class := SELECTORS.get(selector_type)) is None: - raise vol.Invalid(f"Unknown selector type {selector_type} found") + raise probatio.Invalid(f"Unknown selector type {selector_type} found") return selector_type, selector_class @@ -80,7 +82,7 @@ class Selector[_T: Mapping[str, Any]]: return self.selector_type == other.selector_type and self.config == other.config def serialize(self) -> dict[str, dict[str, _T]]: - """Serialize Selector for voluptuous_serialize.""" + """Serialize Selector for to_field_list.""" return {"selector": {self.selector_type: self.config}} @@ -103,7 +105,7 @@ def _validate_supported_feature(supported_feature: str) -> int: try: domain, enum, feature = supported_feature.split(".", 2) except ValueError as exc: - raise vol.Invalid( + raise probatio.Invalid( f"Invalid supported feature '{supported_feature}', expected " ".." ) from exc @@ -111,7 +113,9 @@ def _validate_supported_feature(supported_feature: str) -> int: try: return _entity_feature_flag(domain, enum, feature) except (ModuleNotFoundError, AttributeError) as exc: - raise vol.Invalid(f"Unknown supported feature '{supported_feature}'") from exc + raise probatio.Invalid( + f"Unknown supported feature '{supported_feature}'" + ) from exc def _validate_supported_features(supported_features: list[str]) -> int: @@ -128,11 +132,11 @@ def _validate_supported_features(supported_features: list[str]) -> int: def _validate_selector_reorder_config(config: Any) -> Any: """Validate selectors with reorder option.""" if config.get("reorder") and not config.get("multiple"): - raise vol.Invalid("reorder can only be used when multiple is true") + raise probatio.Invalid("reorder can only be used when multiple is true") return config -def make_selector_config_schema(schema_dict: dict | None = None) -> vol.Schema: +def make_selector_config_schema(schema_dict: dict | None = None) -> probatio.Schema: """Make selector config schema.""" if schema_dict is None: schema_dict = {} @@ -142,11 +146,11 @@ def make_selector_config_schema(schema_dict: dict | None = None) -> vol.Schema: return {} return value - return vol.Schema( - vol.All( + return probatio.Schema( + probatio.All( none_to_empty_dict, { - vol.Optional("read_only"): bool, + probatio.Optional("read_only"): bool, **schema_dict, }, ) @@ -159,20 +163,22 @@ class BaseSelectorConfig(TypedDict, total=False): read_only: bool -ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( +ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA = probatio.Schema( { # Integration that provided the entity - vol.Optional("integration"): str, + probatio.Optional("integration"): str, # Domain the entity belongs to - vol.Optional("domain"): vol.All(cv.ensure_list, [str]), + probatio.Optional("domain"): probatio.All(cv.ensure_list, [str]), # Device class of the entity - vol.Optional("device_class"): vol.All(cv.ensure_list, [str]), + probatio.Optional("device_class"): probatio.All(cv.ensure_list, [str]), # Features supported by the entity - vol.Optional("supported_features"): [ - vol.All(cv.ensure_list, [str], _validate_supported_features) + probatio.Optional("supported_features"): [ + probatio.All(cv.ensure_list, [str], _validate_supported_features) ], # Unit of measurement of the entity - vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.All(cv.ensure_list, [str]), + probatio.Optional(CONF_UNIT_OF_MEASUREMENT): probatio.All( + cv.ensure_list, [str] + ), } ) @@ -194,11 +200,11 @@ class _LegacyEntityFilterSelectorConfig(TypedDict, total=False): # https://github.com/home-assistant/frontend/pull/15302 _LEGACY_ENTITY_SELECTOR_CONFIG_SCHEMA_DICT = { # Integration that provided the entity - vol.Optional("integration"): str, + probatio.Optional("integration"): str, # Domain the entity belongs to - vol.Optional("domain"): vol.All(cv.ensure_list, [str]), + probatio.Optional("domain"): probatio.All(cv.ensure_list, [str]), # Device class of the entity - vol.Optional("device_class"): vol.All(cv.ensure_list, [str]), + probatio.Optional("device_class"): probatio.All(cv.ensure_list, [str]), } @@ -212,16 +218,16 @@ class EntityFilterSelectorConfig(TypedDict, total=False): unit_of_measurement: str | list[str] -DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( +DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = probatio.Schema( { # Integration linked to it with a config entry - vol.Optional("integration"): str, + probatio.Optional("integration"): str, # Manufacturer of device - vol.Optional("manufacturer"): str, + probatio.Optional("manufacturer"): str, # Model of device - vol.Optional("model"): str, + probatio.Optional("model"): str, # Model ID of device - vol.Optional("model_id"): str, + probatio.Optional("model_id"): str, } ) @@ -232,11 +238,11 @@ DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( # https://github.com/home-assistant/frontend/pull/15302 _LEGACY_DEVICE_SELECTOR_CONFIG_SCHEMA_DICT = { # Integration linked to it with a config entry - vol.Optional("integration"): str, + probatio.Optional("integration"): str, # Manufacturer of device - vol.Optional("manufacturer"): str, + probatio.Optional("manufacturer"): str, # Model of device - vol.Optional("model"): str, + probatio.Optional("model"): str, } @@ -253,7 +259,7 @@ ENTITY_WITH_DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = ( ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA.extend( { # Filter on properties of the device the entity belongs to - vol.Optional("device"): DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA, + probatio.Optional("device"): DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA, } ) ) @@ -305,8 +311,8 @@ class AppSelector(Selector[AppSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("name"): str, - vol.Optional("slug"): str, + probatio.Optional("name"): str, + probatio.Optional("slug"): str, } ) @@ -316,7 +322,7 @@ class AppSelector(Selector[AppSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - app: str = vol.Schema(str)(data) + app: str = probatio.Schema(str)(data) return app @@ -342,7 +348,7 @@ class AddonSelector(Selector[AddonSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - addon: str = vol.Schema(str)(data) + addon: str = probatio.Schema(str)(data) return addon @@ -361,19 +367,19 @@ class AreaSelector(Selector[AreaSelectorConfig]): selector_type = "area" - CONFIG_SCHEMA = vol.All( + CONFIG_SCHEMA = probatio.All( make_selector_config_schema( { - vol.Optional("entity"): vol.All( + probatio.Optional("entity"): probatio.All( cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("device"): vol.All( + probatio.Optional("device"): probatio.All( cv.ensure_list, [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("multiple", default=False): cv.boolean, - vol.Optional("reorder", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, + probatio.Optional("reorder", default=False): cv.boolean, } ), _validate_selector_reorder_config, @@ -386,11 +392,11 @@ class AreaSelector(Selector[AreaSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - area_id: str = vol.Schema(str)(data) + area_id: str = probatio.Schema(str)(data) return area_id if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class AssistPipelineSelectorConfig(BaseSelectorConfig, total=False): @@ -411,7 +417,7 @@ class AssistPipelineSelector(Selector[AssistPipelineSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - pipeline: str = vol.Schema(str)(data) + pipeline: str = probatio.Schema(str)(data) return pipeline @@ -430,10 +436,10 @@ class AttributeSelector(Selector[AttributeSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("entity_id"): cv.entity_id, + probatio.Required("entity_id"): cv.entity_id, # hide_attributes is used to hide attributes in the frontend. # A hidden attribute can still be provided manually. - vol.Optional("hide_attributes"): [str], + probatio.Optional("hide_attributes"): [str], } ) @@ -447,7 +453,7 @@ class AttributeSelector(Selector[AttributeSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - attribute: str = vol.Schema(str)(data) + attribute: str = probatio.Schema(str)(data) return attribute @@ -495,10 +501,10 @@ class AutomationBehaviorSelector(Selector[AutomationBehaviorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("mode"): vol.All( - vol.Coerce(AutomationBehaviorSelectorMode), lambda val: val.value + probatio.Required("mode"): probatio.All( + probatio.Coerce(AutomationBehaviorSelectorMode), lambda val: val.value ), - vol.Optional("translation_key"): cv.string, + probatio.Optional("translation_key"): cv.string, }, ) @@ -509,9 +515,9 @@ class AutomationBehaviorSelector(Selector[AutomationBehaviorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" if not isinstance(data, str): - raise vol.Invalid("Value should be a string") + raise probatio.Invalid("Value should be a string") mode = AutomationBehaviorSelectorMode(self.config["mode"]) - return vol.In(_AUTOMATION_BEHAVIOR_MODES[mode])(data) + return probatio.In(_AUTOMATION_BEHAVIOR_MODES[mode])(data) class BackupLocationSelectorConfig(BaseSelectorConfig, total=False): @@ -532,7 +538,7 @@ class BackupLocationSelector(Selector[BackupLocationSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - name: str = vol.Match(r"^(?:\/backup|\w+)$")(data) + name: str = probatio.Match(r"^(?:\/backup|\w+)$")(data) return name @@ -554,7 +560,7 @@ class BooleanSelector(Selector[BooleanSelectorConfig]): def __call__(self, data: Any) -> bool: """Validate the passed selection.""" - value: bool = vol.Coerce(bool)(data) + value: bool = probatio.Coerce(bool)(data) return value @@ -564,7 +570,7 @@ def reject_nested_choose_selector(config: dict[str, Any]) -> dict[str, Any]: if isinstance(choice["selector"], dict): selector_type, _ = _get_selector_type_and_class(choice["selector"]) if selector_type == "choose": - raise vol.Invalid("Nested choose selectors are not allowed") + raise probatio.Invalid("Nested choose selectors are not allowed") return config @@ -587,15 +593,17 @@ class ChooseSelector(Selector[ChooseSelectorConfig]): selector_type = "choose" - CONFIG_SCHEMA = vol.All( + CONFIG_SCHEMA = probatio.All( make_selector_config_schema( { - vol.Required("choices"): { + probatio.Required("choices"): { str: { - vol.Required("selector"): vol.Any(Selector, validate_selector), + probatio.Required("selector"): probatio.Any( + Selector, validate_selector + ), } }, - vol.Optional("translation_key"): cv.string, + probatio.Optional("translation_key"): cv.string, }, ), reject_nested_choose_selector, @@ -607,7 +615,7 @@ class ChooseSelector(Selector[ChooseSelectorConfig]): @override def serialize(self) -> dict[str, dict[str, ChooseSelectorConfig]]: - """Serialize ChooseSelectorConfig for voluptuous_serialize.""" + """Serialize ChooseSelectorConfig for to_field_list.""" _config = deepcopy(self.config) if "choices" in _config: for choice in _config["choices"].values(): @@ -621,21 +629,21 @@ class ChooseSelector(Selector[ChooseSelectorConfig]): for choice in self.config["choices"].values(): try: validated = selector(choice["selector"])(data) # type: ignore[operator] - except vol.Invalid, vol.MultipleInvalid: + except probatio.Invalid, probatio.MultipleInvalid: continue else: return validated - raise vol.Invalid("Value does not match any choice selector") + raise probatio.Invalid("Value does not match any choice selector") if "active_choice" not in data: - raise vol.Invalid("Missing active_choice key") + raise probatio.Invalid("Missing active_choice key") if data["active_choice"] not in data: - raise vol.Invalid("Missing value for active choice") + raise probatio.Invalid("Missing value for active choice") choices = self.config.get("choices", {}) if data["active_choice"] not in choices: - raise vol.Invalid("Invalid active_choice key") + raise probatio.Invalid("Invalid active_choice key") return selector(choices[data["active_choice"]]["selector"])( # type: ignore[operator] data[data["active_choice"]] ) @@ -659,7 +667,9 @@ class ColorRGBSelector(Selector[ColorRGBSelectorConfig]): def __call__(self, data: Any) -> list[int]: """Validate the passed selection.""" - value: list[int] = vol.All(list, vol.ExactSequence((cv.byte,) * 3))(data) + value: list[int] = probatio.All(list, probatio.ExactSequence((cv.byte,) * 3))( + data + ) return value @@ -688,13 +698,15 @@ class ColorTempSelector(Selector[ColorTempSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("unit", default=ColorTempSelectorUnit.MIRED): vol.All( - vol.Coerce(ColorTempSelectorUnit), lambda val: val.value + probatio.Optional( + "unit", default=ColorTempSelectorUnit.MIRED + ): probatio.All( + probatio.Coerce(ColorTempSelectorUnit), lambda val: val.value ), - vol.Optional("min"): vol.Coerce(int), - vol.Optional("max"): vol.Coerce(int), - vol.Optional("max_mireds"): vol.Coerce(int), - vol.Optional("min_mireds"): vol.Coerce(int), + probatio.Optional("min"): probatio.Coerce(int), + probatio.Optional("max"): probatio.Coerce(int), + probatio.Optional("max_mireds"): probatio.Coerce(int), + probatio.Optional("min_mireds"): probatio.Coerce(int), } ) @@ -713,9 +725,9 @@ class ColorTempSelector(Selector[ColorTempSelectorConfig]): if range_max is None: range_max = self.config.get("max_mireds") - value: int = vol.All( - vol.Coerce(float), - vol.Range( + value: int = probatio.All( + probatio.Coerce(float), + probatio.Range( min=range_min, max=range_max, ), @@ -741,7 +753,7 @@ class ConditionSelector(Selector[ConditionSelectorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - return vol.Schema(cv.CONDITIONS_SCHEMA)(data) + return probatio.Schema(cv.CONDITIONS_SCHEMA)(data) class ConfigEntrySelectorConfig(BaseSelectorConfig, total=False): @@ -758,7 +770,7 @@ class ConfigEntrySelector(Selector[ConfigEntrySelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("integration"): str, + probatio.Optional("integration"): str, } ) @@ -768,7 +780,7 @@ class ConfigEntrySelector(Selector[ConfigEntrySelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - config: str = vol.Schema(str)(data) + config: str = probatio.Schema(str)(data) return config @@ -788,9 +800,9 @@ class ConstantSelector(Selector[ConstantSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("label"): str, - vol.Optional("translation_key"): cv.string, - vol.Required("value"): vol.Any(str, int, bool), + probatio.Optional("label"): str, + probatio.Optional("translation_key"): cv.string, + probatio.Required("value"): probatio.Any(str, int, bool), } ) @@ -800,7 +812,7 @@ class ConstantSelector(Selector[ConstantSelectorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - vol.Schema(self.config["value"])(data) + probatio.Schema(self.config["value"])(data) return self.config["value"] @@ -818,7 +830,7 @@ class ConversationAgentSelector(Selector[ConversationAgentSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("language"): str, + probatio.Optional("language"): str, } ) @@ -828,7 +840,7 @@ class ConversationAgentSelector(Selector[ConversationAgentSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - agent: str = vol.Schema(str)(data) + agent: str = probatio.Schema(str)(data) return agent @@ -847,8 +859,8 @@ class CountrySelector(Selector[CountrySelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("countries"): [str], - vol.Optional("no_sort", default=False): cv.boolean, + probatio.Optional("countries"): [str], + probatio.Optional("no_sort", default=False): cv.boolean, } ) @@ -858,11 +870,11 @@ class CountrySelector(Selector[CountrySelectorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - country: str = vol.Schema(str)(data) + country: str = probatio.Schema(str)(data) if "countries" in self.config and ( country not in self.config["countries"] or country not in COUNTRIES ): - raise vol.Invalid(f"Value {country} is not a valid option") + raise probatio.Invalid(f"Value {country} is not a valid option") return country @@ -953,10 +965,10 @@ class DeviceClassSelector(Selector[DeviceClassSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("domain"): vol.All( - vol.In(SUPPORTED_PLATFORMS), lambda val: Platform(val).value + probatio.Required("domain"): probatio.All( + probatio.In(SUPPORTED_PLATFORMS), lambda val: Platform(val).value ), - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, } ) @@ -969,13 +981,13 @@ class DeviceClassSelector(Selector[DeviceClassSelectorConfig]): valid_options = _enum_options( self.config["domain"], self.SUPPORTED_PLATFORMS[self.config["domain"]] ) - options_schema = vol.In(valid_options) + options_schema = probatio.In(valid_options) if not self.config["multiple"]: - return options_schema(vol.Schema(str)(data)) + return options_schema(probatio.Schema(str)(data)) if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [options_schema(vol.Schema(str)(val)) for val in data] + raise probatio.Invalid("Value should be a list") + return [options_schema(probatio.Schema(str)(val)) for val in data] class DeviceSelectorConfig(BaseSelectorConfig, DeviceFilterSelectorConfig, total=False): @@ -996,11 +1008,11 @@ class DeviceSelector(Selector[DeviceSelectorConfig]): { **_LEGACY_DEVICE_SELECTOR_CONFIG_SCHEMA_DICT, # Device has to contain entities matching this selector - vol.Optional("entity"): vol.All( + probatio.Optional("entity"): probatio.All( cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA] ), - vol.Optional("multiple", default=False): cv.boolean, - vol.Optional("filter"): vol.All( + probatio.Optional("multiple", default=False): cv.boolean, + probatio.Optional("filter"): probatio.All( cv.ensure_list, [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], ), @@ -1014,11 +1026,11 @@ class DeviceSelector(Selector[DeviceSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - device_id: str = vol.Schema(str)(data) + device_id: str = probatio.Schema(str)(data) return device_id if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class DurationSelectorConfig(BaseSelectorConfig, total=False): @@ -1040,13 +1052,13 @@ class DurationSelector(Selector[DurationSelectorConfig]): { # Enable day field in frontend. A selection with `days` set is allowed # even if `enable_day` is not set - vol.Optional("enable_day"): cv.boolean, + probatio.Optional("enable_day"): cv.boolean, # Enable seconds field in frontend. - vol.Optional("enable_second", default=True): cv.boolean, + probatio.Optional("enable_second", default=True): cv.boolean, # Enable millisecond field in frontend. - vol.Optional("enable_millisecond"): cv.boolean, + probatio.Optional("enable_millisecond"): cv.boolean, # Allow negative durations. - vol.Optional("allow_negative"): cv.boolean, + probatio.Optional("allow_negative"): cv.boolean, } ) @@ -1088,15 +1100,15 @@ class EntitySelector(Selector[EntitySelectorConfig]): selector_type = "entity" - CONFIG_SCHEMA = vol.All( + CONFIG_SCHEMA = probatio.All( make_selector_config_schema( { **_LEGACY_ENTITY_SELECTOR_CONFIG_SCHEMA_DICT, - vol.Optional("exclude_entities"): [str], - vol.Optional("include_entities"): [str], - vol.Optional("multiple", default=False): cv.boolean, - vol.Optional("reorder", default=False): cv.boolean, - vol.Optional("filter"): vol.All( + probatio.Optional("exclude_entities"): [str], + probatio.Optional("include_entities"): [str], + probatio.Optional("multiple", default=False): cv.boolean, + probatio.Optional("reorder", default=False): cv.boolean, + probatio.Optional("filter"): probatio.All( cv.ensure_list, [ENTITY_WITH_DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], ), @@ -1122,21 +1134,21 @@ class EntitySelector(Selector[EntitySelectorConfig]): if allowed_domains := cv.ensure_list(self.config.get("domain")): domain = split_entity_id(e_or_u)[0] if domain not in allowed_domains: - raise vol.Invalid( + raise probatio.Invalid( f"Entity {e_or_u} belongs to domain {domain}, " f"expected {allowed_domains}" ) if include_entities: - vol.In(include_entities)(e_or_u) + probatio.In(include_entities)(e_or_u) if exclude_entities: - vol.NotIn(exclude_entities)(e_or_u) + probatio.NotIn(exclude_entities)(e_or_u) return e_or_u if not self.config["multiple"]: return validate(data) if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return cast(list, vol.Schema([validate])(data)) # Output is a list + raise probatio.Invalid("Value should be a list") + return cast(list, probatio.Schema([validate])(data)) # Output is a list class FileSelectorConfig(BaseSelectorConfig): @@ -1154,7 +1166,7 @@ class FileSelector(Selector[FileSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept - vol.Required("accept"): str, + probatio.Required("accept"): str, } ) @@ -1165,7 +1177,7 @@ class FileSelector(Selector[FileSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" if not isinstance(data, str): - raise vol.Invalid("Value should be a string") + raise probatio.Invalid("Value should be a string") UUID(data) @@ -1188,15 +1200,15 @@ class FloorSelector(Selector[FloorSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("entity"): vol.All( + probatio.Optional("entity"): probatio.All( cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("device"): vol.All( + probatio.Optional("device"): probatio.All( cv.ensure_list, [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, } ) @@ -1207,11 +1219,11 @@ class FloorSelector(Selector[FloorSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - floor_id: str = vol.Schema(str)(data) + floor_id: str = probatio.Schema(str)(data) return floor_id if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class IconSelectorConfig(BaseSelectorConfig, total=False): @@ -1227,7 +1239,7 @@ class IconSelector(Selector[IconSelectorConfig]): selector_type = "icon" CONFIG_SCHEMA = make_selector_config_schema( - {vol.Optional("placeholder"): str} + {probatio.Optional("placeholder"): str} # Frontend also has a fallbackPath option, this is not used by core ) @@ -1237,7 +1249,7 @@ class IconSelector(Selector[IconSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - icon: str = vol.Schema(str)(data) + icon: str = probatio.Schema(str)(data) return icon @@ -1255,7 +1267,7 @@ class LabelSelector(Selector[LabelSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, } ) @@ -1266,11 +1278,11 @@ class LabelSelector(Selector[LabelSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - label_id: str = vol.Schema(str)(data) + label_id: str = probatio.Schema(str)(data) return label_id if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class LanguageSelectorConfig(BaseSelectorConfig, total=False): @@ -1289,9 +1301,9 @@ class LanguageSelector(Selector[LanguageSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("languages"): [str], - vol.Optional("native_name", default=False): cv.boolean, - vol.Optional("no_sort", default=False): cv.boolean, + probatio.Optional("languages"): [str], + probatio.Optional("native_name", default=False): cv.boolean, + probatio.Optional("no_sort", default=False): cv.boolean, } ) @@ -1301,9 +1313,9 @@ class LanguageSelector(Selector[LanguageSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - language: str = vol.Schema(str)(data) + language: str = probatio.Schema(str)(data) if "languages" in self.config and language not in self.config["languages"]: - raise vol.Invalid(f"Value {language} is not a valid option") + raise probatio.Invalid(f"Value {language} is not a valid option") return language @@ -1321,13 +1333,13 @@ class LocationSelector(Selector[LocationSelectorConfig]): selector_type = "location" CONFIG_SCHEMA = make_selector_config_schema( - {vol.Optional("radius"): bool, vol.Optional("icon"): str} + {probatio.Optional("radius"): bool, probatio.Optional("icon"): str} ) - DATA_SCHEMA = vol.Schema( + DATA_SCHEMA = probatio.Schema( { - vol.Required("latitude"): vol.Coerce(float), - vol.Required("longitude"): vol.Coerce(float), - vol.Optional("radius"): vol.Coerce(float), + probatio.Required("latitude"): probatio.Coerce(float), + probatio.Required("longitude"): probatio.Coerce(float), + probatio.Optional("radius"): probatio.Coerce(float), } ) @@ -1356,20 +1368,20 @@ class MediaSelector(Selector[MediaSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("accept"): [str], - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("accept"): [str], + probatio.Optional("multiple", default=False): cv.boolean, } ) - DATA_SCHEMA = vol.Schema( + DATA_SCHEMA = probatio.Schema( { # If accept is set, the entity_id field will not be present - vol.Optional("entity_id"): cv.entity_id_or_uuid, + probatio.Optional("entity_id"): cv.entity_id_or_uuid, # Although marked as optional in frontend, this field is required - vol.Required("media_content_id"): str, + probatio.Required("media_content_id"): str, # Although marked as optional in frontend, this field is required - vol.Required("media_content_type"): str, + probatio.Required("media_content_type"): str, # Data used by frontend for decoration. - vol.Optional("metadata"): dict, + probatio.Optional("metadata"): dict, } ) @@ -1391,9 +1403,9 @@ class MediaSelector(Selector[MediaSelectorConfig]): if "accept" not in self.config: # If accept is not set, the entity_id field is required - item_schema_dict[vol.Required("entity_id")] = cv.entity_id_or_uuid + item_schema_dict[probatio.Required("entity_id")] = cv.entity_id_or_uuid - item_schema = vol.Schema(item_schema_dict) + item_schema = probatio.Schema(item_schema_dict) if not self.config["multiple"]: media: dict[str, Any] = item_schema(data) @@ -1432,7 +1444,7 @@ def validate_slider(data: Any) -> Any: data["mode"] = "slider" if has_min_max else "box" if data["mode"] == "slider" and not has_min_max: - raise vol.Invalid("min and max are required in slider mode") + raise probatio.Invalid("min and max are required in slider mode") return data @@ -1443,21 +1455,22 @@ class NumberSelector(Selector[NumberSelectorConfig]): selector_type = "number" - CONFIG_SCHEMA = vol.All( + CONFIG_SCHEMA = probatio.All( make_selector_config_schema( { - vol.Optional("min"): vol.Coerce(float), - vol.Optional("max"): vol.Coerce(float), + probatio.Optional("min"): probatio.Coerce(float), + probatio.Optional("max"): probatio.Coerce(float), # Controls slider steps, and up/down keyboard binding for the box # user input is not rounded - vol.Optional("step", default=1): vol.Any( - "any", vol.All(vol.Coerce(float), vol.Range(min=1e-3)) + probatio.Optional("step", default=1): probatio.Any( + "any", + probatio.All(probatio.Coerce(float), probatio.Range(min=1e-3)), ), - vol.Optional(CONF_UNIT_OF_MEASUREMENT): str, - vol.Optional(CONF_MODE): vol.All( - vol.Coerce(NumberSelectorMode), lambda val: val.value + probatio.Optional(CONF_UNIT_OF_MEASUREMENT): str, + probatio.Optional(CONF_MODE): probatio.All( + probatio.Coerce(NumberSelectorMode), lambda val: val.value ), - vol.Optional("translation_key"): str, + probatio.Optional("translation_key"): str, } ), validate_slider, @@ -1469,13 +1482,13 @@ class NumberSelector(Selector[NumberSelectorConfig]): def __call__(self, data: Any) -> float: """Validate the passed selection.""" - value: float = vol.Coerce(float)(data) + value: float = probatio.Coerce(float)(data) if "min" in self.config and value < self.config["min"]: - raise vol.Invalid(f"Value {value} is too small") + raise probatio.Invalid(f"Value {value} is too small") if "max" in self.config and value > self.config["max"]: - raise vol.Invalid(f"Value {value} is too large") + raise probatio.Invalid(f"Value {value} is too large") return value @@ -1536,7 +1549,7 @@ def _validate_numeric_threshold_active_choice( ) -> dict[str, Any]: """Validate that active_choice matches an existing key in the entry.""" if "active_choice" not in data and "number" in data and "entity" in data: - raise vol.Invalid( + raise probatio.Invalid( "Value entry contains both 'number' and 'entity';" " set 'active_choice' to disambiguate" ) @@ -1544,26 +1557,30 @@ def _validate_numeric_threshold_active_choice( return data active_choice = data["active_choice"] if active_choice not in data: - raise vol.Invalid( + raise probatio.Invalid( f"active_choice is '{active_choice}' but '{active_choice}' key is missing" ) return data -_NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA = vol.All( - vol.Schema( +_NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA = probatio.All( + probatio.Schema( { - vol.Optional("active_choice"): vol.All( - vol.Coerce(NumericThresholdActiveChoice), lambda val: val.value + probatio.Optional("active_choice"): probatio.All( + probatio.Coerce(NumericThresholdActiveChoice), lambda val: val.value ), - vol.Optional("number"): vol.Coerce(float), - vol.Optional("entity"): cv.entity_id, - vol.Optional("unit_of_measurement"): vol.Any(str, None), + probatio.Optional("number"): probatio.Coerce(float), + probatio.Optional("entity"): cv.entity_id, + probatio.Optional("unit_of_measurement"): probatio.Any(str, None), } ), - vol.Any( - vol.Schema({vol.Required("number"): object}, extra=vol.ALLOW_EXTRA), - vol.Schema({vol.Required("entity"): object}, extra=vol.ALLOW_EXTRA), + probatio.Any( + probatio.Schema( + {probatio.Required("number"): object}, extra=probatio.ALLOW_EXTRA + ), + probatio.Schema( + {probatio.Required("entity"): object}, extra=probatio.ALLOW_EXTRA + ), msg="Value entry must contain at least one of 'number' or 'entity'", ), _validate_numeric_threshold_active_choice, @@ -1584,35 +1601,35 @@ def _validate_numeric_threshold_range[_T: dict[str, Any]](value: _T) -> _T: min_number = min_entry.get("number") max_number = max_entry.get("number") if min_number is not None and max_number is not None and min_number > max_number: - raise vol.Invalid( + raise probatio.Invalid( f"value_min ({min_number}) must not be greater than" f" value_max ({max_number})" ) return value -_NUMERIC_THRESHOLD_VALUE_SCHEMA = vol.All( - vol.Any( - vol.Schema( +_NUMERIC_THRESHOLD_VALUE_SCHEMA = probatio.All( + probatio.Any( + probatio.Schema( { - vol.Required("type"): vol.In( + probatio.Required("type"): probatio.In( [NumericThresholdType.ABOVE, NumericThresholdType.BELOW] ), - vol.Required("value"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, + probatio.Required("value"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, } ), - vol.Schema( + probatio.Schema( { - vol.Required("type"): vol.In( + probatio.Required("type"): probatio.In( [NumericThresholdType.BETWEEN, NumericThresholdType.OUTSIDE] ), - vol.Required("value_min"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, - vol.Required("value_max"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, + probatio.Required("value_min"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, + probatio.Required("value_max"): _NUMERIC_THRESHOLD_VALUE_ENTRY_SCHEMA, } ), - vol.Schema( + probatio.Schema( { - vol.Required("type"): vol.In([NumericThresholdType.ANY]), + probatio.Required("type"): probatio.In([NumericThresholdType.ANY]), } ), ), @@ -1638,12 +1655,12 @@ def _validate_numeric_threshold_unit[_T: dict[str, Any]]( if "number" not in entry: continue if "unit_of_measurement" not in entry: - raise vol.Invalid( + raise probatio.Invalid( f"Missing unit_of_measurement, expected one of {allowed_units}" ) unit = entry["unit_of_measurement"] if unit not in allowed_units: - raise vol.Invalid( + raise probatio.Invalid( f"Invalid unit_of_measurement '{unit}'," f" expected one of {allowed_units}" ) @@ -1655,7 +1672,9 @@ def _validate_numeric_threshold_unit[_T: dict[str, Any]]( def _validate_numeric_threshold_not_any[_T: dict[str, Any]](value: _T) -> _T: """Validate that the threshold type is not 'any'.""" if value.get("type") == NumericThresholdType.ANY: - raise vol.Invalid("Threshold type 'any' is only allowed when mode is 'changed'") + raise probatio.Invalid( + "Threshold type 'any' is only allowed when mode is 'changed'" + ) return value @@ -1680,11 +1699,11 @@ def _validate_numeric_threshold_number_range[_T: dict[str, Any]]( continue number = entry["number"] if min_value is not None and number < min_value: - raise vol.Invalid( + raise probatio.Invalid( f"Value {number} is less than the minimum {min_value}" ) if max_value is not None and number > max_value: - raise vol.Invalid( + raise probatio.Invalid( f"Value {number} is greater than the maximum {max_value}" ) return value @@ -1700,12 +1719,12 @@ class NumericThresholdSelector(Selector[NumericThresholdSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("mode"): vol.All( - vol.Coerce(NumericThresholdMode), lambda val: val.value + probatio.Required("mode"): probatio.All( + probatio.Coerce(NumericThresholdMode), lambda val: val.value ), - vol.Optional("unit_of_measurement"): [vol.Any(str, None)], - vol.Optional("number"): NumberSelector.CONFIG_SCHEMA, - vol.Optional("entity"): vol.All( + probatio.Optional("unit_of_measurement"): [probatio.Any(str, None)], + probatio.Optional("number"): NumberSelector.CONFIG_SCHEMA, + probatio.Optional("entity"): probatio.All( cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA] ), } @@ -1725,7 +1744,7 @@ class NumericThresholdSelector(Selector[NumericThresholdSelectorConfig]): validators.append(_validate_numeric_threshold_unit(allowed_units)) if number_config := cast(dict[str, Any] | None, self.config.get("number")): validators.append(_validate_numeric_threshold_number_range(number_config)) - return vol.All(*validators)(data) + return probatio.All(*validators)(data) class ObjectSelectorField(TypedDict, total=False): @@ -1754,17 +1773,19 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("fields"): { + probatio.Optional("fields"): { str: { - vol.Required("selector"): vol.Any(Selector, validate_selector), - vol.Optional("required"): bool, - vol.Optional("label"): str, + probatio.Required("selector"): probatio.Any( + Selector, validate_selector + ), + probatio.Optional("required"): bool, + probatio.Optional("label"): str, } }, - vol.Optional("multiple", default=False): bool, - vol.Optional("label_field"): str, - vol.Optional("description_field"): str, - vol.Optional("translation_key"): str, + probatio.Optional("multiple", default=False): bool, + probatio.Optional("label_field"): str, + probatio.Optional("description_field"): str, + probatio.Optional("translation_key"): str, } ) @@ -1774,7 +1795,7 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): @override def serialize(self) -> dict[str, dict[str, ObjectSelectorConfig]]: - """Serialize ObjectSelector for voluptuous_serialize.""" + """Serialize ObjectSelector for to_field_list.""" _config = deepcopy(self.config) if "fields" in _config: for field_items in _config["fields"].values(): @@ -1791,16 +1812,16 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): return data if not isinstance(data, (list, dict)): - raise vol.Invalid("Value should be a dict or a list of dicts") + raise probatio.Invalid("Value should be a dict or a list of dicts") if isinstance(data, list) and not self.config["multiple"]: - raise vol.Invalid("Value should not be a list") + raise probatio.Invalid("Value should not be a list") test_data = data if isinstance(data, list) else [data] for _config in test_data: for field, field_data in self.config["fields"].items(): if field_data.get("required") and field not in _config: - raise vol.Invalid(f"Field {field} is required") + raise probatio.Invalid(f"Field {field} is required") if field in _config: field_selector = field_data["selector"] if isinstance(field_selector, Selector): @@ -1810,7 +1831,7 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): for key in _config: if key not in self.config["fields"]: - raise vol.Invalid(f"Field {key} is not allowed") + raise probatio.Invalid(f"Field {key} is not allowed") return data @@ -1840,10 +1861,10 @@ class QrCodeSelector(Selector[QrCodeSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("data"): str, - vol.Optional("scale"): int, - vol.Optional("error_correction_level"): vol.All( - vol.Coerce(QrErrorCorrectionLevel), lambda val: val.value + probatio.Required("data"): str, + probatio.Optional("scale"): int, + probatio.Optional("error_correction_level"): probatio.All( + probatio.Coerce(QrErrorCorrectionLevel), lambda val: val.value ), } ) @@ -1854,16 +1875,16 @@ class QrCodeSelector(Selector[QrCodeSelectorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - vol.Schema(vol.Any(str, None))(data) + probatio.Schema(probatio.Any(str, None))(data) return self.config["data"] -select_option = vol.All( +select_option = probatio.All( dict, - vol.Schema( + probatio.Schema( { - vol.Required("value"): str, - vol.Required("label"): str, + probatio.Required("value"): str, + probatio.Required("label"): str, } ), ) @@ -1902,14 +1923,16 @@ class SelectSelector(Selector[SelectSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Required("options"): vol.All(vol.Any([str], [select_option])), - vol.Optional("multiple", default=False): cv.boolean, - vol.Optional("custom_value", default=False): cv.boolean, - vol.Optional("mode"): vol.All( - vol.Coerce(SelectSelectorMode), lambda val: val.value + probatio.Required("options"): probatio.All( + probatio.Any([str], [select_option]) ), - vol.Optional("translation_key"): cv.string, - vol.Optional("sort", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, + probatio.Optional("custom_value", default=False): cv.boolean, + probatio.Optional("mode"): probatio.All( + probatio.Coerce(SelectSelectorMode), lambda val: val.value + ), + probatio.Optional("translation_key"): cv.string, + probatio.Optional("sort", default=False): cv.boolean, } ) @@ -1929,15 +1952,15 @@ class SelectSelector(Selector[SelectSelectorConfig]): for option in cast(Sequence[SelectOptionDict], config_options) ] - parent_schema: vol.In | vol.Any = vol.In(options) + parent_schema: probatio.In | probatio.Any = probatio.In(options) if self.config["custom_value"]: - parent_schema = vol.Any(parent_schema, str) + parent_schema = probatio.Any(parent_schema, str) if not self.config["multiple"]: - return parent_schema(vol.Schema(str)(data)) + return parent_schema(probatio.Schema(str)(data)) if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [parent_schema(vol.Schema(str)(val)) for val in data] + raise probatio.Invalid("Value should be a list") + return [parent_schema(probatio.Schema(str)(val)) for val in data] class SerialPortSelectorConfig(BaseSelectorConfig, total=False): @@ -1954,7 +1977,7 @@ class SerialPortSelector(Selector[SerialPortSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("extra_recommended_domains"): [str], + probatio.Optional("extra_recommended_domains"): [str], } ) @@ -1964,7 +1987,7 @@ class SerialPortSelector(Selector[SerialPortSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - serial: str = vol.Schema(str)(data) + serial: str = probatio.Schema(str)(data) return serial @@ -1984,14 +2007,14 @@ class StateClassSelector(Selector[StateClassSelectorConfig]): @staticmethod def _valid_state_classes(options: list[str]) -> list[str]: """Validate state classes and raise if invalid.""" - vol.In(_enum_options(Platform.SENSOR, "SensorStateClass"))(options) + probatio.In(_enum_options(Platform.SENSOR, "SensorStateClass"))(options) return options - CONFIG_SCHEMA = vol.All( + CONFIG_SCHEMA = probatio.All( make_selector_config_schema( { - vol.Optional("multiple", default=False): cv.boolean, - vol.Optional("state_classes"): vol.All( + probatio.Optional("multiple", default=False): cv.boolean, + probatio.Optional("state_classes"): probatio.All( cv.ensure_list, [str], [_valid_state_classes] ), }, @@ -2010,13 +2033,13 @@ class StateClassSelector(Selector[StateClassSelectorConfig]): for option in _enum_options(Platform.SENSOR, "SensorStateClass") if state_classes_filter is None or option in state_classes_filter ] - options_schema = vol.In(valid_options) + options_schema = probatio.In(valid_options) if not self.config["multiple"]: - return options_schema(vol.Schema(str)(data)) + return options_schema(probatio.Schema(str)(data)) if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [options_schema(vol.Schema(str)(val)) for val in data] + raise probatio.Invalid("Value should be a list") + return [options_schema(probatio.Schema(str)(val)) for val in data] class StateSelectorConfig(BaseSelectorConfig, total=False): @@ -2036,10 +2059,10 @@ class StateSelector(Selector[StateSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("entity_id"): cv.entity_id, - vol.Optional("hide_states"): [str], - vol.Optional("attribute"): str, - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("entity_id"): cv.entity_id, + probatio.Optional("hide_states"): [str], + probatio.Optional("attribute"): str, + probatio.Optional("multiple", default=False): cv.boolean, } ) @@ -2058,11 +2081,11 @@ class StateSelector(Selector[StateSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - state: str = vol.Schema(str)(data) + state: str = probatio.Schema(str)(data) return state if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class StatisticSelectorConfig(BaseSelectorConfig, total=False): @@ -2079,7 +2102,7 @@ class StatisticSelector(Selector[StatisticSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("multiple", default=False): cv.boolean, + probatio.Optional("multiple", default=False): cv.boolean, } ) @@ -2091,11 +2114,11 @@ class StatisticSelector(Selector[StatisticSelectorConfig]): """Validate the passed selection.""" if not self.config["multiple"]: - stat: str = vol.Schema(str)(data) + stat: str = probatio.Schema(str)(data) return stat if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class TargetSelectorConfig(BaseSelectorConfig, total=False): @@ -2117,20 +2140,20 @@ class TargetSelector(Selector[TargetSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("entity"): vol.All( + probatio.Optional("entity"): probatio.All( cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("device"): vol.All( + probatio.Optional("device"): probatio.All( cv.ensure_list, [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], ), - vol.Optional("primary_entities_only"): cv.boolean, + probatio.Optional("primary_entities_only"): cv.boolean, } ) # We want to transition to not including templates in the target selector. - TARGET_SELECTION_SCHEMA = vol.Schema(cv._TARGET_SERVICE_FIELDS_TEMPLATED) # noqa: SLF001 + TARGET_SELECTION_SCHEMA = probatio.Schema(cv._TARGET_SERVICE_FIELDS_TEMPLATED) # noqa: SLF001 def __init__(self, config: TargetSelectorConfig | None = None) -> None: """Instantiate a selector.""" @@ -2201,16 +2224,16 @@ class TextSelector(Selector[TextSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("multiline", default=False): bool, - vol.Optional("prefix"): str, - vol.Optional("suffix"): str, + probatio.Optional("multiline", default=False): bool, + probatio.Optional("prefix"): str, + probatio.Optional("suffix"): str, # The "type" controls the input field in the browser, the resulting # data can be any string so we don't validate it. - vol.Optional("type"): vol.All( - vol.Coerce(TextSelectorType), lambda val: val.value + probatio.Optional("type"): probatio.All( + probatio.Coerce(TextSelectorType), lambda val: val.value ), - vol.Optional("autocomplete"): str, - vol.Optional("multiple", default=False): bool, + probatio.Optional("autocomplete"): str, + probatio.Optional("multiple", default=False): bool, } ) @@ -2221,11 +2244,11 @@ class TextSelector(Selector[TextSelectorConfig]): def __call__(self, data: Any) -> str | list[str]: """Validate the passed selection.""" if not self.config["multiple"]: - text: str = vol.Schema(str)(data) + text: str = probatio.Schema(str)(data) return text if not isinstance(data, list): - raise vol.Invalid("Value should be a list") - return [vol.Schema(str)(val) for val in data] + raise probatio.Invalid("Value should be a list") + return [probatio.Schema(str)(val) for val in data] class ThemeSelectorConfig(BaseSelectorConfig): @@ -2240,7 +2263,7 @@ class ThemeSelector(Selector[ThemeSelectorConfig]): CONFIG_SCHEMA = make_selector_config_schema( { - vol.Optional("include_default", default=False): cv.boolean, + probatio.Optional("include_default", default=False): cv.boolean, } ) @@ -2250,7 +2273,7 @@ class ThemeSelector(Selector[ThemeSelectorConfig]): def __call__(self, data: Any) -> str: """Validate the passed selection.""" - theme: str = vol.Schema(str)(data) + theme: str = probatio.Schema(str)(data) return theme @@ -2294,7 +2317,7 @@ class TriggerSelector(Selector[TriggerSelectorConfig]): def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - return vol.Schema(cv.TRIGGER_SCHEMA)(data) + return probatio.Schema(cv.TRIGGER_SCHEMA)(data) dumper.add_representer( diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index d4e9534c6337..00c8904136b7 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -9,7 +9,7 @@ import logging from types import ModuleType from typing import TYPE_CHECKING, Any, Literal, TypedDict, cast, overload -import voluptuous as vol +import probatio from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL from homeassistant.config_entries import ConfigEntry, ConfigEntryState @@ -131,26 +131,26 @@ def _validate_option_or_feature(option_or_feature: str, label: str) -> Any: try: domain, enum, option = option_or_feature.split(".", 2) except ValueError as exc: - raise vol.Invalid( + raise probatio.Invalid( f"Invalid {label} '{option_or_feature}', expected .." ) from exc base_components = _base_components() if not (base_component := base_components.get(domain)): - raise vol.Invalid(f"Unknown base component '{domain}'") + raise probatio.Invalid(f"Unknown base component '{domain}'") try: attribute_enum = getattr(base_component, enum) except AttributeError as exc: - raise vol.Invalid(f"Unknown {label} enum '{domain}.{enum}'") from exc + raise probatio.Invalid(f"Unknown {label} enum '{domain}.{enum}'") from exc if not issubclass(attribute_enum, Enum): - raise vol.Invalid(f"Expected {label} '{domain}.{enum}' to be an enum") + raise probatio.Invalid(f"Expected {label} '{domain}.{enum}' to be an enum") try: return getattr(attribute_enum, option).value except AttributeError as exc: - raise vol.Invalid(f"Unknown {label} '{enum}.{option}'") from exc + raise probatio.Invalid(f"Unknown {label} '{enum}.{option}'") from exc def validate_attribute_option(attribute_option: str) -> Any: @@ -165,50 +165,50 @@ def validate_supported_feature(supported_feature: str) -> Any: # Basic schemas which translate attribute and supported feature enum names # to their values. Full validation is done by hassfest.services -_FIELD_SCHEMA = vol.Schema( +_FIELD_SCHEMA = probatio.Schema( { - vol.Optional(CONF_SELECTOR): selector.validate_selector, - vol.Optional("filter"): { - vol.Optional("attribute"): { - vol.Required(str): [vol.All(str, validate_attribute_option)], + probatio.Optional(CONF_SELECTOR): selector.validate_selector, + probatio.Optional("filter"): { + probatio.Optional("attribute"): { + probatio.Required(str): [probatio.All(str, validate_attribute_option)], }, - vol.Optional("supported_features"): [ - vol.All(str, validate_supported_feature) + probatio.Optional("supported_features"): [ + probatio.All(str, validate_supported_feature) ], }, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) -_SECTION_SCHEMA = vol.Schema( +_SECTION_SCHEMA = probatio.Schema( { - vol.Required("fields"): vol.Schema({str: _FIELD_SCHEMA}), + probatio.Required("fields"): probatio.Schema({str: _FIELD_SCHEMA}), }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) -_SERVICE_SCHEMA = vol.Schema( +_SERVICE_SCHEMA = probatio.Schema( { - vol.Optional("target"): TargetSelector.CONFIG_SCHEMA, - vol.Optional("fields"): vol.Schema( - {str: vol.Any(_SECTION_SCHEMA, _FIELD_SCHEMA)} + probatio.Optional("target"): TargetSelector.CONFIG_SCHEMA, + probatio.Optional("fields"): probatio.Schema( + {str: probatio.Any(_SECTION_SCHEMA, _FIELD_SCHEMA)} ), }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) def starts_with_dot(key: str) -> str: """Check if key starts with dot.""" if not key.startswith("."): - raise vol.Invalid("Key does not start with .") + raise probatio.Invalid("Key does not start with .") return key -_SERVICES_SCHEMA = vol.Schema( +_SERVICES_SCHEMA = probatio.Schema( { - vol.Remove(vol.All(str, starts_with_dot)): object, - cv.slug: vol.Any(None, _SERVICE_SCHEMA), + probatio.Remove(probatio.All(str, starts_with_dot)): object, + cv.slug: probatio.Any(None, _SERVICE_SCHEMA), } ) @@ -268,7 +268,7 @@ def async_prepare_call_from_config( if validate_config: try: config = cv.SERVICE_SCHEMA(config) - except vol.Invalid as ex: + except probatio.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}" ) from ex @@ -286,7 +286,7 @@ def async_prepare_call_from_config( raise HomeAssistantError( f"Error rendering service name template: {ex}" ) from ex - except vol.Invalid as ex: + except probatio.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}" ) from ex @@ -314,7 +314,7 @@ def async_prepare_call_from_config( raise HomeAssistantError( f"Error rendering service target template: {ex}" ) from ex - except vol.Invalid as ex: + except probatio.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid entity IDs: {target[CONF_ENTITY_ID]}" ) from ex @@ -457,7 +457,7 @@ def _load_services_file(integration: Integration) -> JSON_TYPE: "Unable to find services.yaml for the %s integration", integration.domain ) return {} - except (HomeAssistantError, vol.Invalid) as ex: + except (HomeAssistantError, probatio.Invalid) as ex: _LOGGER.warning( "Unable to parse services.yaml for the %s integration: %s", integration.domain, @@ -615,7 +615,7 @@ def async_set_service_schema( # Match validation applied to descriptions loaded from services.yaml. try: description["target"] = TargetSelector.CONFIG_SCHEMA(schema["target"]) - except vol.Invalid as err: + except probatio.Invalid as err: _LOGGER.warning( "Invalid target in the description of service %s.%s, ignoring it: %s", domain, @@ -1005,7 +1005,7 @@ def async_register_admin_service( | EntityServiceResponse | None, ], - schema: VolSchemaType = vol.Schema({}, extra=vol.PREVENT_EXTRA), + schema: VolSchemaType = probatio.Schema({}, extra=probatio.PREVENT_EXTRA), supports_response: SupportsResponse = SupportsResponse.NONE, *, description_placeholders: Mapping[str, str] | None = None, diff --git a/homeassistant/helpers/template/extensions/areas.py b/homeassistant/helpers/template/extensions/areas.py index 5f446568f6a4..7dbd2cd5bf1b 100644 --- a/homeassistant/helpers/template/extensions/areas.py +++ b/homeassistant/helpers/template/extensions/areas.py @@ -3,7 +3,7 @@ from collections.abc import Iterable from typing import TYPE_CHECKING -import voluptuous as vol +import probatio from homeassistant.helpers import ( area_registry as ar, @@ -92,7 +92,7 @@ class AreaExtension(BaseTemplateExtension): try: cv.entity_id(lookup_value) - except vol.Invalid: + except probatio.Invalid: pass else: if entity := ent_reg.async_get(lookup_value): diff --git a/homeassistant/helpers/template/extensions/devices.py b/homeassistant/helpers/template/extensions/devices.py index 271a347ab251..8d79983628dd 100644 --- a/homeassistant/helpers/template/extensions/devices.py +++ b/homeassistant/helpers/template/extensions/devices.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from itertools import chain from typing import TYPE_CHECKING, Any -import voluptuous as vol +import probatio from homeassistant.exceptions import TemplateError from homeassistant.helpers import ( @@ -103,7 +103,7 @@ class DeviceExtension(BaseTemplateExtension): try: cv.entity_id(lookup_value) - except vol.Invalid: + except probatio.Invalid: pass else: if entity := ent_reg.async_get(lookup_value): diff --git a/homeassistant/helpers/template/extensions/labels.py b/homeassistant/helpers/template/extensions/labels.py index e65d4a004353..6ebc1be77bbd 100644 --- a/homeassistant/helpers/template/extensions/labels.py +++ b/homeassistant/helpers/template/extensions/labels.py @@ -3,7 +3,7 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any -import voluptuous as vol +import probatio from homeassistant.helpers import ( area_registry as ar, @@ -95,7 +95,7 @@ class LabelExtension(BaseTemplateExtension): try: cv.entity_id(lookup_value) - except vol.Invalid: + except probatio.Invalid: pass else: if entity := ent_reg.async_get(lookup_value): diff --git a/homeassistant/helpers/template/helpers.py b/homeassistant/helpers/template/helpers.py index 039b6c40b537..7c94006ad671 100644 --- a/homeassistant/helpers/template/helpers.py +++ b/homeassistant/helpers/template/helpers.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, NoReturn, overload -import voluptuous as vol +import probatio from homeassistant.helpers import ( area_registry as ar, @@ -51,7 +51,7 @@ def resolve_area_id(hass: HomeAssistant, lookup_value: Any) -> str | None: # Check if it's an entity ID try: cv.entity_id(lookup_value) - except vol.Invalid: + except probatio.Invalid: pass else: if entity := ent_reg.async_get(lookup_value): @@ -83,7 +83,7 @@ def forgiving_boolean[_T]( """Try to convert value to a boolean.""" try: return cv.boolean(value) - except vol.Invalid: + except probatio.Invalid: if default is _SENTINEL: raise_no_default("bool", value) return default diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 9451757998c6..0dfe924eac57 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -22,7 +22,7 @@ from typing import ( override, ) -import voluptuous as vol +import probatio from homeassistant.const import ( ATTR_ENTITY_ID, @@ -122,33 +122,33 @@ TRIGGERS: HassKey[dict[str, str]] = HassKey("triggers") # Basic schemas to sanity check the trigger descriptions, # full validation is done by hassfest.triggers -_FIELD_DESCRIPTION_SCHEMA = vol.Schema( +_FIELD_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Optional(CONF_SELECTOR): selector.validate_selector, + probatio.Optional(CONF_SELECTOR): selector.validate_selector, }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) -_TRIGGER_DESCRIPTION_SCHEMA = vol.Schema( +_TRIGGER_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Optional("target"): TargetSelector.CONFIG_SCHEMA, - vol.Optional("fields"): vol.Schema({str: _FIELD_DESCRIPTION_SCHEMA}), + probatio.Optional("target"): TargetSelector.CONFIG_SCHEMA, + probatio.Optional("fields"): probatio.Schema({str: _FIELD_DESCRIPTION_SCHEMA}), }, - extra=vol.ALLOW_EXTRA, + extra=probatio.ALLOW_EXTRA, ) def starts_with_dot(key: str) -> str: """Check if key starts with dot.""" if not key.startswith("."): - raise vol.Invalid("Key does not start with .") + raise probatio.Invalid("Key does not start with .") return key -_TRIGGERS_DESCRIPTION_SCHEMA = vol.Schema( +_TRIGGERS_DESCRIPTION_SCHEMA = probatio.Schema( { - vol.Remove(vol.All(str, starts_with_dot)): object, - cv.underscore_slug: vol.Any(None, _TRIGGER_DESCRIPTION_SCHEMA), + probatio.Remove(probatio.All(str, starts_with_dot)): object, + cv.underscore_slug: probatio.Any(None, _TRIGGER_DESCRIPTION_SCHEMA), } ) @@ -231,8 +231,8 @@ async def _register_trigger_platform( _TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( { - vol.Optional(CONF_OPTIONS): object, - vol.Optional(CONF_TARGET): cv.TARGET_FIELDS, + probatio.Optional(CONF_OPTIONS): object, + probatio.Optional(CONF_TARGET): cv.TARGET_FIELDS, } ) @@ -355,21 +355,21 @@ def _backwards_compatible_behavior(value: Any) -> Any: return value -ENTITY_STATE_TRIGGER_SCHEMA = vol.Schema( +ENTITY_STATE_TRIGGER_SCHEMA = probatio.Schema( { - vol.Required(CONF_TARGET): cv.TARGET_FIELDS, - vol.Required(CONF_OPTIONS, default={}): {}, + probatio.Required(CONF_TARGET): cv.TARGET_FIELDS, + probatio.Required(CONF_OPTIONS, default={}): {}, } ) ENTITY_STATE_TRIGGER_SCHEMA_WITH_BEHAVIOR = ENTITY_STATE_TRIGGER_SCHEMA.extend( { - vol.Required(CONF_OPTIONS, default={}): { - vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_EACH): vol.All( + probatio.Required(CONF_OPTIONS, default={}): { + probatio.Required(ATTR_BEHAVIOR, default=BEHAVIOR_EACH): probatio.All( _backwards_compatible_behavior, - vol.In([BEHAVIOR_FIRST, BEHAVIOR_ALL, BEHAVIOR_EACH]), + probatio.In([BEHAVIOR_FIRST, BEHAVIOR_ALL, BEHAVIOR_EACH]), ), - vol.Optional(CONF_FOR): cv.positive_time_period, + probatio.Optional(CONF_FOR): cv.positive_time_period, }, } ) @@ -391,7 +391,7 @@ class EntityTriggerBase(Trigger): # `_excluded_states`. Subclasses can override to relax the origin # check. _excluded_from_states: ClassVar[frozenset[str]] = _excluded_states - _schema: vol.Schema = ENTITY_STATE_TRIGGER_SCHEMA_WITH_BEHAVIOR + _schema: probatio.Schema = ENTITY_STATE_TRIGGER_SCHEMA_WITH_BEHAVIOR # When True, indirect target expansion (via device/area/floor) skips # entities with an entity_category. _primary_entities_only: ClassVar[bool] = True @@ -791,15 +791,15 @@ class StatelessEntityTriggerBase(EntityTriggerBase): after startup must still fire the trigger. """ - _schema: vol.Schema = ENTITY_STATE_TRIGGER_SCHEMA + _schema: probatio.Schema = ENTITY_STATE_TRIGGER_SCHEMA _excluded_from_states: ClassVar[frozenset[str]] = frozenset({STATE_UNAVAILABLE}) NUMERICAL_ATTRIBUTE_CHANGED_TRIGGER_SCHEMA = ENTITY_STATE_TRIGGER_SCHEMA.extend( { - vol.Required(CONF_OPTIONS, default={}): vol.All( + probatio.Required(CONF_OPTIONS, default={}): probatio.All( { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig(mode=NumericThresholdMode.CHANGED) ) }, @@ -1097,13 +1097,13 @@ class EntityNumericalStateChangedTriggerBase(EntityNumericalStateTriggerBase): def make_numerical_state_changed_with_unit_schema( unit_converter: type[BaseUnitConverter], -) -> vol.Schema: +) -> probatio.Schema: """Factory for numerical state trigger schema with unit option.""" return ENTITY_STATE_TRIGGER_SCHEMA.extend( { - vol.Required(CONF_OPTIONS, default={}): vol.All( + probatio.Required(CONF_OPTIONS, default={}): probatio.All( { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig( mode=NumericThresholdMode.CHANGED, unit_of_measurement=list(unit_converter.VALID_UNITS), @@ -1131,8 +1131,8 @@ class EntityNumericalStateChangedTriggerWithUnitBase( NUMERICAL_ATTRIBUTE_CROSSED_THRESHOLD_SCHEMA = ( ENTITY_STATE_TRIGGER_SCHEMA_WITH_BEHAVIOR.extend( { - vol.Required(CONF_OPTIONS): { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required(CONF_OPTIONS): { + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig(mode=NumericThresholdMode.CROSSED) ), }, @@ -1158,7 +1158,7 @@ class EntityNumericalStateCrossedThresholdTriggerBase(EntityNumericalStateTrigge def _make_numerical_state_crossed_threshold_with_unit_schema( unit_converter: type[BaseUnitConverter], -) -> vol.Schema: +) -> probatio.Schema: """Trigger for numerical state and state attribute changes. This trigger only fires when the observed attribute @@ -1166,8 +1166,8 @@ def _make_numerical_state_crossed_threshold_with_unit_schema( """ return ENTITY_STATE_TRIGGER_SCHEMA_WITH_BEHAVIOR.extend( { - vol.Required(CONF_OPTIONS, default={}): { - vol.Required("threshold"): NumericThresholdSelector( + probatio.Required(CONF_OPTIONS, default={}): { + probatio.Required("threshold"): NumericThresholdSelector( NumericThresholdSelectorConfig( mode=NumericThresholdMode.CROSSED, unit_of_measurement=list(unit_converter.VALID_UNITS), @@ -1353,7 +1353,7 @@ class TriggerProtocol(Protocol): async def async_get_triggers(self, hass: HomeAssistant) -> dict[str, type[Trigger]]: """Return the triggers provided by this integration.""" - TRIGGER_SCHEMA: vol.Schema + TRIGGER_SCHEMA: probatio.Schema async def async_validate_trigger_config( self, hass: HomeAssistant, config: ConfigType @@ -1634,11 +1634,11 @@ async def _async_get_trigger_platform( try: integration = await async_get_integration(hass, platform) except IntegrationNotFound: - raise vol.Invalid(f"Invalid trigger '{trigger_key}' specified") from None + raise probatio.Invalid(f"Invalid trigger '{trigger_key}' specified") from None try: platform_module = await integration.async_get_platform("trigger") except ImportError: - raise vol.Invalid( + raise probatio.Invalid( f"Integration '{platform}' does not provide trigger support" ) from None @@ -1662,7 +1662,7 @@ async def async_validate_trigger_config( platform_domain, trigger_key ) if not (trigger := trigger_descriptors.get(relative_trigger_key)): - raise vol.Invalid(f"Invalid trigger '{trigger_key}' specified") + raise probatio.Invalid(f"Invalid trigger '{trigger_key}' specified") conf = await trigger.async_validate_complete_config(hass, conf) elif hasattr(platform, "async_validate_trigger_config"): conf = move_options_fields_to_top_level(conf, cv.TRIGGER_BASE_SCHEMA) @@ -1928,7 +1928,7 @@ def _load_triggers_file(integration: Integration) -> dict[str, Any]: "Unable to find triggers.yaml for the %s integration", integration.domain ) return {} - except (HomeAssistantError, vol.Invalid) as ex: + except (HomeAssistantError, probatio.Invalid) as ex: _LOGGER.warning( "Unable to parse triggers.yaml for the %s integration: %s", integration.domain, diff --git a/homeassistant/helpers/trigger_template_entity.py b/homeassistant/helpers/trigger_template_entity.py index 9515fd43f85d..3a860a2d61d6 100644 --- a/homeassistant/helpers/trigger_template_entity.py +++ b/homeassistant/helpers/trigger_template_entity.py @@ -5,7 +5,7 @@ import logging from typing import Any, override import jinja2 -import voluptuous as vol +import probatio from homeassistant.components.sensor import ( CONF_STATE_CLASS, @@ -51,24 +51,24 @@ CONF_TO_ATTRIBUTE = { CONF_PICTURE: EntityStateAttribute.ENTITY_PICTURE, } -TEMPLATE_ENTITY_BASE_SCHEMA = vol.Schema( +TEMPLATE_ENTITY_BASE_SCHEMA = probatio.Schema( { - vol.Optional(CONF_ICON): cv.template, - vol.Optional(CONF_NAME): cv.template, - vol.Optional(CONF_PICTURE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, + probatio.Optional(CONF_ICON): cv.template, + probatio.Optional(CONF_NAME): cv.template, + probatio.Optional(CONF_PICTURE): cv.template, + probatio.Optional(CONF_UNIQUE_ID): cv.string, } ) -def make_template_entity_base_schema(default_name: str) -> vol.Schema: +def make_template_entity_base_schema(default_name: str) -> probatio.Schema: """Return a schema with default name.""" - return vol.Schema( + return probatio.Schema( { - vol.Optional(CONF_ICON): cv.template, - vol.Optional(CONF_NAME, default=default_name): cv.template, - vol.Optional(CONF_PICTURE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, + probatio.Optional(CONF_ICON): cv.template, + probatio.Optional(CONF_NAME, default=default_name): cv.template, + probatio.Optional(CONF_PICTURE): cv.template, + probatio.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -94,11 +94,11 @@ def log_triggered_template_error( ) -TEMPLATE_SENSOR_BASE_SCHEMA = vol.Schema( +TEMPLATE_SENSOR_BASE_SCHEMA = probatio.Schema( { - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + probatio.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + probatio.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, + probatio.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, } ).extend(TEMPLATE_ENTITY_BASE_SCHEMA.schema) diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 9c044c7ea036..0ee784b23ce1 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -4,7 +4,7 @@ from collections.abc import Mapping from enum import Enum from typing import Any, Never -import voluptuous as vol +import probatio type GPSType = tuple[float, float] type ConfigType = dict[str, Any] @@ -13,8 +13,8 @@ type ServiceDataType = dict[str, Any] type StateType = str | int | float | None type TemplateVarsType = Mapping[str, Any] | None type NoEventData = Mapping[str, Never] -type VolSchemaType = vol.Schema | vol.All | vol.Any -type VolDictType = dict[str | vol.Marker, Any] +type VolSchemaType = probatio.Schema | probatio.All | probatio.Any +type VolDictType = dict[str | probatio.Marker, Any] # Custom type for recorder Queries type QueryType = Any diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 10f1697c4eee..11a326064648 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -31,8 +31,8 @@ from awesomeversion import ( AwesomeVersionException, AwesomeVersionStrategy, ) +import probatio from propcache.api import cached_property -import voluptuous as vol from . import generated from .const import Platform @@ -379,7 +379,7 @@ async def async_get_config_flows( class ComponentProtocol(Protocol): """Define the format of an integration.""" - CONFIG_SCHEMA: vol.Schema + CONFIG_SCHEMA: probatio.Schema DOMAIN: str async def async_setup_entry( diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index d3e9249a2fa1..5c0e20b4337c 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from numbers import Number from typing import TYPE_CHECKING, Final -import voluptuous as vol +import probatio from homeassistant.const import ( ACCUMULATED_PRECIPITATION, @@ -249,10 +249,10 @@ def _deprecated_unit_system(value: str) -> str: return value -validate_unit_system = vol.All( - vol.Lower, +validate_unit_system = probatio.All( + probatio.Lower, _deprecated_unit_system, - vol.Any(_CONF_UNIT_SYSTEM_METRIC, _CONF_UNIT_SYSTEM_US_CUSTOMARY), + probatio.Any(_CONF_UNIT_SYSTEM_METRIC, _CONF_UNIT_SYSTEM_US_CUSTOMARY), ) METRIC_SYSTEM = UnitSystem(