Skip to content

Commit

Permalink
Merge branch 'home-assistant:dev' into lektrico2
Browse files Browse the repository at this point in the history
  • Loading branch information
Lektrico authored Apr 1, 2024
2 parents 38a0bf8 + 55657dc commit 38de2b5
Show file tree
Hide file tree
Showing 302 changed files with 5,247 additions and 1,978 deletions.
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ homeassistant.components.electric_kiwi.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
homeassistant.components.energenie_power_sockets.*
homeassistant.components.energy.*
homeassistant.components.energyzero.*
homeassistant.components.enigma2.*
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ build.json @home-assistant/supervisor
/tests/components/emulated_hue/ @bdraco @Tho85
/homeassistant/components/emulated_kasa/ @kbickar
/tests/components/emulated_kasa/ @kbickar
/homeassistant/components/energenie_power_sockets/ @gnumpi
/tests/components/energenie_power_sockets/ @gnumpi
/homeassistant/components/energy/ @home-assistant/core
/tests/components/energy/ @home-assistant/core
/homeassistant/components/energyzero/ @klaasnicolaas
Expand Down
43 changes: 41 additions & 2 deletions homeassistant/block_async_io.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
"""Block blocking calls being done in asyncio."""

from contextlib import suppress
from http.client import HTTPConnection
import importlib
import sys
import time
from typing import Any

from .util.async_ import protect_loop
from .helpers.frame import get_current_frame
from .util.loop import protect_loop

_IN_TESTS = "unittest" in sys.modules


def _check_import_call_allowed(mapped_args: dict[str, Any]) -> bool:
# If the module is already imported, we can ignore it.
return bool((args := mapped_args.get("args")) and args[0] in sys.modules)


def _check_sleep_call_allowed(mapped_args: dict[str, Any]) -> bool:
#
# Avoid extracting the stack unless we need to since it
# will have to access the linecache which can do blocking
# I/O and we are trying to avoid blocking calls.
#
# frame[0] is us
# frame[1] is check_loop
# frame[2] is protected_loop_func
# frame[3] is the offender
with suppress(ValueError):
return get_current_frame(4).f_code.co_filename.endswith("pydevd.py")
return False


def enable() -> None:
Expand All @@ -14,8 +41,20 @@ def enable() -> None:
)

# Prevent sleeping in event loop. Non-strict since 2022.02
time.sleep = protect_loop(time.sleep, strict=False)
time.sleep = protect_loop(
time.sleep, strict=False, check_allowed=_check_sleep_call_allowed
)

# Currently disabled. pytz doing I/O when getting timezone.
# Prevent files being opened inside the event loop
# builtins.open = protect_loop(builtins.open)

if not _IN_TESTS:
# unittest uses `importlib.import_module` to do mocking
# so we cannot protect it if we are running tests
importlib.import_module = protect_loop(
importlib.import_module,
strict_core=False,
strict=False,
check_allowed=_check_import_call_allowed,
)
16 changes: 15 additions & 1 deletion homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
import voluptuous as vol
import yarl

from . import config as conf_util, config_entries, core, loader, requirements
from . import (
block_async_io,
config as conf_util,
config_entries,
core,
loader,
requirements,
)

# Pre-import frontend deps which have no requirements here to avoid
# loading them at run time and blocking the event loop. We do this ahead
Expand Down Expand Up @@ -93,6 +100,11 @@
from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_virtual_env

with contextlib.suppress(ImportError):
# Ensure anyio backend is imported to avoid it being imported in the event loop
from anyio._backends import _asyncio # noqa: F401


if TYPE_CHECKING:
from .runner import RuntimeConfig

Expand Down Expand Up @@ -255,6 +267,8 @@ async def async_setup_hass(
_LOGGER.info("Config directory: %s", runtime_config.config_dir)

loader.async_setup(hass)
block_async_io.enable()

config_dict = None
basic_setup_success = False

Expand Down
9 changes: 5 additions & 4 deletions homeassistant/components/amberelectric/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,18 @@ def _fetch_sites(self, token: str) -> list[Site] | None:

try:
sites: list[Site] = filter_sites(api.get_sites())
if len(sites) == 0:
self._errors[CONF_API_TOKEN] = "no_site"
return None
return sites
except amberelectric.ApiException as api_exception:
if api_exception.status == 403:
self._errors[CONF_API_TOKEN] = "invalid_api_token"
else:
self._errors[CONF_API_TOKEN] = "unknown_error"
return None

if len(sites) == 0:
self._errors[CONF_API_TOKEN] = "no_site"
return None
return sites

async def async_step_user(
self, user_input: dict[str, str] | None = None
) -> ConfigFlowResult:
Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/analytics_insights/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

await coordinator.async_config_entry_first_refresh()

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnalyticsInsightsData(
coordinator=coordinator, names=names
)
hass.data[DOMAIN] = AnalyticsInsightsData(coordinator=coordinator, names=names)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
Expand All @@ -62,7 +60,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
hass.data.pop(DOMAIN)

return unload_ok

Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/analytics_insights/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
self._async_abort_entries_match()
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/analytics_insights/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["python_homeassistant_analytics"],
"requirements": ["python-homeassistant-analytics==0.6.0"]
"requirements": ["python-homeassistant-analytics==0.6.0"],
"single_config_entry": true
}
2 changes: 1 addition & 1 deletion homeassistant/components/analytics_insights/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async def async_setup_entry(
) -> None:
"""Initialize the entries."""

analytics_data: AnalyticsInsightsData = hass.data[DOMAIN][entry.entry_id]
analytics_data: AnalyticsInsightsData = hass.data[DOMAIN]
coordinator: HomeassistantAnalyticsDataUpdateCoordinator = (
analytics_data.coordinator
)
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/analytics_insights/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
}
},
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"no_integration_selected": "You must select at least one integration to track"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/apple_tv/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ def _host_filter() -> list[str] | None:
return None
try:
ip_address(identifier)
return [identifier]
except ValueError:
return None
return [identifier]

