Skip to content

Commit

Permalink
de-lint DTZ
Browse files Browse the repository at this point in the history
  • Loading branch information
zxdavb committed Sep 1, 2024
1 parent 40e86e1 commit 01f219c
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 39 deletions.
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@
"C90", # mcabe
"COM", # flake8-commas
# "D", # pydocstyle # a big job
# "DTZ", # flake8-datetimez
"DTZ", # flake8-datetimez # desirable
"E", # pycodestyle
# "EM", # flake8-errmsg
"EM", # flake8-errmsg
"EXE", # flake8-executable
"F", # Pyflakes
"FA", # flake8-future-annotations
Expand Down Expand Up @@ -210,6 +210,8 @@
"G004", # Logging statement uses f-string
"E501", # Line too long
"EXE001", # Shebang is present but file is not executable
"EM101", # Exception must not use a string literal
"EM102", # Exception must not use an f-string literal
"S101", # Use of assert detected
"SIM102", # Use a single `if` statement instead of nested `if` statements
"SIM114", # Combine `if` branches using logical `or` operator
Expand Down
11 changes: 8 additions & 3 deletions src/evohomeasync2/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ def _password(self) -> str:
def _token_data_reset(self) -> None:
"""Reset the token data to its falsy state."""
self.access_token = ""
self.access_token_expires = dt.min
self.access_token_expires = dt.now().astimezone().replace(year=1901)
self.refresh_token = ""

def _token_data_from_api(self, tokens: OAuthTokenData) -> None:
"""Convert the token data from the vendor's API to the internal format."""
self.access_token = tokens[SZ_ACCESS_TOKEN]
self.access_token_expires = dt.now() + td(seconds=tokens[SZ_EXPIRES_IN] - 15)
self.access_token_expires = dt.now().astimezone() + td(
seconds=tokens[SZ_EXPIRES_IN] - 15
)
self.refresh_token = tokens[SZ_REFRESH_TOKEN]

def _token_data_from_dict(self, tokens: _EvoTokenData) -> None:
Expand All @@ -135,7 +137,10 @@ def _token_data_as_dict(self) -> _EvoTokenData:

def is_token_data_valid(self) -> bool:
"""Return True if we have a valid access token."""
return bool(self.access_token) and self.access_token_expires > dt.now()
return (
bool(self.access_token)
and self.access_token_expires > dt.now().astimezone()
)

async def fetch_access_token(self) -> str: # HA api
"""Return a valid access token.
Expand Down
2 changes: 1 addition & 1 deletion src/evohomeasync2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async def save_access_token(self) -> None:
token_cache = {
k: v
for k, v in token_cache.items()
if v[SZ_ACCESS_TOKEN_EXPIRES] > dt.now().isoformat()
if v[SZ_ACCESS_TOKEN_EXPIRES] > dt.now().astimezone().isoformat()
}

content = json.dumps(
Expand Down
8 changes: 4 additions & 4 deletions src/evohomeasync2/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
SZ_TEMPERATURE_CONTROL_SYSTEMS,
)
from .system import System
from .zone import ActiveFaultsBase, EntityBase
from .zone import ActiveFaultsMixin, EntityBase

if TYPE_CHECKING:
import voluptuous as vol
Expand All @@ -30,15 +30,15 @@ class _GatewayDeprecated: # pragma: no cover
"""Deprecated attributes and methods removed from the evohome-client namespace."""

@property
def systemId(self) -> NoReturn: # noqa: N802
def gatewayId(self) -> NoReturn: # noqa: N802
raise exc.DeprecationError(f"{self}: .gatewayId is deprecated, use .id")


class Gateway(_GatewayDeprecated, ActiveFaultsBase, EntityBase):
class Gateway(_GatewayDeprecated, ActiveFaultsMixin, EntityBase):
"""Instance of a location's gateway."""

STATUS_SCHEMA: Final[vol.Schema] = SCH_GWY_STATUS
TYPE: Final = SZ_GATEWAY # type: ignore[misc]
TYPE: Final = SZ_GATEWAY # used for RESTful API calls

def __init__(self, location: Location, config: _EvoDictT, /) -> None:
super().__init__(config[SZ_GATEWAY_INFO][SZ_GATEWAY_ID], location)
Expand Down
2 changes: 1 addition & 1 deletion src/evohomeasync2/hotwater.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class HotWater(_HotWaterDeprecated, _ZoneBase, EntityBase):
"""Instance of a TCS's DHW zone (domesticHotWater)."""

STATUS_SCHEMA: Final = SCH_DHW_STATUS # type: ignore[misc]
TYPE: Final = SZ_DOMESTIC_HOT_WATER # type: ignore[misc]
TYPE: Final = SZ_DOMESTIC_HOT_WATER # type: ignore[misc] # used for RESTful API calls

SCH_SCHEDULE_GET: Final[vol.Schema] = SCH_GET_SCHEDULE_DHW # type: ignore[misc]
SCH_SCHEDULE_PUT: Final[vol.Schema] = SCH_PUT_SCHEDULE_DHW # type: ignore[misc]
Expand Down
7 changes: 6 additions & 1 deletion src/evohomeasync2/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

