Skip to content

Commit

Permalink
typing, mostly
Browse files Browse the repository at this point in the history
  • Loading branch information
zxdavb committed Nov 24, 2024
1 parent 4a75538 commit c76271c
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 101 deletions.
12 changes: 6 additions & 6 deletions cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import tempfile
from datetime import datetime as dt, timedelta as td
from pathlib import Path
from typing import Any, Final, NotRequired, TypedDict
from typing import TYPE_CHECKING, Any, Final, NotRequired, TypedDict

import aiofiles
import aiofiles.os

from evohomeasync.auth import SZ_SESSION_ID_EXPIRES, AbstractSessionManager, SessionIdT
from evohomeasync2.auth import (
SZ_ACCESS_TOKEN_EXPIRES,
AbstractTokenManager,
AuthTokensT,
)
from evohomeasync2.auth import SZ_ACCESS_TOKEN_EXPIRES, AbstractTokenManager

if TYPE_CHECKING:
from evohomeasync2.auth import AuthTokensT


CACHE_FILE: Final = Path(tempfile.gettempdir() + "/.evo-cache.tmp")

Expand Down
22 changes: 9 additions & 13 deletions src/evohomeasync2/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import datetime as dt, timedelta as td
from http import HTTPMethod, HTTPStatus
from types import TracebackType
from typing import TYPE_CHECKING, Any, Final, TypedDict
from typing import TYPE_CHECKING, Any, Final

import aiohttp
import voluptuous as vol
Expand All @@ -23,6 +23,10 @@
from aiohttp.typedefs import StrOrURL

from .schemas import _EvoDictT, _EvoSchemaT # pragma: no cover
from .schemas.typedefs import (
EvoAuthTokensDictT as AuthTokensT,
TccAuthTokensResponseT as AuthTokenResponseT,
)


_APPLICATION_ID: Final = base64.b64encode(
Expand Down Expand Up @@ -99,18 +103,6 @@
}


class AuthTokensT(TypedDict):
access_token: str
access_token_expires: str # dt.isoformat()
refresh_token: str


class AuthTokenResponseT(TypedDict):
access_token: str
expires_in: int # number of seconds
refresh_token: str


class AbstractTokenManager(ABC):
"""An ABC for managing the auth tokens used for HTTP authentication."""

Expand Down Expand Up @@ -394,6 +386,8 @@ async def get(self, url: StrOrURL, schema: vol.Schema | None = None) -> _EvoSche
Optionally checks the response JSON against the expected schema and logs a
warning if it doesn't match.
Any keys in the response JSON will have been converted to snake_case.
"""

response: _EvoSchemaT
Expand All @@ -420,6 +414,8 @@ async def put(
Optionally checks the payload JSON against the expected schema and logs a
warning if it doesn't match.
Any snake_case keys in the request JSON will be converted to camelCase.
"""

response: dict[str, Any] | list[dict[str, Any]]
Expand Down
45 changes: 24 additions & 21 deletions src/evohomeasync2/control_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
if TYPE_CHECKING:
from . import Gateway, Location
from .schemas import _EvoDictT, _ScheduleT
from .schemas.typedefs import (
EvoAllowedSystemModeT,
EvoDhwConfigT,
EvoSystemModeStatusT,
EvoTcsConfigT,
EvoTcsEntryT,
EvoZonConfigT,
)


class ControlSystem(ActiveFaultsBase):
Expand All @@ -56,7 +64,7 @@ class ControlSystem(ActiveFaultsBase):
STATUS_SCHEMA: Final = factory_tcs_status(camel_to_snake)
_TYPE: Final = EntityType.TCS # type: ignore[misc]

