Skip to content

Commit

Permalink
Merge branch 'dev' into hass-key
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p authored Apr 29, 2024
2 parents a78bdfb + 8233b62 commit cca749f
Show file tree
Hide file tree
Showing 62 changed files with 1,849 additions and 772 deletions.
4 changes: 2 additions & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,8 @@ build.json @home-assistant/supervisor
/tests/components/group/ @home-assistant/core
/homeassistant/components/guardian/ @bachya
/tests/components/guardian/ @bachya
/homeassistant/components/habitica/ @ASMfreaK @leikoilja
/tests/components/habitica/ @ASMfreaK @leikoilja
/homeassistant/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r
/tests/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r
/homeassistant/components/hardkernel/ @home-assistant/core
/tests/components/hardkernel/ @home-assistant/core
/homeassistant/components/hardware/ @home-assistant/core
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ENV \
ARG QEMU_CPU

# Install uv
RUN pip3 install uv==0.1.35
RUN pip3 install uv==0.1.39

WORKDIR /usr/src

Expand Down
4 changes: 3 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Check out `home-assistant.io <https://home-assistant.io>`__ for `a
demo <https://demo.home-assistant.io>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
`tutorials <https://home-assistant.io/getting-started/automation/>`__ and `documentation <https://home-assistant.io/docs/>`__.

This is a project of the `Open Home Foundation <https://www.openhomefoundation.org/>`__.

|screenshot-states|

Featured integrations
Expand All @@ -25,4 +27,4 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-states.png
:target: https://demo.home-assistant.io
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-integrations.png
:target: https://home-assistant.io/integrations/
:target: https://home-assistant.io/integrations/
27 changes: 18 additions & 9 deletions homeassistant/components/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,16 +325,24 @@ def __getattribute__(self, __name: str) -> Any:

# Convert the supported features to ClimateEntityFeature.
# Remove this compatibility shim in 2025.1 or later.
_supported_features = super().__getattribute__(__name)
_supported_features: ClimateEntityFeature = super().__getattribute__(
"supported_features"
)
_mod_supported_features: ClimateEntityFeature = super().__getattribute__(
"_ClimateEntity__mod_supported_features"
)
if type(_supported_features) is int: # noqa: E721
new_features = ClimateEntityFeature(_supported_features)
self._report_deprecated_supported_features_values(new_features)
_features = ClimateEntityFeature(_supported_features)
self._report_deprecated_supported_features_values(_features)
else:
_features = _supported_features

if not _mod_supported_features:
return _features

# Add automatically calculated ClimateEntityFeature.TURN_OFF/TURN_ON to
# supported features and return it
return _supported_features | super().__getattribute__(
"_ClimateEntity__mod_supported_features"
)
return _features | _mod_supported_features

@callback
def add_to_platform_start(
Expand Down Expand Up @@ -375,7 +383,8 @@ def _report_turn_on_off(feature: str, method: str) -> None:
# Return if integration has migrated already
return

if not self.supported_features & ClimateEntityFeature.TURN_OFF and (
supported_features = self.supported_features
if not supported_features & ClimateEntityFeature.TURN_OFF and (
type(self).async_turn_off is not ClimateEntity.async_turn_off
or type(self).turn_off is not ClimateEntity.turn_off
):
Expand All @@ -385,7 +394,7 @@ def _report_turn_on_off(feature: str, method: str) -> None:
ClimateEntityFeature.TURN_OFF
)

if not self.supported_features & ClimateEntityFeature.TURN_ON and (
if not supported_features & ClimateEntityFeature.TURN_ON and (
type(self).async_turn_on is not ClimateEntity.async_turn_on
or type(self).turn_on is not ClimateEntity.turn_on
):
Expand All @@ -398,7 +407,7 @@ def _report_turn_on_off(feature: str, method: str) -> None:
if (modes := self.hvac_modes) and len(modes) >= 2 and HVACMode.OFF in modes:
# turn_on/off implicitly supported by including more modes than 1 and one of these
# are HVACMode.OFF
_modes = [_mode for _mode in self.hvac_modes if _mode is not None]
_modes = [_mode for _mode in modes if _mode is not None]
_report_turn_on_off(", ".join(_modes or []), "turn_on/turn_off")
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
Expand Down
17 changes: 13 additions & 4 deletions homeassistant/components/fritz/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,10 @@ async def _async_update_hosts_info(self) -> dict[str, Device]:
)
except Exception as ex: # pylint: disable=[broad-except]
if not self.hass.is_stopping:
raise HomeAssistantError("Error refreshing hosts info") from ex
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="error_refresh_hosts_info",
) from ex

hosts: dict[str, Device] = {}
if hosts_attributes:
Expand Down Expand Up @@ -730,7 +733,9 @@ async def service_fritzbox(
_LOGGER.debug("FRITZ!Box service: %s", service_call.service)

if not self.connection:
raise HomeAssistantError("Unable to establish a connection")
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unable_to_connect"
)

try:
if service_call.service == SERVICE_REBOOT:
Expand Down Expand Up @@ -765,9 +770,13 @@ async def service_fritzbox(
return

except (FritzServiceError, FritzActionError) as ex:
raise HomeAssistantError("Service or parameter unknown") from ex
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="service_parameter_unknown"
) from ex
except FritzConnectionException as ex:
raise HomeAssistantError("Service not supported") from ex
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="service_not_supported"
) from ex


class AvmWrapper(FritzBoxTools): # pylint: disable=hass-enforce-coordinator-module
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/fritz/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ async def async_call_fritz_service(service_call: ServiceCall) -> None:
)
):
raise HomeAssistantError(
f"Failed to call service '{service_call.service}'. Config entry for"
" target not found"
translation_domain=DOMAIN,
translation_key="config_entry_not_found",
translation_placeholders={"service": service_call.service},
)

