From 317ed418dd19dc7dcf2906ddaf3ad6b04583bf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 19 Dec 2020 13:46:27 +0200 Subject: [PATCH] Use singleton enum for "not set" sentinels (#41990) * Use singleton enum for "not set" sentinel https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions * Remove unused variable --- homeassistant/components/camera/prefs.py | 7 +- homeassistant/components/cloud/prefs.py | 42 ++++----- homeassistant/components/deconz/__init__.py | 4 +- homeassistant/components/person/__init__.py | 2 - homeassistant/config_entries.py | 23 +++-- homeassistant/core.py | 2 +- homeassistant/helpers/device_registry.py | 97 ++++++++++----------- homeassistant/helpers/entity_registry.py | 67 +++++++------- homeassistant/helpers/typing.py | 10 +++ homeassistant/loader.py | 2 +- homeassistant/requirements.py | 14 +-- 11 files changed, 139 insertions(+), 131 deletions(-) diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index ae182c62dc6..ec35a448407 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -1,11 +1,12 @@ """Preference management for camera component.""" +from homeassistant.helpers.typing import UNDEFINED + from .const import DOMAIN, PREF_PRELOAD_STREAM # mypy: allow-untyped-defs, no-check-untyped-defs STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 -_UNDEF = object() class CameraEntityPreferences: @@ -44,14 +45,14 @@ class CameraPreferences: self._prefs = prefs async def async_update( - self, entity_id, *, preload_stream=_UNDEF, stream_options=_UNDEF + self, entity_id, *, preload_stream=UNDEFINED, stream_options=UNDEFINED ): """Update camera preferences.""" if not self._prefs.get(entity_id): self._prefs[entity_id] = {} for key, value in ((PREF_PRELOAD_STREAM, preload_stream),): - if value is not _UNDEF: + if value is not UNDEFINED: self._prefs[entity_id][key] = value await self._store.async_save(self._prefs) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 0a41f8e2a8f..6e0e78839c1 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -5,6 +5,7 @@ from typing import List, Optional from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.models import User from homeassistant.core import callback +from homeassistant.helpers.typing import UNDEFINED from homeassistant.util.logging import async_create_catching_coro from .const import ( @@ -36,7 +37,6 @@ from .const import ( STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 -_UNDEF = object() class CloudPreferences: @@ -74,18 +74,18 @@ class CloudPreferences: async def async_update( self, *, - google_enabled=_UNDEF, - alexa_enabled=_UNDEF, - remote_enabled=_UNDEF, - google_secure_devices_pin=_UNDEF, - cloudhooks=_UNDEF, - cloud_user=_UNDEF, - google_entity_configs=_UNDEF, - alexa_entity_configs=_UNDEF, - alexa_report_state=_UNDEF, - google_report_state=_UNDEF, - alexa_default_expose=_UNDEF, - google_default_expose=_UNDEF, + google_enabled=UNDEFINED, + alexa_enabled=UNDEFINED, + remote_enabled=UNDEFINED, + google_secure_devices_pin=UNDEFINED, + cloudhooks=UNDEFINED, + cloud_user=UNDEFINED, + google_entity_configs=UNDEFINED, + alexa_entity_configs=UNDEFINED, + alexa_report_state=UNDEFINED, + google_report_state=UNDEFINED, + alexa_default_expose=UNDEFINED, + google_default_expose=UNDEFINED, ): """Update user preferences.""" prefs = {**self._prefs} @@ -104,7 +104,7 @@ class CloudPreferences: (PREF_ALEXA_DEFAULT_EXPOSE, alexa_default_expose), (PREF_GOOGLE_DEFAULT_EXPOSE, google_default_expose), ): - if value is not _UNDEF: + if value is not UNDEFINED: prefs[key] = value if remote_enabled is True and self._has_local_trusted_network: @@ -121,10 +121,10 @@ class CloudPreferences: self, *, entity_id, - override_name=_UNDEF, - disable_2fa=_UNDEF, - aliases=_UNDEF, - should_expose=_UNDEF, + override_name=UNDEFINED, + disable_2fa=UNDEFINED, + aliases=UNDEFINED, + should_expose=UNDEFINED, ): """Update config for a Google entity.""" entities = self.google_entity_configs @@ -137,7 +137,7 @@ class CloudPreferences: (PREF_ALIASES, aliases), (PREF_SHOULD_EXPOSE, should_expose), ): - if value is not _UNDEF: + if value is not UNDEFINED: changes[key] = value if not changes: @@ -149,7 +149,7 @@ class CloudPreferences: await self.async_update(google_entity_configs=updated_entities) async def async_update_alexa_entity_config( - self, *, entity_id, should_expose=_UNDEF + self, *, entity_id, should_expose=UNDEFINED ): """Update config for an Alexa entity.""" entities = self.alexa_entity_configs @@ -157,7 +157,7 @@ class CloudPreferences: changes = {} for key, value in ((PREF_SHOULD_EXPOSE, should_expose),): - if value is not _UNDEF: + if value is not UNDEFINED: changes[key] = value if not changes: diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 507b48da9db..fec7b82e365 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -1,8 +1,8 @@ """Support for deCONZ devices.""" import voluptuous as vol -from homeassistant.config_entries import _UNDEF from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.helpers.typing import UNDEFINED from .config_flow import get_master_gateway from .const import CONF_BRIDGE_ID, CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN @@ -39,7 +39,7 @@ async def async_setup_entry(hass, config_entry): # 0.104 introduced config entry unique id, this makes upgrading possible if config_entry.unique_id is None: - new_data = _UNDEF + new_data = UNDEFINED if CONF_BRIDGE_ID in config_entry.data: new_data = dict(config_entry.data) new_data[CONF_GROUP_ID_BASE] = config_entry.data[CONF_BRIDGE_ID] diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 584ce708d15..d0c0e9eccc8 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -87,8 +87,6 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -_UNDEF = object() - @bind_hass async def async_create_person(hass, name, *, user_id=None, device_trackers=None): diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index c42a53b2dae..601ce1efbfe 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -13,12 +13,12 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry from homeassistant.helpers.event import Event +from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.setup import async_process_deps_reqs, async_setup_component from homeassistant.util.decorator import Registry import homeassistant.util.uuid as uuid_util _LOGGER = logging.getLogger(__name__) -_UNDEF: dict = {} SOURCE_DISCOVERY = "discovery" SOURCE_HASSIO = "hassio" @@ -760,12 +760,11 @@ class ConfigEntries: self, entry: ConfigEntry, *, - # pylint: disable=dangerous-default-value # _UNDEFs not modified - unique_id: Union[str, dict, None] = _UNDEF, - title: Union[str, dict] = _UNDEF, - data: dict = _UNDEF, - options: dict = _UNDEF, - system_options: dict = _UNDEF, + unique_id: Union[str, dict, None, UndefinedType] = UNDEFINED, + title: Union[str, dict, UndefinedType] = UNDEFINED, + data: Union[dict, UndefinedType] = UNDEFINED, + options: Union[dict, UndefinedType] = UNDEFINED, + system_options: Union[dict, UndefinedType] = UNDEFINED, ) -> bool: """Update a config entry. @@ -777,24 +776,24 @@ class ConfigEntries: """ changed = False - if unique_id is not _UNDEF and entry.unique_id != unique_id: + if unique_id is not UNDEFINED and entry.unique_id != unique_id: changed = True entry.unique_id = cast(Optional[str], unique_id) - if title is not _UNDEF and entry.title != title: + if title is not UNDEFINED and entry.title != title: changed = True entry.title = cast(str, title) - if data is not _UNDEF and entry.data != data: # type: ignore + if data is not UNDEFINED and entry.data != data: # type: ignore changed = True entry.data = MappingProxyType(data) - if options is not _UNDEF and entry.options != options: # type: ignore + if options is not UNDEFINED and entry.options != options: # type: ignore changed = True entry.options = MappingProxyType(options) if ( - system_options is not _UNDEF + system_options is not UNDEFINED and entry.system_options.as_dict() != system_options ): changed = True diff --git a/homeassistant/core.py b/homeassistant/core.py index 9eeaf6fccca..01c4047af65 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -89,7 +89,7 @@ block_async_io.enable() fix_threading_exception_logging() T = TypeVar("T") -_UNDEF: dict = {} +_UNDEF: dict = {} # Internal; not helpers.typing.UNDEFINED due to circular dependency # pylint: disable=invalid-name CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) CALLBACK_TYPE = Callable[[], None] diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index cc8f9a17827..6e8c09bbd60 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -11,7 +11,7 @@ import homeassistant.util.uuid as uuid_util from .debounce import Debouncer from .singleton import singleton -from .typing import HomeAssistantType +from .typing import UNDEFINED, HomeAssistantType if TYPE_CHECKING: from . import entity_registry @@ -19,7 +19,6 @@ if TYPE_CHECKING: # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs _LOGGER = logging.getLogger(__name__) -_UNDEF = object() DATA_REGISTRY = "device_registry" EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated" @@ -224,17 +223,17 @@ class DeviceRegistry: config_entry_id, connections=None, identifiers=None, - manufacturer=_UNDEF, - model=_UNDEF, - name=_UNDEF, - default_manufacturer=_UNDEF, - default_model=_UNDEF, - default_name=_UNDEF, - sw_version=_UNDEF, - entry_type=_UNDEF, + manufacturer=UNDEFINED, + model=UNDEFINED, + name=UNDEFINED, + default_manufacturer=UNDEFINED, + default_model=UNDEFINED, + default_name=UNDEFINED, + sw_version=UNDEFINED, + entry_type=UNDEFINED, via_device=None, # To disable a device if it gets created - disabled_by=_UNDEF, + disabled_by=UNDEFINED, ): """Get device. Create if it doesn't exist.""" if not identifiers and not connections: @@ -261,27 +260,27 @@ class DeviceRegistry: ) self._add_device(device) - if default_manufacturer is not _UNDEF and device.manufacturer is None: + if default_manufacturer is not UNDEFINED and device.manufacturer is None: manufacturer = default_manufacturer - if default_model is not _UNDEF and device.model is None: + if default_model is not UNDEFINED and device.model is None: model = default_model - if default_name is not _UNDEF and device.name is None: + if default_name is not UNDEFINED and device.name is None: name = default_name if via_device is not None: via = self.async_get_device({via_device}, set()) - via_device_id = via.id if via else _UNDEF + via_device_id = via.id if via else UNDEFINED else: - via_device_id = _UNDEF + via_device_id = UNDEFINED return self._async_update_device( device.id, add_config_entry_id=config_entry_id, via_device_id=via_device_id, - merge_connections=connections or _UNDEF, - merge_identifiers=identifiers or _UNDEF, + merge_connections=connections or UNDEFINED, + merge_identifiers=identifiers or UNDEFINED, manufacturer=manufacturer, model=model, name=name, @@ -295,16 +294,16 @@ class DeviceRegistry: self, device_id, *, - area_id=_UNDEF, - manufacturer=_UNDEF, - model=_UNDEF, - name=_UNDEF, - name_by_user=_UNDEF, - new_identifiers=_UNDEF, - sw_version=_UNDEF, - via_device_id=_UNDEF, - remove_config_entry_id=_UNDEF, - disabled_by=_UNDEF, + area_id=UNDEFINED, + manufacturer=UNDEFINED, + model=UNDEFINED, + name=UNDEFINED, + name_by_user=UNDEFINED, + new_identifiers=UNDEFINED, + sw_version=UNDEFINED, + via_device_id=UNDEFINED, + remove_config_entry_id=UNDEFINED, + disabled_by=UNDEFINED, ): """Update properties of a device.""" return self._async_update_device( @@ -326,20 +325,20 @@ class DeviceRegistry: self, device_id, *, - add_config_entry_id=_UNDEF, - remove_config_entry_id=_UNDEF, - merge_connections=_UNDEF, - merge_identifiers=_UNDEF, - new_identifiers=_UNDEF, - manufacturer=_UNDEF, - model=_UNDEF, - name=_UNDEF, - sw_version=_UNDEF, - entry_type=_UNDEF, - via_device_id=_UNDEF, - area_id=_UNDEF, - name_by_user=_UNDEF, - disabled_by=_UNDEF, + add_config_entry_id=UNDEFINED, + remove_config_entry_id=UNDEFINED, + merge_connections=UNDEFINED, + merge_identifiers=UNDEFINED, + new_identifiers=UNDEFINED, + manufacturer=UNDEFINED, + model=UNDEFINED, + name=UNDEFINED, + sw_version=UNDEFINED, + entry_type=UNDEFINED, + via_device_id=UNDEFINED, + area_id=UNDEFINED, + name_by_user=UNDEFINED, + disabled_by=UNDEFINED, ): """Update device attributes.""" old = self.devices[device_id] @@ -349,13 +348,13 @@ class DeviceRegistry: config_entries = old.config_entries if ( - add_config_entry_id is not _UNDEF + add_config_entry_id is not UNDEFINED and add_config_entry_id not in old.config_entries ): config_entries = old.config_entries | {add_config_entry_id} if ( - remove_config_entry_id is not _UNDEF + remove_config_entry_id is not UNDEFINED and remove_config_entry_id in config_entries ): if config_entries == {remove_config_entry_id}: @@ -373,10 +372,10 @@ class DeviceRegistry: ): old_value = getattr(old, attr_name) # If not undefined, check if `value` contains new items. - if value is not _UNDEF and not value.issubset(old_value): + if value is not UNDEFINED and not value.issubset(old_value): changes[attr_name] = old_value | value - if new_identifiers is not _UNDEF: + if new_identifiers is not UNDEFINED: changes["identifiers"] = new_identifiers for attr_name, value in ( @@ -388,13 +387,13 @@ class DeviceRegistry: ("via_device_id", via_device_id), ("disabled_by", disabled_by), ): - if value is not _UNDEF and value != getattr(old, attr_name): + if value is not UNDEFINED and value != getattr(old, attr_name): changes[attr_name] = value - if area_id is not _UNDEF and area_id != old.area_id: + if area_id is not UNDEFINED and area_id != old.area_id: changes["area_id"] = area_id - if name_by_user is not _UNDEF and name_by_user != old.name_by_user: + if name_by_user is not UNDEFINED and name_by_user != old.name_by_user: changes["name_by_user"] = name_by_user if old.is_new: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 4582fc5f3b6..44f5c9c56f7 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -39,7 +39,7 @@ from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml from .singleton import singleton -from .typing import HomeAssistantType +from .typing import UNDEFINED, HomeAssistantType if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntry # noqa: F401 @@ -51,7 +51,6 @@ DATA_REGISTRY = "entity_registry" EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated" SAVE_DELAY = 10 _LOGGER = logging.getLogger(__name__) -_UNDEF = object() DISABLED_CONFIG_ENTRY = "config_entry" DISABLED_DEVICE = "device" DISABLED_HASS = "hass" @@ -225,15 +224,15 @@ class EntityRegistry: if entity_id: return self._async_update_entity( # type: ignore entity_id, - config_entry_id=config_entry_id or _UNDEF, - device_id=device_id or _UNDEF, - area_id=area_id or _UNDEF, - capabilities=capabilities or _UNDEF, - supported_features=supported_features or _UNDEF, - device_class=device_class or _UNDEF, - unit_of_measurement=unit_of_measurement or _UNDEF, - original_name=original_name or _UNDEF, - original_icon=original_icon or _UNDEF, + config_entry_id=config_entry_id or UNDEFINED, + device_id=device_id or UNDEFINED, + area_id=area_id or UNDEFINED, + capabilities=capabilities or UNDEFINED, + supported_features=supported_features or UNDEFINED, + device_class=device_class or UNDEFINED, + unit_of_measurement=unit_of_measurement or UNDEFINED, + original_name=original_name or UNDEFINED, + original_icon=original_icon or UNDEFINED, # When we changed our slugify algorithm, we invalidated some # stored entity IDs with either a __ or ending in _. # Fix introduced in 0.86 (Jan 23, 2019). Next line can be @@ -333,12 +332,12 @@ class EntityRegistry: self, entity_id, *, - name=_UNDEF, - icon=_UNDEF, - area_id=_UNDEF, - new_entity_id=_UNDEF, - new_unique_id=_UNDEF, - disabled_by=_UNDEF, + name=UNDEFINED, + icon=UNDEFINED, + area_id=UNDEFINED, + new_entity_id=UNDEFINED, + new_unique_id=UNDEFINED, + disabled_by=UNDEFINED, ): """Update properties of an entity.""" return cast( # cast until we have _async_update_entity type hinted @@ -359,20 +358,20 @@ class EntityRegistry: self, entity_id, *, - name=_UNDEF, - icon=_UNDEF, - config_entry_id=_UNDEF, - new_entity_id=_UNDEF, - device_id=_UNDEF, - area_id=_UNDEF, - new_unique_id=_UNDEF, - disabled_by=_UNDEF, - capabilities=_UNDEF, - supported_features=_UNDEF, - device_class=_UNDEF, - unit_of_measurement=_UNDEF, - original_name=_UNDEF, - original_icon=_UNDEF, + name=UNDEFINED, + icon=UNDEFINED, + config_entry_id=UNDEFINED, + new_entity_id=UNDEFINED, + device_id=UNDEFINED, + area_id=UNDEFINED, + new_unique_id=UNDEFINED, + disabled_by=UNDEFINED, + capabilities=UNDEFINED, + supported_features=UNDEFINED, + device_class=UNDEFINED, + unit_of_measurement=UNDEFINED, + original_name=UNDEFINED, + original_icon=UNDEFINED, ): """Private facing update properties method.""" old = self.entities[entity_id] @@ -393,10 +392,10 @@ class EntityRegistry: ("original_name", original_name), ("original_icon", original_icon), ): - if value is not _UNDEF and value != getattr(old, attr_name): + if value is not UNDEFINED and value != getattr(old, attr_name): changes[attr_name] = value - if new_entity_id is not _UNDEF and new_entity_id != old.entity_id: + if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity is already registered") @@ -409,7 +408,7 @@ class EntityRegistry: self.entities.pop(entity_id) entity_id = changes["entity_id"] = new_entity_id - if new_unique_id is not _UNDEF: + if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( old.domain, old.platform, new_unique_id ) diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index bed0d2b8d17..279bc0f686f 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -1,4 +1,5 @@ """Typing Helpers for Home Assistant.""" +from enum import Enum from typing import Any, Dict, Mapping, Optional, Tuple, Union import homeassistant.core @@ -16,3 +17,12 @@ TemplateVarsType = Optional[Mapping[str, Any]] # Custom type for recorder Queries QueryType = Any + + +class UndefinedType(Enum): + """Singleton type for use with not set sentinel values.""" + + _singleton = 0 + + +UNDEFINED = UndefinedType._singleton # pylint: disable=protected-access diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 6dabfdf0447..ba29ff4a8da 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -48,7 +48,7 @@ CUSTOM_WARNING = ( "cause stability problems, be sure to disable it if you " "experience issues with Home Assistant." ) -_UNDEF = object() +_UNDEF = object() # Internal; not helpers.typing.UNDEFINED due to circular dependency MAX_LOAD_CONCURRENTLY = 4 diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index b3af06ad070..cebfd95591f 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -5,6 +5,7 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.loader import Integration, IntegrationNotFound, async_get_integration import homeassistant.util.package as pkg_util @@ -17,7 +18,6 @@ DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { "ssdp": ("ssdp",), "zeroconf": ("zeroconf", "homekit"), } -_UNDEF = object() class RequirementsNotFound(HomeAssistantError): @@ -53,19 +53,21 @@ async def async_get_integration_with_requirements( if cache is None: cache = hass.data[DATA_INTEGRATIONS_WITH_REQS] = {} - int_or_evt: Union[Integration, asyncio.Event, None] = cache.get(domain, _UNDEF) + int_or_evt: Union[Integration, asyncio.Event, None, UndefinedType] = cache.get( + domain, UNDEFINED + ) if isinstance(int_or_evt, asyncio.Event): await int_or_evt.wait() - int_or_evt = cache.get(domain, _UNDEF) + int_or_evt = cache.get(domain, UNDEFINED) - # When we have waited and it's _UNDEF, it doesn't exist + # When we have waited and it's UNDEFINED, it doesn't exist # We don't cache that it doesn't exist, or else people can't fix it # and then restart, because their config will never be valid. - if int_or_evt is _UNDEF: + if int_or_evt is UNDEFINED: raise IntegrationNotFound(domain) - if int_or_evt is not _UNDEF: + if int_or_evt is not UNDEFINED: return cast(Integration, int_or_evt) event = cache[domain] = asyncio.Event()