# If we have an address, only probe that address to avoid
# broadcast traffic on the network
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/auth/indieauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ def verify_client_id(client_id: str) -> bool:
"""Verify that the client id is valid."""
try:
_parse_client_id(client_id)
return True
except ValueError:
return False
return True


def _parse_url(url: str) -> ParseResult:
Expand Down
9 changes: 4 additions & 5 deletions homeassistant/components/awair/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,12 @@ async def _check_cloud_connection(
try:
user = await awair.user()
devices = await user.devices()
if not devices:
return (None, "no_devices_found")

return (user, None)

except AuthError:
return (None, "invalid_access_token")
except AwairError as err:
LOGGER.error("Unexpected API error: %s", err)
return (None, "unknown")

if not devices:
return (None, "no_devices_found")
return (user, None)
2 changes: 1 addition & 1 deletion homeassistant/components/awair/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async def _async_update_data(self) -> dict[str, AwairResult]:
devices = await self._awair.devices()
self._device = devices[0]
result = await self._fetch_air_data(self._device)
return {result.device.uuid: result}
except AwairError as err:
LOGGER.error("Unexpected API error: %s", err)
raise UpdateFailed(err) from err
return {result.device.uuid: result}
5 changes: 3 additions & 2 deletions homeassistant/components/axis/hub/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,16 @@ async def async_use_mqtt(self, hass: HomeAssistant, component: str) -> None:
if status.status.state == ClientState.ACTIVE:
self.config.entry.async_on_unload(
await mqtt.async_subscribe(
hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message
hass, f"{status.config.device_topic_prefix}/#", self.mqtt_message
)
)

@callback
def mqtt_message(self, message: ReceiveMessage) -> None:
"""Receive Axis MQTT message."""
self.disconnect_from_stream()

if message.topic.endswith("event/connection"):
return
event = mqtt_json_to_event(message.payload)
self.api.event.handler(event)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/axis/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"iot_class": "local_push",
"loggers": ["axis"],
"quality_scale": "platinum",
"requirements": ["axis==59"],
"requirements": ["axis==60"],
"ssdp": [
{
"manufacturer": "AXIS"
Expand Down
9 changes: 5 additions & 4 deletions homeassistant/components/buienradar/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,22 +819,23 @@ def _load_data(self, data): # noqa: C901
self._attr_native_value = data.get(FORECAST)[fcday].get(
sensor_type[:-3]
)
if self.state is not None:
self._attr_native_value = round(self.state * 3.6, 1)
return True
except IndexError:
_LOGGER.warning("No forecast for fcday=%s", fcday)
return False

if self.state is not None:
self._attr_native_value = round(self.state * 3.6, 1)
return True

# update all other sensors
try:
self._attr_native_value = data.get(FORECAST)[fcday].get(
sensor_type[:-3]
)
return True
except IndexError:
_LOGGER.warning("No forecast for fcday=%s", fcday)
return False
return True

if sensor_type == SYMBOL or sensor_type.startswith(CONDITION):
# update weather symbol & status text
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/cert_expiry/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ async def _test_connection(
user_input[CONF_HOST],
user_input.get(CONF_PORT, DEFAULT_PORT),
)
return True
except ResolveFailed:
self._errors[CONF_HOST] = "resolve_failed"
except ConnectionTimeout:
Expand All @@ -52,6 +51,8 @@ async def _test_connection(
self._errors[CONF_HOST] = "connection_refused"
except ValidationFailure:
return True
else:
return True
return False

async def async_step_user(
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/citybikes/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ async def get_closest_network_id(self, latitude, longitude):
self.hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA
)
self.networks = networks[ATTR_NETWORKS_LIST]
except CityBikesRequestError as err:
raise PlatformNotReady from err
else:
result = None
minimum_dist = None
for network in self.networks:
Expand All @@ -241,8 +244,6 @@ async def get_closest_network_id(self, latitude, longitude):
result = network[ATTR_ID]

return result
except CityBikesRequestError as err:
raise PlatformNotReady from err
finally:
self.networks_loading.release()

Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/climate/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse
raise intent.NoStatesMatchedError(
name=entity_text or entity_name,
area=area_name or area_id,
floor=None,
domains={DOMAIN},
device_classes=None,
)
Expand All @@ -75,6 +76,7 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse
raise intent.NoStatesMatchedError(
name=entity_name,
area=None,
floor=None,
domains={DOMAIN},
device_classes=None,
)
Expand Down
5 changes: 1 addition & 4 deletions homeassistant/components/cloud/alexa_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,16 +509,13 @@ async def _sync_helper(self, to_update: list[str], to_remove: list[str]) -> bool
try:
async with asyncio.timeout(10):
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)

return True

except TimeoutError:
_LOGGER.warning("Timeout trying to sync entities to Alexa")
return False

except aiohttp.ClientError as err:
_LOGGER.warning("Error trying to sync entities to Alexa: %s", err)
return False
return True

async def _handle_entity_registry_updated(self, event: Event) -> None:
"""Handle when entity registry updated."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cloud/http_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ async def error_handler(
"""Handle exceptions that raise from the wrapped request handler."""
try:
result = await handler(view, request, *args, **kwargs)
return result
except Exception as err: # pylint: disable=broad-except
status, msg = _process_cloud_exception(err, request.path)
return view.json_message(
msg, status_code=status, message_code=err.__class__.__name__.lower()
)
return result

return error_handler

Expand Down
Loading

0 comments on commit 38de2b5

Please sign in to comment.