from __future__ import annotations

from datetime import timedelta as td, timezone
from typing import TYPE_CHECKING, Any, Final, NoReturn

from . import exceptions as exc
from .gateway import Gateway
from .schema import SCH_LOCN_STATUS
from .schema.const import (
SZ_COUNTRY,
SZ_CURRENT_OFFSET_MINUTES,
SZ_GATEWAY_ID,
SZ_GATEWAYS,
SZ_LOCATION,
Expand Down Expand Up @@ -47,7 +49,7 @@ class Location(_LocationDeprecated, EntityBase):
"""Instance of an account's location."""

STATUS_SCHEMA: Final[vol.Schema] = SCH_LOCN_STATUS
TYPE: Final = SZ_LOCATION
TYPE: Final = SZ_LOCATION # used for RESTful API calls

def __init__(self, client: EvohomeClient, config: _EvoDictT, /) -> None:
super().__init__(
Expand All @@ -62,6 +64,9 @@ def __init__(self, client: EvohomeClient, config: _EvoDictT, /) -> None:
self._config: Final[_EvoDictT] = config[SZ_LOCATION_INFO]
self._status: _EvoDictT = {}

# NOTE: the TZ of the evohome location, not of this system
self._tz = timezone(td(minutes=self.time_zone[SZ_CURRENT_OFFSET_MINUTES]))

self.gateways: list[Gateway] = []
self.gateway_by_id: dict[str, Gateway] = {}

Expand Down
10 changes: 5 additions & 5 deletions src/evohomeasync2/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@
SZ_ZONE_ID,
SZ_ZONES,
)
from .zone import ActiveFaultsBase, EntityBase, Zone
from .zone import ActiveFaultsMixin, EntityBase, Zone

if TYPE_CHECKING:
from datetime import datetime as dt

import voluptuous as vol

from . import Gateway, Location
from . import Gateway
from .schema import _DhwIdT, _EvoDictT, _EvoListT, _ScheduleT, _ZoneIdT


Expand Down Expand Up @@ -119,17 +119,17 @@ async def restore_schedules(self, *args: Any, **kwargs: Any) -> NoReturn:
)


class System(_SystemDeprecated, ActiveFaultsBase, EntityBase):
class System(_SystemDeprecated, ActiveFaultsMixin, EntityBase):
"""Instance of a gateway's TCS (temperatureControlSystem)."""

STATUS_SCHEMA: Final[vol.Schema] = SCH_TCS_STATUS
TYPE: Final = SZ_TEMPERATURE_CONTROL_SYSTEM # type: ignore[misc]
TYPE: Final = SZ_TEMPERATURE_CONTROL_SYSTEM # used for RESTful API calls

def __init__(self, gateway: Gateway, config: _EvoDictT, /) -> None:
super().__init__(config[SZ_SYSTEM_ID], gateway)

self.gateway = gateway
self.location: Location = gateway.location
self.location = gateway.location

self._config: Final[_EvoDictT] = {
k: v for k, v in config.items() if k not in (SZ_DHW, SZ_ZONES)
Expand Down
31 changes: 13 additions & 18 deletions src/evohomeasync2/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def __str__(self) -> str:
return f"{self.__class__.__name__}(id='{self.id}')"


class ActiveFaultsBase(EntityBase):
TYPE: _DhwIdT | _ZoneIdT # "temperatureZone", "domesticHotWater"
class ActiveFaultsMixin(EntityBase):
"""Provide the base for ActiveFaultsMixin."""

def __init__(self, child_id: str, parent: EntityBase, /) -> None:
super().__init__(child_id, parent._broker, parent._logger) # noqa: SLF001
Expand All @@ -85,12 +85,8 @@ def __init__(self, child_id: str, parent: EntityBase, /) -> None:
def active_faults(self) -> _EvoListT:
return self._active_faults

@active_faults.setter
def active_faults(self, value: _EvoListT) -> None:
self._active_faults = value

def _update_status(self, status: _EvoDictT) -> None:
last_logged = {}
self._active_faults, old_faults = status[SZ_ACTIVE_FAULTS], self._active_faults

def hash_(fault: _EvoDictT) -> str:
return f"{fault[SZ_FAULT_TYPE]}_{fault[SZ_SINCE]}"
Expand All @@ -99,28 +95,26 @@ def log_as_active(fault: _EvoDictT) -> None:
self._logger.warning(
f"Active fault: {self}: {fault[SZ_FAULT_TYPE]}, since {fault[SZ_SINCE]}"
)
last_logged[hash_(fault)] = dt.now()
self._last_logged[hash_(fault)] = dt.now().astimezone()

def log_as_resolved(fault: _EvoDictT) -> None:
self._logger.info(
f"Fault cleared: {self}: {fault[SZ_FAULT_TYPE]}, since {fault[SZ_SINCE]}"
)
del self._last_logged[hash_(fault)]

for fault in status[SZ_ACTIVE_FAULTS]:
if fault not in self.active_faults: # new active fault
for fault in self._active_faults:
if fault not in old_faults: # is a new fault
log_as_active(fault)

for fault in self.active_faults:
if fault not in status[SZ_ACTIVE_FAULTS]: # fault resolved
for fault in old_faults:
if fault not in self._active_faults: # fault has resolved
log_as_resolved(fault)

elif dt.now() - self._last_logged[hash_(fault)] > _ONE_DAY:
# re-log active faults that haven't been logged in the last 24 hours
elif self._last_logged[hash_(fault)] < dt.now().astimezone() - _ONE_DAY:
log_as_active(fault)

self.active_faults = status[SZ_ACTIVE_FAULTS]
self._last_logged |= last_logged


class _ZoneBaseDeprecated: # pragma: no cover
"""Deprecated attributes and methods removed from the evohome-client namespace."""
Expand All @@ -135,10 +129,11 @@ async def schedule(self) -> NoReturn:
)