def __init__(self, gateway: Gateway, config: _EvoDictT) -> None:
def __init__(self, gateway: Gateway, config: EvoTcsEntryT) -> None:
super().__init__(
config[SZ_SYSTEM_ID],
gateway._auth,
Expand All @@ -66,7 +74,7 @@ def __init__(self, gateway: Gateway, config: _EvoDictT) -> None:
self.gateway = gateway # parent
self.location: Location = gateway.location

self._config: Final[_EvoDictT] = { # type: ignore[misc]
self._config: Final[EvoTcsConfigT] = { # type: ignore[assignment,misc]
k: v for k, v in config.items() if k not in (SZ_DHW, SZ_ZONES)
}
self._status: _EvoDictT = {}
Expand All @@ -77,22 +85,22 @@ def __init__(self, gateway: Gateway, config: _EvoDictT) -> None:

self.hotwater: HotWater | None = None

zon_config: _EvoDictT
for zon_config in config[SZ_ZONES]:
zon_entry: EvoZonConfigT
for zon_entry in config[SZ_ZONES]:
try:
zone = Zone(self, zon_config)
zone = Zone(self, zon_entry)
except exc.InvalidSchemaError as err:
self._logger.warning(
f"{self}: zone_id='{zon_config[SZ_ZONE_ID]}' ignored: {err}"
f"{self}: zone_id='{zon_entry[SZ_ZONE_ID]}' ignored: {err}"
)
else:
self.zones.append(zone)
self.zones_by_name[zone.name] = zone
self.zones_by_id[zone.id] = zone

dhw_config: _EvoDictT
if dhw_config := config.get(SZ_DHW): # type: ignore[assignment]
self.hotwater = HotWater(self, dhw_config)
dhw_entry: EvoDhwConfigT
if dhw_entry := config.get(SZ_DHW): # type: ignore[assignment]
self.hotwater = HotWater(self, dhw_entry)

def _update_status(self, status: _EvoDictT) -> None:
super()._update_status(status) # process active faults
Expand Down Expand Up @@ -120,7 +128,7 @@ def _update_status(self, status: _EvoDictT) -> None:
)

@property
def allowed_system_modes(self) -> list[_EvoDictT]:
def allowed_system_modes(self) -> list[EvoAllowedSystemModeT]:
"""
"allowedSystemModes": [
{"systemMode": "HeatingOff", "canBePermanent": true, "canBeTemporary": false},
Expand All @@ -133,37 +141,32 @@ def allowed_system_modes(self) -> list[_EvoDictT]:
]
"""

ret: list[_EvoDictT] = self._config[SZ_ALLOWED_SYSTEM_MODES]
return ret
return self._config[SZ_ALLOWED_SYSTEM_MODES]

@property # a convenience attr
def mode(self) -> SystemMode | None:
if self.system_mode_status is None:
return None
ret: SystemMode = self.system_mode_status[SZ_MODE]
return ret
return self.system_mode_status[SZ_MODE]

@property # a convenience attr
def modes(self) -> tuple[SystemMode]:
ret = tuple(d[SZ_SYSTEM_MODE] for d in self.allowed_system_modes)
return ret
return tuple(d[SZ_SYSTEM_MODE] for d in self.allowed_system_modes) # type: ignore[return-value]

@property
def model(self) -> TcsModelType:
ret: TcsModelType = self._config[SZ_MODEL_TYPE]
return ret
return self._config[SZ_MODEL_TYPE]

@property
def system_mode_status(self) -> _EvoDictT | None:
def system_mode_status(self) -> EvoSystemModeStatusT | None:
"""
"systemModeStatus": {
"mode": "AutoWithEco",
"isPermanent": true
}
"""

ret: _EvoDictT | None = self._status.get(SZ_SYSTEM_MODE_STATUS)
return ret
return self._status.get(SZ_SYSTEM_MODE_STATUS)

@property
def zones_by_name(self) -> dict[str, Zone]:
Expand Down
15 changes: 8 additions & 7 deletions src/evohomeasync2/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
if TYPE_CHECKING:
from . import Location
from .schemas import _EvoDictT
from .schemas.typedefs import EvoGwyConfigT, EvoGwyEntryT, EvoTcsEntryT


class Gateway(ActiveFaultsBase):
Expand All @@ -30,33 +31,33 @@ class Gateway(ActiveFaultsBase):
STATUS_SCHEMA: Final = factory_gwy_status(camel_to_snake)
_TYPE: Final = EntityType.GWY # type: ignore[misc]

def __init__(self, location: Location, config: _EvoDictT) -> None:
def __init__(self, location: Location, config: EvoGwyEntryT) -> None:
super().__init__(
config[SZ_GATEWAY_INFO][SZ_GATEWAY_ID],
location._auth,
location._logger,
)

self.location = location # parent
#

self._config: Final[_EvoDictT] = config[SZ_GATEWAY_INFO] # type: ignore[misc]
self._config: Final[EvoGwyConfigT] = config[SZ_GATEWAY_INFO] # type: ignore[assignment,misc]
self._status: _EvoDictT = {}

# children
self.control_systems: list[ControlSystem] = []
self.control_system_by_id: dict[str, ControlSystem] = {} # tcs by id

tcs_config: _EvoDictT
for tcs_config in config[SZ_TEMPERATURE_CONTROL_SYSTEMS]:
tcs = ControlSystem(self, tcs_config)
tcs_entry: EvoTcsEntryT
for tcs_entry in config[SZ_TEMPERATURE_CONTROL_SYSTEMS]:
tcs = ControlSystem(self, tcs_entry)

self.control_systems.append(tcs)
self.control_system_by_id[tcs.id] = tcs

@property
def mac_address(self) -> str:
ret: str = self._config[SZ_MAC]
return ret
return self._config[SZ_MAC]

def _update_status(self, status: _EvoDictT) -> None:
super()._update_status(status) # process active faults
Expand Down
10 changes: 8 additions & 2 deletions src/evohomeasync2/hotwater.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
if TYPE_CHECKING:
from . import ControlSystem
from .schemas import _EvoDictT
from .schemas.typedefs import DayOfWeekDhwT, EvoDhwConfigT


class HotWater(_ZoneBase):
Expand All @@ -46,8 +47,13 @@ class HotWater(_ZoneBase):
SCH_SCHEDULE_GET: Final = factory_schedule_dhw(camel_to_snake) # type: ignore[misc]
SCH_SCHEDULE_PUT: Final = factory_put_schedule_dhw(camel_to_snake) # type: ignore[misc]

def __init__(self, tcs: ControlSystem, config: _EvoDictT) -> None:
super().__init__(config[SZ_DHW_ID], tcs, config)
def __init__(self, tcs: ControlSystem, config: EvoDhwConfigT) -> None:
super().__init__(config[SZ_DHW_ID], tcs)

self._config: Final[EvoDhwConfigT] = config # type: ignore[assignment,misc]
self._status: _EvoDictT = {}

self._schedule: list[DayOfWeekDhwT] | None = None # type: ignore[assignment]

@property # a for convenience attr
def mode(self) -> ZoneMode | None:
Expand Down
39 changes: 21 additions & 18 deletions src/evohomeasync2/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
if TYPE_CHECKING:
from . import _EvohomeClientNew as EvohomeClient
from .schemas import _EvoDictT
from .schemas.typedefs import (
EvoGwyEntryT,
EvoLocationOwnerInfoT,
EvoLocConfigT,
EvoLocEntryT,
EvoTimeZoneInfoT,
)


class Location(EntityBase):
Expand All @@ -34,25 +41,26 @@ class Location(EntityBase):
STATUS_SCHEMA: Final = factory_loc_status(camel_to_snake)
_TYPE: Final = EntityType.LOC # type: ignore[misc]

def __init__(self, client: EvohomeClient, config: _EvoDictT) -> None:
def __init__(self, client: EvohomeClient, config: EvoLocEntryT) -> None:
super().__init__(
config[SZ_LOCATION_INFO][SZ_LOCATION_ID],
client.auth,
client._logger,
)

self.client = client # proxy for location's parent
self.client = client # proxy for parent
#

self._config: Final[_EvoDictT] = config[SZ_LOCATION_INFO] # type: ignore[misc]
self._config: Final[EvoLocConfigT] = config[SZ_LOCATION_INFO] # type: ignore[assignment,misc]
self._status: _EvoDictT = {}

# children
self.gateways: list[Gateway] = []
self.gateway_by_id: dict[str, Gateway] = {} # gwy by id

gwy_config: _EvoDictT
for gwy_config in config[SZ_GATEWAYS]:
gwy = Gateway(self, gwy_config)
gwy_entry: EvoGwyEntryT
for gwy_entry in config[SZ_GATEWAYS]:
gwy = Gateway(self, gwy_entry)

self.gateways.append(gwy)
self.gateway_by_id[gwy.id] = gwy
Expand All @@ -64,16 +72,14 @@ def country(self) -> str:
# "Netherlands"
# "UnitedKingdom"

ret: str = self._config[SZ_COUNTRY]
return ret
return self._config[SZ_COUNTRY]

@property
def name(self) -> str:
ret: str = self._config[SZ_NAME]
return ret
return self._config[SZ_NAME]

@property
def owner(self) -> _EvoDictT:
def owner(self) -> EvoLocationOwnerInfoT:
"""
"locationOwner": {
"userId": "1234567",
Expand All @@ -83,11 +89,10 @@ def owner(self) -> _EvoDictT:
}
"""

ret: _EvoDictT = self._config[SZ_LOCATION_OWNER]
return ret
return self._config[SZ_LOCATION_OWNER]

@property
def time_zone(self) -> _EvoDictT:
def time_zone(self) -> EvoTimeZoneInfoT:
"""
"timeZone": {
"timeZoneId": "GMTStandardTime",
Expand All @@ -105,13 +110,11 @@ def time_zone(self) -> _EvoDictT:
# "FLEStandardTime": "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius",
# "AUSEasternStandardTime": "(UTC+10:00) Canberra, Melbourne, Sydney",

ret: _EvoDictT = self._config[SZ_TIME_ZONE]
return ret
return self._config[SZ_TIME_ZONE]

@property
def use_daylight_save_switching(self) -> bool:
ret: bool = self._config[SZ_USE_DAYLIGHT_SAVE_SWITCHING]
return ret
return self._config[SZ_USE_DAYLIGHT_SAVE_SWITCHING]

async def update(self) -> _EvoDictT:
"""Get the latest state of the location and update its status.
Expand Down
Loading

0 comments on commit c76271c

Please sign in to comment.