8000 Cache zoneinfo for smart devices by sdb9696 · Pull Request #1156 · python-kasa/python-kasa · GitHub
[go: up one dir, main page]

Skip to content

Cache zoneinfo for smart devices #1156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions kasa/cachedzoneinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Module for caching ZoneInfos."""

from __future__ import annotations

import asyncio

from zoneinfo import ZoneInfo


class CachedZoneInfo(ZoneInfo):
"""Cache ZoneInfo objects."""

_cache: dict[str, ZoneInfo] = {}

@classmethod
async def get_cached_zone_info(cls, time_zone_str: str) -> ZoneInfo:
"""Get a cached zone info object."""
if cached := cls._cache.get(time_zone_str):
return cached
loop = asyncio.get_running_loop()
zinfo = await loop.run_in_executor(None, _get_zone_info, time_zone_str)
cls._cache[time_zone_str] = zinfo
return zinfo


def _get_zone_info(time_zone_str: str) -> ZoneInfo:
"""Get a time zone object for the given time zone string."""
return ZoneInfo(time_zone_str)
30 changes: 4 additions & 26 deletions kasa/iot/iottimezone.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from __future__ import annotations

import asyncio
import logging
from datetime import datetime, tzinfo

from zoneinfo import ZoneInfo
from ..cachedzoneinfo import CachedZoneInfo

_LOGGER = logging.getLogger(__name__)

Expand All @@ -17,10 +16,10 @@ async def get_timezone(index: int) -> tzinfo:
_LOGGER.error(
"Unexpected index %s not configured as a timezone, defaulting to UTC", index
)
return await _CachedZoneInfo.get_cached_zone_info("Etc/UTC")
return await CachedZoneInfo.get_cached_zone_info("Etc/UTC")

name = TIMEZONE_INDEX[index]
return await _CachedZoneInfo.get_cached_zone_info(name)
return await CachedZoneInfo.get_cached_zone_info(name)


async def get_timezone_index(name: str) -> int:
Expand All @@ -30,7 +29,7 @@ async def get_timezone_index(name: str) -> int:
return rev[name]

# Try to find a supported timezone matching dst true/false
zone = await _CachedZoneInfo.get_cached_zone_info(name)
zone = await CachedZoneInfo.get_cached_zone_info(name)
now = datetime.now()
winter = datetime(now.year, 1, 1, 12)
summer = datetime(now.year, 7, 1, 12)
Expand All @@ -43,27 +42,6 @@ async def get_timezone_index(name: str) -> int:
raise ValueError("Device does not support timezone %s", name)


class _CachedZoneInfo(ZoneInfo):
"""Cache zone info objects."""

_cache: dict[str, ZoneInfo] = {}

@classmethod
async def get_cached_zone_info(cls, time_zone_str: str) -> ZoneInfo:
"""Get a cached zone info object."""
if cached := cls._cache.get(time_zone_str):
return cached
loop = asyncio.get_running_loop()
zinfo = await loop.run_in_executor(None, _get_zone_info, time_zone_str)
cls._cache[time_zone_str] = zinfo
return zinfo


def _get_zone_info(time_zone_str: str) -> ZoneInfo:
"""Get a time zone object for the given time zone string."""
return ZoneInfo(time_zone_str)


TIMEZONE_INDEX = {
0: "Etc/GMT+12",
1: "Pacific/Samoa",
Expand Down
19 changes: 13 additions & 6 deletions kasa/smart/modules/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from time import mktime
from typing import cast

from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from zoneinfo import ZoneInfoNotFoundError

from ...cachedzoneinfo import CachedZoneInfo
from ...feature import Feature
from ..smartmodule import SmartModule

Expand All @@ -18,6 +19,8 @@ class Time(SmartModule):
REQUIRED_COMPONENT = "time"
QUERY_GETTER_NAME = "get_device_time"

_timezone: tzinfo = timezone.utc

def _initialize_features(self):
"""Initialize features after the initial update."""
self._add_feature(
Expand All @@ -32,21 +35,25 @@ def _initialize_features(self):
)
)

@property
def timezone(self) -> tzinfo:
"""Return current timezone."""
async def _post_update_hook(self):
"""Perform actions after a device update."""
td = timedelta(minutes=cast(float, self.data.get("time_diff")))
if region := self.data.get("region"):
try:
# Zoneinfo will return a DST aware object
tz: tzinfo = ZoneInfo(region)
tz: tzinfo = await CachedZoneInfo.get_cached_zone_info(region)
except ZoneInfoNotFoundError:
tz = timezone(td, region)
else:
# in case the device returns a blank region this will result in the
# tzname being a UTC offset
tz = timezone(td)
return tz
self._timezone = tz

@property
def timezone(self) -> tzinfo:
"""Return current timezone."""
return self._timezone

@property
def time(self) -> datetime:
Expand Down
Loading
0