class _ZoneBase(_ZoneBaseDeprecated, ActiveFaultsBase, EntityBase):
class _ZoneBase(_ZoneBaseDeprecated, ActiveFaultsMixin, EntityBase):
"""Provide the base for temperatureZone / domesticHotWater Zones."""

STATUS_SCHEMA: Final[vol.Schema] # type: ignore[misc]
TYPE: _DhwIdT | _ZoneIdT # "temperatureZone", "domesticHotWater"

SCH_SCHEDULE_GET: Final[vol.Schema] # type: ignore[misc]
SCH_SCHEDULE_PUT: Final[vol.Schema] # type: ignore[misc]
Expand Down Expand Up @@ -258,7 +253,7 @@ class Zone(_ZoneDeprecated, _ZoneBase, EntityBase):
"""Instance of a TCS's heating zone (temperatureZone)."""

STATUS_SCHEMA: Final = SCH_ZONE_STATUS # type: ignore[misc]
TYPE: Final = SZ_TEMPERATURE_ZONE # type: ignore[misc]
TYPE: Final = SZ_TEMPERATURE_ZONE # type: ignore[misc] # used for RESTful API calls

SCH_SCHEDULE_GET: Final = SCH_GET_SCHEDULE_ZONE # type: ignore[misc]
SCH_SCHEDULE_PUT: Final = SCH_PUT_SCHEDULE_ZONE # type: ignore[misc]
Expand Down
8 changes: 5 additions & 3 deletions tests/tests_rf/test_v2_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ async def _test_task_id(evo: evo2.EvohomeClient) -> None:
new_mode = {
SZ_MODE: ZoneMode.TEMPORARY_OVERRIDE,
SZ_STATE: DhwState.ON,
SZ_UNTIL_TIME: (dt.now() + td(hours=1)).strftime(API_STRFTIME),
SZ_UNTIL_TIME: (dt.now().astimezone() + td(hours=1)).strftime(API_STRFTIME),
}

result = await should_work(evo, HTTPMethod.PUT, put_url, json=new_mode)
Expand All @@ -118,7 +118,7 @@ async def _test_task_id(evo: evo2.EvohomeClient) -> None:
new_mode = {
SZ_MODE: ZoneMode.TEMPORARY_OVERRIDE,
SZ_STATE: DhwState.ON,
SZ_UNTIL_TIME: (dt.now() + td(hours=1)).strftime(API_STRFTIME),
SZ_UNTIL_TIME: (dt.now().astimezone() + td(hours=1)).strftime(API_STRFTIME),
}
_ = await should_work(evo, HTTPMethod.PUT, put_url, json=new_mode) # HTTP 201

Expand All @@ -129,7 +129,9 @@ async def _test_task_id(evo: evo2.EvohomeClient) -> None:
new_mode = { # NOTE: different capitalisation, until time
pascal_case(SZ_MODE): ZoneMode.TEMPORARY_OVERRIDE,
pascal_case(SZ_STATE): DhwState.ON,
pascal_case(SZ_UNTIL_TIME): (dt.now() + td(hours=2)).strftime(API_STRFTIME),
pascal_case(SZ_UNTIL_TIME): (dt.now().astimezone() + td(hours=2)).strftime(
API_STRFTIME
),
}
_ = await should_work(evo, HTTPMethod.PUT, put_url, json=new_mode)
_ = await wait_for_comm_task(evo, task_id)
Expand Down
2 changes: 1 addition & 1 deletion tests/tests_rf/test_v2_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def _test_tcs_mode(evo: evo2.EvohomeClient) -> None:
new_mode = {
SZ_SYSTEM_MODE: SystemMode.AWAY,
SZ_PERMANENT: True,
SZ_TIME_UNTIL: (dt.now() + td(hours=1)).strftime(API_STRFTIME),
SZ_TIME_UNTIL: (dt.now().astimezone() + td(hours=1)).strftime(API_STRFTIME),
}
_ = await should_work(evo, HTTPMethod.PUT, url, json=new_mode)

Expand Down

0 comments on commit 01f219c

Please sign in to comment.