async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the xbox component.""" hass.data[DOMAIN] = {} if DOMAIN not in config: return True await application_credentials.async_import_client_credential( hass, DOMAIN, application_credentials.ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET]), ) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.9.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) _LOGGER.warning( "Configuration of Xbox integration in YAML is deprecated and " "will be removed in Home Assistant 2022.9.; Your existing configuration " "(including OAuth Application Credentials) has been imported into " "the UI automatically and can be safely removed from your " "configuration.yaml file") return True
async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Open Exchange Rates sensor.""" async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.11.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config, ) ) LOGGER.warning( "Configuration of Open Exchange Rates integration in YAML is deprecated and " "will be removed in Home Assistant 2022.11.; Your existing configuration " "has been imported into the UI automatically and can be safely removed from" " your configuration.yaml file" )
async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up our socket to the AVR.""" async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.10.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) _LOGGER.warning( "Configuration of the Anthem A/V Receivers integration in YAML is " "deprecated and will be removed in Home Assistant 2022.10; Your " "existing configuration has been imported into the UI automatically " "and can be safely removed from your configuration.yaml file") await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config, )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Migrate from YAML to ConfigEntry.""" if DOMAIN not in config: return True hass.data[DOMAIN] = {} if not hass.config_entries.async_entries(DOMAIN): new_conf = {} new_conf[CONF_USERNAME] = config[DOMAIN][CONF_USERNAME] new_conf[CONF_PASSWORD] = config[DOMAIN][CONF_PASSWORD] new_conf[CONF_REGION] = config[DOMAIN].get(CONF_REGION) new_conf[CONF_SCANDINAVIAN_MILES] = config[DOMAIN][ CONF_SCANDINAVIAN_MILES] new_conf[CONF_MUTABLE] = config[DOMAIN][CONF_MUTABLE] hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=new_conf)) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version=None, is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) return True
def validator(config: ConfigType) -> ConfigType: """Return a validator.""" nonlocal warned if domain in warned: return config _LOGGER.warning( "Manually configured MQTT %s(s) found under platform key '%s', " "please move to the mqtt integration key, see " "https://www.home-assistant.io/integrations/%s.mqtt/#new_format", domain, domain, domain, ) warned.add(domain) # Register a repair async_create_issue( async_get_hass(), DOMAIN, f"deprecated_yaml_{domain}", breaks_in_ha_version="2022.12.0", # Warning first added in 2022.6.0 is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", translation_placeholders={ "more_info_url": f"https://www.home-assistant.io/integrations/{domain}.mqtt/#new_format", "platform": domain, }, ) return config
async def async_get_service( hass: HomeAssistant, config: ConfigType, discovery_info: DiscoveryInfoType | None = None, ) -> PushoverNotificationService | None: """Get the Pushover notification service.""" if discovery_info is None: async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.11.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config, )) return None pushover_api: PushoverAPI = hass.data[DOMAIN][discovery_info["entry_id"]] return PushoverNotificationService(hass, pushover_api, discovery_info[CONF_USER_KEY])
def async_log_deprecated_service_call( hass: HomeAssistant, call: ServiceCall, alternate_service: str, alternate_target: str, breaks_in_ha_version: str, ) -> None: """Log a warning about a deprecated service call.""" deprecated_service = f"{call.domain}.{call.service}" async_create_issue( hass, DOMAIN, f"deprecated_service_{deprecated_service}", breaks_in_ha_version=breaks_in_ha_version, is_fixable=True, is_persistent=True, severity=IssueSeverity.WARNING, translation_key="deprecated_service", translation_placeholders={ "alternate_service": alternate_service, "alternate_target": alternate_target, "deprecated_service": deprecated_service, }, ) LOGGER.warning( ('The "%s" service is deprecated and will be removed in %s; use the "%s" ' 'service and pass it a target entity ID of "%s"'), deprecated_service, breaks_in_ha_version, alternate_service, alternate_target, )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Google component.""" if DOMAIN not in config: return True conf = config.get(DOMAIN, {}) hass.data[DOMAIN] = {DATA_CONFIG: conf} if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf: await async_import_client_credential( hass, DOMAIN, ClientCredential( conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], ), DEVICE_AUTH_IMPL, ) # Import credentials from the old token file into the new way as # a ConfigEntry managed by home assistant. storage = Storage(hass.config.path(TOKEN_FILE)) creds = await hass.async_add_executor_job(storage.get) if creds and get_feature_access(hass).scope in creds.scopes: _LOGGER.debug("Importing configuration entry with credentials") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={ "creds": creds, }, )) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.9.0", # Warning first added in 2022.6.0 is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) if conf.get(CONF_TRACK_NEW) is False: # The track_new as False would previously result in new entries # in google_calendars.yaml with track set to False which is # handled at calendar entity creation time. async_create_issue( hass, DOMAIN, "removed_track_new_yaml", breaks_in_ha_version="2022.6.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="removed_track_new_yaml", ) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Ambee integration.""" async_create_issue( hass, DOMAIN, "pending_removal", breaks_in_ha_version="2022.10.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="pending_removal", ) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Flu Near You as config entry.""" async_create_issue( hass, DOMAIN, "integration_removal", is_fixable=True, severity=IssueSeverity.ERROR, translation_key="integration_removal", ) websession = aiohttp_client.async_get_clientsession(hass) client = Client(session=websession) latitude = entry.data.get(CONF_LATITUDE, hass.config.latitude) longitude = entry.data.get(CONF_LONGITUDE, hass.config.longitude) async def async_update(api_category: str) -> dict[str, Any]: """Get updated date from the API based on category.""" try: if api_category == CATEGORY_CDC_REPORT: data = await client.cdc_reports.status_by_coordinates( latitude, longitude) else: data = await client.user_reports.status_by_coordinates( latitude, longitude) except FluNearYouError as err: raise UpdateFailed(err) from err return data coordinators = {} data_init_tasks = [] for api_category in (CATEGORY_CDC_REPORT, CATEGORY_USER_REPORT): coordinator = coordinators[api_category] = DataUpdateCoordinator( hass, LOGGER, name=f"{api_category} ({latitude}, {longitude})", update_interval=DEFAULT_UPDATE_INTERVAL, update_method=partial(async_update, api_category), ) data_init_tasks.append(coordinator.async_config_entry_first_refresh()) await asyncio.gather(*data_init_tasks) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinators await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Spotify integration.""" if DOMAIN in config: async_create_issue( hass, DOMAIN, "removed_yaml", breaks_in_ha_version="2022.8.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="removed_yaml", ) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the LaMetric integration.""" hass.data[DOMAIN] = {"hass_config": config} if DOMAIN in config: async_create_issue( hass, DOMAIN, "manual_migration", breaks_in_ha_version="2022.9.0", is_fixable=False, severity=IssueSeverity.ERROR, translation_key="manual_migration", ) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the SkyBell component.""" hass.data.setdefault(DOMAIN, {}) if DOMAIN in config: async_create_issue( hass, DOMAIN, "removed_yaml", breaks_in_ha_version="2022.9.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="removed_yaml", ) return True
async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the MiFlora sensor.""" async_create_issue( hass, "miflora", "replaced", breaks_in_ha_version="2022.8.0", is_fixable=False, severity=IssueSeverity.ERROR, translation_key="replaced", learn_more_url="https://www.home-assistant.io/integrations/xiaomi_ble/", )
async def async_get_service( hass: HomeAssistant, config: ConfigType, discovery_info: DiscoveryInfoType | None = None, ) -> SimplePushNotificationService | None: """Get the Simplepush notification service.""" if discovery_info is None: async_create_issue( hass, DOMAIN, "removed_yaml", breaks_in_ha_version="2022.9.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="removed_yaml", ) return None return SimplePushNotificationService(discovery_info)
async def request_update(call: ServiceCall) -> None: """Request update.""" async_create_issue( self.hass, DOMAIN, "deprecated_service", breaks_in_ha_version="2022.11.0", is_fixable=True, is_persistent=True, severity=IssueSeverity.WARNING, translation_key="deprecated_service", ) _LOGGER.warning( ( 'The "%s" service is deprecated and will be removed in "2022.11.0"; ' 'use the "homeassistant.update_entity" service and pass it a target Speedtest entity_id' ), SPEED_TEST_SERVICE, ) await self.async_request_refresh()
async def test_create_issue_invalid_version(hass: HomeAssistant, hass_ws_client, ha_version) -> None: """Test creating an issue with invalid breaks in version.""" assert await async_setup_component(hass, DOMAIN, {}) client = await hass_ws_client(hass) issue = { "breaks_in_ha_version": ha_version, "domain": "test", "issue_id": "issue_1", "is_fixable": True, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": { "abc": "123" }, } with pytest.raises(Exception): async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []}
async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Radio Thermostat.""" async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.9.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) _LOGGER.warning( "Configuration of the Radio Thermostat climate platform in YAML is deprecated and " "will be removed in Home Assistant 2022.9; Your existing configuration " "has been imported into the UI automatically and can be safely removed " "from your configuration.yaml file") hosts: list[str] = [] if CONF_HOST in config: hosts = config[CONF_HOST] else: hosts.append(await hass.async_add_executor_job( radiotherm.discover.discover_address)) if not hosts: _LOGGER.error("No Radiotherm Thermostats detected") return for host in hosts: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_HOST: host}, ))
async def create_issues(hass, ws_client, issues=None): """Create issues.""" def api_issue(issue): excluded_keys = ("data",) return dict( {key: issue[key] for key in issue if key not in excluded_keys}, created=ANY, dismissed_version=None, ignored=False, issue_domain=None, ) if issues is None: issues = DEFAULT_ISSUES for issue in issues: issue_registry.async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], data=issue.get("data"), is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await ws_client.receive_json() assert msg["success"] assert msg["result"] == {"issues": [api_issue(issue) for issue in issues]} return issues
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the IP Webcam component.""" if DOMAIN not in config: return True async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.11.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) for entry in config[DOMAIN]: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=entry ) ) return True
async def async_update_alerts() -> None: nonlocal last_alerts active_alerts: dict[str, str | None] = {} for issue_id, alert in coordinator.data.items(): # Skip creation if already created and not updated since then if issue_id in last_alerts and alert.date_updated == last_alerts[ issue_id]: active_alerts[issue_id] = alert.date_updated continue # Fetch alert to get title + description try: response = await async_get_clientsession(hass).get( f"https://alerts.home-assistant.io/alerts/{alert.filename}", timeout=aiohttp.ClientTimeout(total=10), ) except asyncio.TimeoutError: _LOGGER.warning("Error fetching %s: timeout", alert.filename) continue alert_content = await response.text() alert_parts = alert_content.split("---") if len(alert_parts) != 3: _LOGGER.warning("Error parsing %s: unexpected metadata format", alert.filename) continue try: alert_info = parse_yaml(alert_parts[1]) except ValueError as err: _LOGGER.warning("Error parsing %s metadata: %s", alert.filename, err) continue if not isinstance(alert_info, dict) or "title" not in alert_info: _LOGGER.warning("Error in %s metadata: title not found", alert.filename) continue alert_title = alert_info["title"] alert_content = alert_parts[2].strip() async_create_issue( hass, DOMAIN, issue_id, is_fixable=False, issue_domain=alert.integration, severity=IssueSeverity.WARNING, translation_key="alert", translation_placeholders={ "title": alert_title, "description": alert_content, }, ) active_alerts[issue_id] = alert.date_updated inactive_alerts = last_alerts.keys() - active_alerts.keys() for issue_id in inactive_alerts: async_delete_issue(hass, DOMAIN, issue_id) last_alerts = active_alerts
async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None: """Attempt to import configuration.yaml settings.""" config = hass.data[DOMAIN][DATA_NEST_CONFIG] new_data = { CONF_PROJECT_ID: config[CONF_PROJECT_ID], **entry.data, } if CONF_SUBSCRIBER_ID not in entry.data: if CONF_SUBSCRIBER_ID not in config: raise ValueError("Configuration option 'subscriber_id' missing") new_data.update({ CONF_SUBSCRIBER_ID: config[CONF_SUBSCRIBER_ID], CONF_SUBSCRIBER_ID_IMPORTED: True, # Don't delete user managed subscriber }) hass.config_entries.async_update_entry(entry, data=new_data, unique_id=new_data[CONF_PROJECT_ID]) if entry.data["auth_implementation"] == INSTALLED_AUTH_DOMAIN: # App Auth credentials have been deprecated and must be re-created # by the user in the config flow async_create_issue( hass, DOMAIN, "removed_app_auth", is_fixable=False, severity=IssueSeverity.ERROR, translation_key="removed_app_auth", translation_placeholders={ "more_info_url": "https://www.home-assistant.io/more-info/nest-auth-deprecation", "documentation_url": "https://www.home-assistant.io/integrations/nest/", }, ) raise ConfigEntryAuthFailed( "Google has deprecated App Auth credentials, and the integration " "must be reconfigured in the UI to restore access to Nest Devices." ) if entry.data["auth_implementation"] == WEB_AUTH_DOMAIN: await async_import_client_credential( hass, DOMAIN, ClientCredential( config[CONF_CLIENT_ID], config[CONF_CLIENT_SECRET], ), WEB_AUTH_DOMAIN, ) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.10.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", )
async def test_load_issues(hass: HomeAssistant) -> None: """Make sure that we can load/save data correctly.""" issues = [ { "breaks_in_ha_version": "2022.9", "domain": "test", "issue_id": "issue_1", "is_fixable": True, "is_persistent": False, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": { "abc": "123" }, }, { "breaks_in_ha_version": "2022.8", "domain": "test", "issue_id": "issue_2", "is_fixable": True, "is_persistent": False, "learn_more_url": "https://theuselessweb.com/abc", "severity": "other", "translation_key": "even_worse", "translation_placeholders": { "def": "456" }, }, { "breaks_in_ha_version": "2022.7", "domain": "test", "issue_id": "issue_3", "is_fixable": True, "is_persistent": False, "learn_more_url": "https://checkboxrace.com", "severity": "other", "translation_key": "even_worse", "translation_placeholders": { "def": "789" }, }, { "breaks_in_ha_version": "2022.6", "data": { "entry_id": "123" }, "domain": "test", "issue_id": "issue_4", "is_fixable": True, "is_persistent": True, "learn_more_url": "https://checkboxrace.com/blah", "severity": "other", "translation_key": "even_worse", "translation_placeholders": { "xyz": "abc" }, }, ] events = async_capture_events( hass, issue_registry.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED) for issue in issues: issue_registry.async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=issue["is_persistent"], learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await hass.async_block_till_done() assert len(events) == 4 assert events[0].data == { "action": "create", "domain": "test", "issue_id": "issue_1", } assert events[1].data == { "action": "create", "domain": "test", "issue_id": "issue_2", } assert events[2].data == { "action": "create", "domain": "test", "issue_id": "issue_3", } assert events[3].data == { "action": "create", "domain": "test", "issue_id": "issue_4", } issue_registry.async_ignore_issue(hass, issues[0]["domain"], issues[0]["issue_id"], True) await hass.async_block_till_done() assert len(events) == 5 assert events[4].data == { "action": "update", "domain": "test", "issue_id": "issue_1", } issue_registry.async_delete_issue(hass, issues[2]["domain"], issues[2]["issue_id"]) await hass.async_block_till_done() assert len(events) == 6 assert events[5].data == { "action": "remove", "domain": "test", "issue_id": "issue_3", } registry: issue_registry.IssueRegistry = hass.data[ issue_registry.DATA_REGISTRY] assert len(registry.issues) == 3 issue1 = registry.async_get_issue("test", "issue_1") issue2 = registry.async_get_issue("test", "issue_2") issue4 = registry.async_get_issue("test", "issue_4") registry2 = issue_registry.IssueRegistry(hass) await flush_store(registry._store) await registry2.async_load() assert list(registry.issues) == list(registry2.issues) issue1_registry2 = registry2.async_get_issue("test", "issue_1") assert issue1_registry2 == issue_registry.IssueEntry( active=False, breaks_in_ha_version=None, created=issue1.created, data=None, dismissed_version=issue1.dismissed_version, domain=issue1.domain, is_fixable=None, is_persistent=issue1.is_persistent, issue_domain=None, issue_id=issue1.issue_id, learn_more_url=None, severity=None, translation_key=None, translation_placeholders=None, ) issue2_registry2 = registry2.async_get_issue("test", "issue_2") assert issue2_registry2 == issue_registry.IssueEntry( active=False, breaks_in_ha_version=None, created=issue2.created, data=None, dismissed_version=issue2.dismissed_version, domain=issue2.domain, is_fixable=None, is_persistent=issue2.is_persistent, issue_domain=None, issue_id=issue2.issue_id, learn_more_url=None, severity=None, translation_key=None, translation_placeholders=None, ) issue4_registry2 = registry2.async_get_issue("test", "issue_4") assert issue4_registry2 == issue4
async def test_delete_issue(hass: HomeAssistant, hass_ws_client, freezer) -> None: """Test we can delete an issue.""" freezer.move_to("2022-07-19 07:53:05") assert await async_setup_component(hass, DOMAIN, {}) client = await hass_ws_client(hass) issues = [ { "breaks_in_ha_version": "2022.9", "domain": "fake_integration", "issue_id": "issue_1", "is_fixable": True, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": { "abc": "123" }, }, ] for issue in issues: async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] } # Delete a non-existing issue async_delete_issue(hass, issues[0]["domain"], "no_such_issue") await client.send_json({"id": 2, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] } # Delete an existing issue async_delete_issue(hass, issues[0]["domain"], issues[0]["issue_id"]) await client.send_json({"id": 3, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []} # Delete the same issue again async_delete_issue(hass, issues[0]["domain"], issues[0]["issue_id"]) await client.send_json({"id": 4, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []} # Create the same issues again created timestamp should change freezer.move_to("2022-07-19 08:53:05") for issue in issues: async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 5, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T08:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] }
async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: """Test ignoring issues.""" assert await async_setup_component(hass, DOMAIN, {}) client = await hass_ws_client(hass) await client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []} issues = [ { "breaks_in_ha_version": "2022.9", "domain": "test", "is_fixable": True, "issue_id": "issue_1", "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": { "abc": "123" }, }, ] for issue in issues: async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 2, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] } # Ignore a non-existing issue with pytest.raises(KeyError): async_ignore_issue(hass, issues[0]["domain"], "no_such_issue", True) await client.send_json({"id": 3, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] } # Ignore an existing issue async_ignore_issue(hass, issues[0]["domain"], issues[0]["issue_id"], True) await client.send_json({"id": 4, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=ha_version, ignored=True, issue_domain=None, ) for issue in issues ] } # Ignore the same issue again async_ignore_issue(hass, issues[0]["domain"], issues[0]["issue_id"], True) await client.send_json({"id": 5, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=ha_version, ignored=True, issue_domain=None, ) for issue in issues ] } # Update an ignored issue async_create_issue( hass, issues[0]["domain"], issues[0]["issue_id"], breaks_in_ha_version=issues[0]["breaks_in_ha_version"], is_fixable=issues[0]["is_fixable"], is_persistent=False, learn_more_url="blablabla", severity=issues[0]["severity"], translation_key=issues[0]["translation_key"], translation_placeholders=issues[0]["translation_placeholders"], ) await client.send_json({"id": 6, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"]["issues"][0] == dict( issues[0], created="2022-07-19T07:53:05+00:00", dismissed_version=ha_version, ignored=True, learn_more_url="blablabla", issue_domain=None, ) # Unignore the same issue async_ignore_issue(hass, issues[0]["domain"], issues[0]["issue_id"], False) await client.send_json({"id": 7, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, learn_more_url="blablabla", issue_domain=None, ) for issue in issues ] }
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the demo environment.""" if DOMAIN not in config: return True if not hass.config_entries.async_entries(DOMAIN): hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={})) # Set up demo platforms for platform in COMPONENTS_WITH_DEMO_PLATFORM: hass.async_create_task( async_load_platform(hass, platform, DOMAIN, {}, config)) config.setdefault(ha.DOMAIN, {}) config.setdefault(DOMAIN, {}) # Set up sun if not hass.config.latitude: hass.config.latitude = 32.87336 if not hass.config.longitude: hass.config.longitude = 117.22743 tasks = [setup.async_setup_component(hass, "sun", config)] # Set up input select tasks.append( setup.async_setup_component( hass, "input_select", { "input_select": { "living_room_preset": { "options": ["Visitors", "Visitors with kids", "Home Alone"] }, "who_cooks": { "icon": "mdi:panda", "initial": "Anne Therese", "name": "Cook today", "options": ["Paulus", "Anne Therese"], }, } }, )) # Set up input boolean tasks.append( setup.async_setup_component( hass, "input_boolean", { "input_boolean": { "notify": { "icon": "mdi:car", "initial": False, "name": "Notify Anne Therese is home", } } }, )) # Set up input button tasks.append( setup.async_setup_component( hass, "input_button", { "input_button": { "bell": { "icon": "mdi:bell-ring-outline", "name": "Ring bell", } } }, )) # Set up input number tasks.append( setup.async_setup_component( hass, "input_number", { "input_number": { "noise_allowance": { "icon": "mdi:bell-ring", "min": 0, "max": 10, "name": "Allowed Noise", "unit_of_measurement": SOUND_PRESSURE_DB, } } }, )) results = await asyncio.gather(*tasks) if any(not result for result in results): return False # Set up example persistent notification persistent_notification.async_create( hass, "This is an example of a persistent notification.", title="Example Notification", ) async def demo_start_listener(_event: Event) -> None: """Finish set up.""" await finish_setup(hass, config) hass.bus.async_listen(EVENT_HOMEASSISTANT_START, demo_start_listener) # Create issues async_create_issue( hass, DOMAIN, "transmogrifier_deprecated", breaks_in_ha_version="2023.1.1", is_fixable=False, learn_more_url="https://en.wiktionary.org/wiki/transmogrifier", severity=IssueSeverity.WARNING, translation_key="transmogrifier_deprecated", ) async_create_issue( hass, DOMAIN, "out_of_blinker_fluid", breaks_in_ha_version="2023.1.1", is_fixable=True, learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", severity=IssueSeverity.CRITICAL, translation_key="out_of_blinker_fluid", ) async_create_issue( hass, DOMAIN, "unfixable_problem", is_fixable=False, learn_more_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", severity=IssueSeverity.WARNING, translation_key="unfixable_problem", ) async_create_issue( hass, DOMAIN, "bad_psu", is_fixable=True, learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", severity=IssueSeverity.CRITICAL, translation_key="bad_psu", ) return True
async def test_list_issues(hass: HomeAssistant, hass_storage, hass_ws_client) -> None: """Test we can list issues.""" # Add an inactive issue, this should not be exposed in the list hass_storage[issue_registry.STORAGE_KEY] = { "version": issue_registry.STORAGE_VERSION_MAJOR, "data": { "issues": [ { "created": "2022-07-19T09:41:13.746514+00:00", "dismissed_version": None, "domain": "test", "is_persistent": False, "issue_id": "issue_3_inactive", "issue_domain": None, }, ] }, } assert await async_setup_component(hass, DOMAIN, {}) client = await hass_ws_client(hass) await client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []} issues = [ { "breaks_in_ha_version": "2022.9", "domain": "test", "is_fixable": True, "issue_id": "issue_1", "issue_domain": None, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": {"abc": "123"}, }, { "breaks_in_ha_version": "2022.8", "domain": "test", "is_fixable": False, "issue_id": "issue_2", "issue_domain": None, "learn_more_url": "https://theuselessweb.com/abc", "severity": "other", "translation_key": "even_worse", "translation_placeholders": {"def": "456"}, }, ] for issue in issues: issue_registry.async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 2, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, ) for issue in issues ] }
async def async_trigger( self, run_variables: dict[str, Any], context: Context | None = None, skip_condition: bool = False, ) -> None: """Trigger automation. This method is a coroutine. """ reason = "" alias = "" if "trigger" in run_variables: if "description" in run_variables["trigger"]: reason = f' by {run_variables["trigger"]["description"]}' if "alias" in run_variables["trigger"]: alias = f' trigger \'{run_variables["trigger"]["alias"]}\'' self._logger.debug("Automation%s triggered%s", alias, reason) # Create a new context referring to the old context. parent_id = None if context is None else context.id trigger_context = Context(parent_id=parent_id) with trace_automation( self.hass, self.unique_id, self._raw_config, self._blueprint_inputs, trigger_context, self._trace_config, ) as automation_trace: this = None if state := self.hass.states.get(self.entity_id): this = state.as_dict() variables: dict[str, Any] = {"this": this, **(run_variables or {})} if self._variables: try: variables = self._variables.async_render( self.hass, variables) except TemplateError as err: self._logger.error("Error rendering variables: %s", err) automation_trace.set_error(err) return # Prepare tracing the automation automation_trace.set_trace(trace_get()) # Set trigger reason trigger_description = variables.get("trigger", {}).get("description") automation_trace.set_trigger_description(trigger_description) # Add initial variables as the trigger step if "trigger" in variables and "idx" in variables["trigger"]: trigger_path = f"trigger/{variables['trigger']['idx']}" else: trigger_path = "trigger" trace_element = TraceElement(variables, trigger_path) trace_append_element(trace_element) if (not skip_condition and self._cond_func is not None and not self._cond_func(variables)): self._logger.debug( "Conditions not met, aborting automation. Condition summary: %s", trace_get(clear=False), ) script_execution_set("failed_conditions") return self.async_set_context(trigger_context) event_data = { ATTR_NAME: self.name, ATTR_ENTITY_ID: self.entity_id, } if "trigger" in variables and "description" in variables["trigger"]: event_data[ATTR_SOURCE] = variables["trigger"]["description"] @callback def started_action(): self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context) # Make a new empty script stack; automations are allowed # to recursively trigger themselves script_stack_cv.set([]) try: with trace_path("action"): await self.action_script.async_run(variables, trigger_context, started_action) except ServiceNotFound as err: async_create_issue( self.hass, DOMAIN, f"{self.entity_id}_service_not_found_{err.domain}.{err.service}", is_fixable=True, is_persistent=True, severity=IssueSeverity.ERROR, translation_key="service_not_found", translation_placeholders={ "service": f"{err.domain}.{err.service}", "entity_id": self.entity_id, "name": self.name or self.entity_id, "edit": f"/config/automation/edit/{self.unique_id}", }, ) automation_trace.set_error(err) except (vol.Invalid, HomeAssistantError) as err: self._logger.error( "Error while executing automation %s: %s", self.entity_id, err, ) automation_trace.set_error(err) except Exception as err: # pylint: disable=broad-except self._logger.exception("While executing automation %s", self.entity_id) automation_trace.set_error(err)
async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None: """Test creating and updating issues.""" assert await async_setup_component(hass, DOMAIN, {}) client = await hass_ws_client(hass) await client.send_json({"id": 1, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == {"issues": []} issues = [ { "breaks_in_ha_version": "2022.9.0dev0", "domain": "test", "issue_id": "issue_1", "is_fixable": True, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", "translation_placeholders": { "abc": "123" }, }, { "breaks_in_ha_version": "2022.8", "domain": "test", "issue_id": "issue_2", "is_fixable": False, "learn_more_url": "https://theuselessweb.com/abc", "severity": "other", "translation_key": "even_worse", "translation_placeholders": { "def": "456" }, }, ] for issue in issues: async_create_issue( hass, issue["domain"], issue["issue_id"], breaks_in_ha_version=issue["breaks_in_ha_version"], is_fixable=issue["is_fixable"], is_persistent=False, learn_more_url=issue["learn_more_url"], severity=issue["severity"], translation_key=issue["translation_key"], translation_placeholders=issue["translation_placeholders"], ) await client.send_json({"id": 2, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"] == { "issues": [ dict( issue, created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, issue_domain=None, ) for issue in issues ] } # Update an issue async_create_issue( hass, issues[0]["domain"], issues[0]["issue_id"], breaks_in_ha_version=issues[0]["breaks_in_ha_version"], is_fixable=issues[0]["is_fixable"], is_persistent=False, issue_domain="my_issue_domain", learn_more_url="blablabla", severity=issues[0]["severity"], translation_key=issues[0]["translation_key"], translation_placeholders=issues[0]["translation_placeholders"], ) await client.send_json({"id": 3, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] assert msg["result"]["issues"][0] == dict( issues[0], created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, learn_more_url="blablabla", issue_domain="my_issue_domain", )