for entry_id in fritzbox_entry_ids:
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/fritz/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,18 @@
}
}
}
},
"exceptions": {
"config_entry_not_found": {
"message": "Failed to call service \"{service}\". Config entry for target not found"
},
"service_parameter_unknown": { "message": "Service or parameter unknown" },
"service_not_supported": { "message": "Service not supported" },
"error_refresh_hosts_info": {
"message": "Error refreshing hosts info"
},
"unable_to_connect": {
"message": "Unable to establish a connection"
}
}
}
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240426.0"]
"requirements": ["home-assistant-frontend==20240429.0"]
}
52 changes: 49 additions & 3 deletions homeassistant/components/fyta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@

from __future__ import annotations

from datetime import datetime
import logging
from typing import Any
from zoneinfo import ZoneInfo

from fyta_cli.fyta_connector import FytaConnector

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_PASSWORD,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant

from .const import DOMAIN
from .const import CONF_EXPIRATION, DOMAIN
from .coordinator import FytaCoordinator

_LOGGER = logging.getLogger(__name__)
Expand All @@ -22,11 +30,16 @@

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the Fyta integration."""
tz: str = hass.config.time_zone

username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
access_token: str = entry.data[CONF_ACCESS_TOKEN]
expiration: datetime = datetime.fromisoformat(
entry.data[CONF_EXPIRATION]
).astimezone(ZoneInfo(tz))

fyta = FytaConnector(username, password)
fyta = FytaConnector(username, password, access_token, expiration, tz)

coordinator = FytaCoordinator(hass, fyta)

Expand All @@ -47,3 +60,36 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)

if config_entry.version > 1:
# This means the user has downgraded from a future version
return False

if config_entry.version == 1:
new = {**config_entry.data}
if config_entry.minor_version < 2:
fyta = FytaConnector(
config_entry.data[CONF_USERNAME], config_entry.data[CONF_PASSWORD]
)
credentials: dict[str, Any] = await fyta.login()
await fyta.client.close()

new[CONF_ACCESS_TOKEN] = credentials[CONF_ACCESS_TOKEN]
new[CONF_EXPIRATION] = credentials[CONF_EXPIRATION].isoformat()

hass.config_entries.async_update_entry(
config_entry, data=new, minor_version=2, version=1
)

_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)

return True
17 changes: 14 additions & 3 deletions homeassistant/components/fyta/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME

from .const import DOMAIN
from .const import CONF_EXPIRATION, DOMAIN

_LOGGER = logging.getLogger(__name__)

Expand All @@ -31,14 +31,19 @@ class FytaConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Fyta."""

VERSION = 1
_entry: ConfigEntry | None = None
MINOR_VERSION = 2

def __init__(self) -> None:
"""Initialize FytaConfigFlow."""
self.credentials: dict[str, Any] = {}
self._entry: ConfigEntry | None = None

async def async_auth(self, user_input: Mapping[str, Any]) -> dict[str, str]:
"""Reusable Auth Helper."""
fyta = FytaConnector(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])

try:
await fyta.login()
self.credentials = await fyta.login()
except FytaConnectionError:
return {"base": "cannot_connect"}
except FytaAuthentificationError:
Expand All @@ -51,6 +56,10 @@ async def async_auth(self, user_input: Mapping[str, Any]) -> dict[str, str]:
finally:
await fyta.client.close()

self.credentials[CONF_EXPIRATION] = self.credentials[
CONF_EXPIRATION
].isoformat()

return {}

async def async_step_user(
Expand All @@ -62,6 +71,7 @@ async def async_step_user(
self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]})

if not (errors := await self.async_auth(user_input)):
user_input |= self.credentials
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)
Expand All @@ -85,6 +95,7 @@ async def async_step_reauth_confirm(
assert self._entry is not None

if user_input and not (errors := await self.async_auth(user_input)):
user_input |= self.credentials
return self.async_update_reload_and_abort(
self._entry, data={**self._entry.data, **user_input}
)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/fyta/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Const for fyta integration."""

DOMAIN = "fyta"
CONF_EXPIRATION = "expiration"
25 changes: 22 additions & 3 deletions homeassistant/components/fyta/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import CONF_EXPIRATION

_LOGGER = logging.getLogger(__name__)


Expand All @@ -39,17 +42,33 @@ async def _async_update_data(
) -> dict[int, dict[str, Any]]:
"""Fetch data from API endpoint."""

if self.fyta.expiration is None or self.fyta.expiration < datetime.now():
if (
self.fyta.expiration is None
or self.fyta.expiration.timestamp() < datetime.now().timestamp()
):
await self.renew_authentication()

return await self.fyta.update_all_plants()

async def renew_authentication(self) -> None:
async def renew_authentication(self) -> bool:
"""Renew access token for FYTA API."""
credentials: dict[str, Any] = {}

try:
await self.fyta.login()
credentials = await self.fyta.login()
except FytaConnectionError as ex:
raise ConfigEntryNotReady from ex
except (FytaAuthentificationError, FytaPasswordError) as ex:
raise ConfigEntryAuthFailed from ex

new_config_entry = {**self.config_entry.data}
new_config_entry[CONF_ACCESS_TOKEN] = credentials[CONF_ACCESS_TOKEN]
new_config_entry[CONF_EXPIRATION] = credentials[CONF_EXPIRATION].isoformat()

self.hass.config_entries.async_update_entry(
self.config_entry, data=new_config_entry
)

_LOGGER.debug("Credentials successfully updated")

return True
2 changes: 1 addition & 1 deletion homeassistant/components/fyta/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/fyta",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["fyta_cli==0.3.5"]
"requirements": ["fyta_cli==0.4.1"]
}
Loading

0 comments on commit cca749f

Please sign in to comment.