def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None: """Backwards compatible current_task.""" report( "called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.2; use asyncio.current_task instead", error_if_core=False, ) return asyncio.current_task()
async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str) -> ModuleType: """Load device automation platform for integration. Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation. """ if isinstance(automation_type, str): report( "uses str for async_get_device_automation_platform automation_type. This " "is deprecated and will stop working in Home Assistant 2022.4, it should " "be updated to use DeviceAutomationType instead", error_if_core=False, ) automation_type = DeviceAutomationType[automation_type.upper()] platform_name = automation_type.value.section try: integration = await async_get_integration_with_requirements( hass, domain) platform = integration.get_platform(platform_name) except IntegrationNotFound as err: raise InvalidDeviceAutomationConfig( f"Integration '{domain}' not found") from err except ImportError as err: raise InvalidDeviceAutomationConfig( f"Integration '{domain}' does not support device automation " f"{automation_type.name.lower()}s") from err return platform
def new_zeroconf_new(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> HaZeroconf: report( "attempted to create another Zeroconf instance. Please use the shared Zeroconf via await homeassistant.components.zeroconf.async_get_instance(hass)", exclude_integrations={"zeroconf"}, error_if_core=False, ) return hass_zc
def _do_get_db_connection_protected(self) -> Any: report( "accesses the database without the database executor; " f"{ADVISE_MSG} " "for faster database operations", exclude_integrations={"recorder"}, error_if_core=False, ) return super(NullPool, self)._create_connection()
async def test_report_missing_integration_frame(caplog): """Test reporting when no integration is detected.""" what = "teststring" with patch( "homeassistant.helpers.frame.get_integration_frame", side_effect=frame.MissingIntegrationFrame, ): frame.report(what, error_if_core=False) assert what in caplog.text assert caplog.text.count(what) == 1
def _do_get(self) -> Any: if self.recorder_or_dbworker: return super()._do_get() report( "accesses the database without the database executor; " "Use homeassistant.components.recorder.get_instance(hass).async_add_executor_job() " "for faster database operations", exclude_integrations={"recorder"}, error_if_core=False, ) return super( # pylint: disable=bad-super-call NullPool, self)._create_connection()
def __getitem__(self, name: str) -> Any: """ Allow property access by name for compatibility reason. Deprecated, and will be removed in version 2022.6. """ report( f"accessed discovery_info['{name}'] instead of discovery_info.{name}; " "this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) return getattr(self, name)
def __getitem__(self, name: str) -> Any: """ Enable method for compatibility reason. Deprecated, and will be removed in version 2022.6. """ if not self._warning_logged: report( f"accessed discovery_info['{name}'] instead of discovery_info.{name}; this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) self._warning_logged = True return getattr(self, name)
def get(self, name: str, default: Any = None) -> Any: """ Enable method for compatibility reason. Deprecated, and will be removed in version 2022.6. """ report( f"accessed discovery_info.get('{name}') instead of discovery_info.{name}; " "this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) if hasattr(self, name): return getattr(self, name) return default
def get(self, name: str, default: Any = None) -> Any: """ Enable method for compatibility reason. Deprecated, and will be removed in version 2022.6. """ if not self._warning_logged: report( f"accessed discovery_info.get('{name}') instead of discovery_info.{name}; this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) self._warning_logged = True if hasattr(self, name): return getattr(self, name) return self.upnp.get(name, self.ssdp_headers.get(name, default))
async def async_get_device_automations( hass: HomeAssistant, automation_type: DeviceAutomationType | str, device_ids: Iterable[str] | None = None, ) -> Mapping[str, Any]: """Return all the device automations for a type optionally limited to specific device ids.""" if isinstance(automation_type, str): report( "uses str for async_get_device_automations automation_type. This is " "deprecated and will stop working in Home Assistant 2022.4, it should be " "updated to use DeviceAutomationType instead", error_if_core=False, ) automation_type = DeviceAutomationType[automation_type.upper()] return await _async_get_device_automations(hass, automation_type, device_ids)
def __contains__(self, name: str) -> bool: """ Enable method for compatibility reason. Deprecated, and will be removed in version 2022.6. """ report( f"accessed discovery_info.__contains__('{name}') " f"instead of discovery_info.upnp.__contains__('{name}') " f"or discovery_info.ssdp_headers.__contains__('{name}'); " "this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) if hasattr(self, name): return getattr(self, name) is not None return name in self.upnp or name in self.ssdp_headers
async def test_prevent_flooding(caplog): """Test to ensure a report is only written once to the log.""" what = "accessed hi instead of hello" key = "/home/paulus/homeassistant/components/hue/light.py:23" frame.report(what, error_if_core=False) assert what in caplog.text assert key in frame._REPORTED_INTEGRATIONS assert len(frame._REPORTED_INTEGRATIONS) == 1 caplog.clear() frame.report(what, error_if_core=False) assert what not in caplog.text assert key in frame._REPORTED_INTEGRATIONS assert len(frame._REPORTED_INTEGRATIONS) == 1
def timeout( delay: float | None, loop: asyncio.AbstractEventLoop | None = None ) -> async_timeout.Timeout: """Backwards compatible timeout context manager that warns with loop usage.""" if loop is None: loop = asyncio.get_running_loop() else: report( "called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.2", error_if_core=False, ) if delay is not None: deadline: float | None = loop.time() + delay else: deadline = None return async_timeout.Timeout(deadline, loop)
def __init__(self, address_or_ble_device: str | BLEDevice, *args: Any, **kwargs: Any) -> None: """Initialize the BleakClient.""" if isinstance(address_or_ble_device, BLEDevice): super().__init__(address_or_ble_device, *args, **kwargs) return report( "attempted to call BleakClient with an address instead of a BLEDevice", exclude_integrations={"bluetooth"}, error_if_core=False, ) assert MANAGER is not None ble_device = MANAGER.async_ble_device_from_address( address_or_ble_device, True) if ble_device is None: raise BleakError( f"No device found for address {address_or_ble_device}") super().__init__(ble_device, *args, **kwargs)
def extract_entities( hass: HomeAssistantType, template: Optional[str], variables: TemplateVarsType = None, ) -> Union[str, List[str]]: """Extract all entities for state_changed listener from template string.""" report( "called template.extract_entities. Please use event.async_track_template_result instead as it can accurately handle watching entities" ) if template is None or not is_template_string(template): return [] if _RE_NONE_ENTITIES.search(template): return MATCH_ALL extraction_final = [] for result in _RE_GET_ENTITIES.finditer(template): if (result.group("entity_id") == "trigger.entity_id" and variables and "trigger" in variables and "entity_id" in variables["trigger"]): extraction_final.append(variables["trigger"]["entity_id"]) elif result.group("entity_id"): if result.group("func") == "expand": for entity in expand(hass, result.group("entity_id")): extraction_final.append(entity.entity_id) extraction_final.append(result.group("entity_id")) elif result.group("domain_inner") or result.group("domain_outer"): extraction_final.extend( hass.states.async_entity_ids( result.group("domain_inner") or result.group("domain_outer"))) if (variables and result.group("variable") in variables and isinstance(variables[result.group("variable")], str) and valid_entity_id(variables[result.group("variable")])): extraction_final.append(variables[result.group("variable")]) if extraction_final: return list(set(extraction_final)) return MATCH_ALL
async def async_resolve_media( hass: HomeAssistant, media_content_id: str, target_media_player: str | None | UndefinedType = UNDEFINED, ) -> PlayMedia: """Get info to play media.""" if DOMAIN not in hass.data: raise Unresolvable("Media Source not loaded") if target_media_player is UNDEFINED: report("calls media_source.async_resolve_media without passing an entity_id") target_media_player = None try: item = _get_media_item(hass, media_content_id, target_media_player) except ValueError as err: raise Unresolvable(str(err)) from err return await item.async_resolve()
def __getitem__(self, name: str) -> Any: """ Allow property access by name for compatibility reason. Deprecated, and will be removed in version 2022.6. """ if not self._warning_logged: report( f"accessed discovery_info['{name}'] instead of discovery_info.{name}; this will fail in version 2022.6", exclude_integrations={DOMAIN}, error_if_core=False, ) self._warning_logged = True # Use a property if it is available, fallback to upnp data if hasattr(self, name): return getattr(self, name) if name in self.ssdp_headers and name not in self.upnp: return self.ssdp_headers.get(name) return self.upnp[name]
def async_get_or_create( self, domain: str, platform: str, unique_id: str, *, # To influence entity ID generation known_object_ids: Iterable[str] | None = None, suggested_object_id: str | None = None, # To disable an entity if it gets created disabled_by: RegistryEntryDisabler | None = None, # Data that we want entry to have area_id: str | None = None, capabilities: Mapping[str, Any] | None = None, config_entry: ConfigEntry | None = None, device_id: str | None = None, entity_category: str | None = None, original_device_class: str | None = None, original_icon: str | None = None, original_name: str | None = None, supported_features: int | None = None, unit_of_measurement: str | None = None, ) -> RegistryEntry: """Get entity. Create if it doesn't exist.""" config_entry_id = None if config_entry: config_entry_id = config_entry.entry_id entity_id = self.async_get_entity_id(domain, platform, unique_id) if entity_id: return self._async_update_entity( entity_id, area_id=area_id or UNDEFINED, capabilities=capabilities or UNDEFINED, config_entry_id=config_entry_id or UNDEFINED, device_id=device_id or UNDEFINED, entity_category=entity_category or UNDEFINED, original_device_class=original_device_class or UNDEFINED, original_icon=original_icon or UNDEFINED, original_name=original_name or UNDEFINED, supported_features=supported_features or UNDEFINED, unit_of_measurement=unit_of_measurement 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 # removed when we release 1.0 or in 2020. new_entity_id=".".join( slugify(part) for part in entity_id.split(".", 1)), ) entity_id = self.async_generate_entity_id( domain, suggested_object_id or f"{platform}_{unique_id}", known_object_ids) if isinstance(disabled_by, str) and not isinstance( disabled_by, RegistryEntryDisabler): report( # type: ignore[unreachable] "uses str for entity registry disabled_by. This is deprecated and will " "stop working in Home Assistant 2022.3, it should be updated to use " "RegistryEntryDisabler instead", error_if_core=False, ) disabled_by = RegistryEntryDisabler(disabled_by) elif (disabled_by is None and config_entry and config_entry.pref_disable_new_entities): disabled_by = RegistryEntryDisabler.INTEGRATION entry = RegistryEntry( area_id=area_id, capabilities=capabilities, config_entry_id=config_entry_id, device_id=device_id, disabled_by=disabled_by, entity_category=entity_category, entity_id=entity_id, original_device_class=original_device_class, original_icon=original_icon, original_name=original_name, platform=platform, supported_features=supported_features or 0, unique_id=unique_id, unit_of_measurement=unit_of_measurement, ) self.entities[entity_id] = entry _LOGGER.info("Registered new %s.%s entity: %s", domain, platform, entity_id) self.async_schedule_save() self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, { "action": "create", "entity_id": entity_id }) return entry
def _async_update_device( self, device_id: str, *, add_config_entry_id: str | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED, configuration_url: str | None | UndefinedType = UNDEFINED, disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED, entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED, manufacturer: str | None | UndefinedType = UNDEFINED, merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, model: str | None | UndefinedType = UNDEFINED, name_by_user: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, remove_config_entry_id: str | UndefinedType = UNDEFINED, suggested_area: str | None | UndefinedType = UNDEFINED, sw_version: str | None | UndefinedType = UNDEFINED, via_device_id: str | None | UndefinedType = UNDEFINED, ) -> DeviceEntry | None: """Update device attributes.""" old = self.devices[device_id] changes: dict[str, Any] = {} config_entries = old.config_entries if isinstance(disabled_by, str) and not isinstance( disabled_by, DeviceEntryDisabler): report( # type: ignore[unreachable] "uses str for device registry disabled_by. This is deprecated and will " "stop working in Home Assistant 2022.3, it should be updated to use " "DeviceEntryDisabler instead", error_if_core=False, ) disabled_by = DeviceEntryDisabler(disabled_by) if (suggested_area not in (UNDEFINED, None, "") and area_id is UNDEFINED and old.area_id is None): area = self.hass.helpers.area_registry.async_get( self.hass).async_get_or_create(suggested_area) area_id = area.id if (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 UNDEFINED and remove_config_entry_id in config_entries): if config_entries == {remove_config_entry_id}: self.async_remove_device(device_id) return None config_entries = config_entries - {remove_config_entry_id} if config_entries != old.config_entries: changes["config_entries"] = config_entries for attr_name, setvalue in ( ("connections", merge_connections), ("identifiers", merge_identifiers), ): old_value = getattr(old, attr_name) # If not undefined, check if `value` contains new items. if setvalue is not UNDEFINED and not setvalue.issubset(old_value): changes[attr_name] = old_value | setvalue if new_identifiers is not UNDEFINED: changes["identifiers"] = new_identifiers for attr_name, value in ( ("configuration_url", configuration_url), ("disabled_by", disabled_by), ("entry_type", entry_type), ("manufacturer", manufacturer), ("model", model), ("name", name), ("suggested_area", suggested_area), ("sw_version", sw_version), ("via_device_id", via_device_id), ): if value is not UNDEFINED and value != getattr(old, attr_name): changes[attr_name] = value if area_id is not UNDEFINED and area_id != old.area_id: changes["area_id"] = area_id 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: changes["is_new"] = False if not changes: return old new = attr.evolve(old, **changes) self._update_device(old, new) self.async_schedule_save() self.hass.bus.async_fire( EVENT_DEVICE_REGISTRY_UPDATED, { "action": "create" if "is_new" in changes else "update", "device_id": new.id, }, ) return new
def async_get_or_create( self, *, config_entry_id: str, configuration_url: str | None | UndefinedType = UNDEFINED, connections: set[tuple[str, str]] | None = None, default_manufacturer: str | None | UndefinedType = UNDEFINED, default_model: str | None | UndefinedType = UNDEFINED, default_name: str | None | UndefinedType = UNDEFINED, # To disable a device if it gets created disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED, entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED, identifiers: set[tuple[str, str]] | None = None, manufacturer: str | None | UndefinedType = UNDEFINED, model: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, suggested_area: str | None | UndefinedType = UNDEFINED, sw_version: str | None | UndefinedType = UNDEFINED, via_device: tuple[str, str] | None = None, ) -> DeviceEntry: """Get device. Create if it doesn't exist.""" if not identifiers and not connections: raise RequiredParameterMissing(["identifiers", "connections"]) if identifiers is None: identifiers = set() if connections is None: connections = set() else: connections = _normalize_connections(connections) device = self.async_get_device(identifiers, connections) if device is None: deleted_device = self._async_get_deleted_device( identifiers, connections) if deleted_device is None: device = DeviceEntry(is_new=True) else: self._remove_device(deleted_device) device = deleted_device.to_device_entry( config_entry_id, connections, identifiers) self._add_device(device) if default_manufacturer is not UNDEFINED and device.manufacturer is None: manufacturer = default_manufacturer if default_model is not UNDEFINED and device.model is None: model = default_model 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}) via_device_id: str | UndefinedType = via.id if via else UNDEFINED else: via_device_id = UNDEFINED if isinstance(entry_type, str) and not isinstance(entry_type, DeviceEntryType): report( # type: ignore[unreachable] "uses str for device registry entry_type. This is deprecated and will " "stop working in Home Assistant 2022.3, it should be updated to use " "DeviceEntryType instead", error_if_core=False, ) entry_type = DeviceEntryType(entry_type) device = self._async_update_device( device.id, add_config_entry_id=config_entry_id, configuration_url=configuration_url, disabled_by=disabled_by, entry_type=entry_type, manufacturer=manufacturer, merge_connections=connections or UNDEFINED, merge_identifiers=identifiers or UNDEFINED, model=model, name=name, suggested_area=suggested_area, sw_version=sw_version, via_device_id=via_device_id, ) # This is safe because _async_update_device will always return a device # in this use case. assert device return device
def _async_update_entity( self, entity_id: str, *, area_id: str | None | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, device_id: str | None | UndefinedType = UNDEFINED, disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, entity_category: str | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, original_device_class: str | None | UndefinedType = UNDEFINED, original_icon: str | None | UndefinedType = UNDEFINED, original_name: str | None | UndefinedType = UNDEFINED, supported_features: int | UndefinedType = UNDEFINED, unit_of_measurement: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] new_values = {} # Dict with new key/value pairs old_values = {} # Dict with old key/value pairs if isinstance(disabled_by, str) and not isinstance( disabled_by, RegistryEntryDisabler): report( # type: ignore[unreachable] "uses str for entity registry disabled_by. This is deprecated and will " "stop working in Home Assistant 2022.3, it should be updated to use " "RegistryEntryDisabler instead", error_if_core=False, ) disabled_by = RegistryEntryDisabler(disabled_by) for attr_name, value in ( ("area_id", area_id), ("capabilities", capabilities), ("config_entry_id", config_entry_id), ("device_class", device_class), ("device_id", device_id), ("disabled_by", disabled_by), ("entity_category", entity_category), ("icon", icon), ("name", name), ("original_device_class", original_device_class), ("original_icon", original_icon), ("original_name", original_name), ("supported_features", supported_features), ("unit_of_measurement", unit_of_measurement), ): if value is not UNDEFINED and value != getattr(old, attr_name): new_values[attr_name] = value old_values[attr_name] = getattr(old, attr_name) 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 with this ID is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id( entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = new_values["entity_id"] = new_entity_id old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( old.domain, old.platform, new_unique_id) if conflict_entity_id: raise ValueError( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'") new_values["unique_id"] = new_unique_id old_values["unique_id"] = old.unique_id if not new_values: return old new = self.entities[entity_id] = attr.evolve(old, **new_values) self.async_schedule_save() data = { "action": "update", "entity_id": entity_id, "changes": old_values } if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new