Skip to content

Commit

Permalink
async cache
Browse files Browse the repository at this point in the history
  • Loading branch information
positiveviking committed Jun 28, 2024
1 parent b2e6488 commit 062413f
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 33 deletions.
14 changes: 9 additions & 5 deletions custom_components/gismeteo/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ async def _async_get_data(

if self._cache and cache_fname is not None:
cache_fname += ".xml"
if self._cache.is_cached(cache_fname):
if await self._cache.is_cached(cache_fname):
_LOGGER.debug("Cached response used")
return self._cache.read_cache(cache_fname)
return await self._cache.read_cache(cache_fname)

headers = {}
if as_browser:
Expand All @@ -220,7 +220,7 @@ async def _async_get_data(
data = await resp.text()

if self._cache and cache_fname is not None and data:
self._cache.save_cache(cache_fname, data)
await self._cache.save_cache(cache_fname, data)

return data

Expand All @@ -234,9 +234,13 @@ async def async_update_location(self) -> None:

url = (
ENDPOINT_URL
+ f"/cities/?lat={self._attributes[ATTR_LATITUDE]}&lng={self._attributes[ATTR_LONGITUDE]}&count=1&lang=en"
+ f"/cities/?lat={self._attributes[ATTR_LATITUDE]}"
+ f"&lng={self._attributes[ATTR_LONGITUDE]}&count=1&lang=en"
)
cache_fname = (
f"location_{self._attributes[ATTR_LATITUDE]}"
+ f"_{self._attributes[ATTR_LONGITUDE]}"
)
cache_fname = f"location_{self._attributes[ATTR_LATITUDE]}_{self._attributes[ATTR_LONGITUDE]}"

response = await self._async_get_data(url, cache_fname)
try:
Expand Down
41 changes: 24 additions & 17 deletions custom_components/gismeteo/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
import logging
import os
import time
from typing import Any, Dict, Optional
from typing import Any

import aiofiles
from aiofiles import os as aio_os

_LOGGER = logging.getLogger(__name__)


class Cache:
"""Data caching class."""

def __init__(self, params: Optional[Dict[str, Any]] = None):
def __init__(self, params: dict[str, Any] | None = None):
"""Initialize cache."""
_LOGGER.debug("Initializing cache")
params = params or {}
Expand Down Expand Up @@ -54,43 +57,47 @@ def _get_file_path(self, file_name: str) -> str:
file_name = ".".join((self._domain, file_name))
return os.path.join(self._cache_dir, file_name)

def cached_for(self, file_name: str) -> Optional[float]:
async def cached_for(self, file_name: str) -> float | None:
"""Return caching time of file if exists. Otherwise None."""
file_path = self._get_file_path(file_name)
if not os.path.exists(file_path) or not os.path.isfile(file_path):
if not await aio_os.path.exists(file_path) or not await aio_os.path.isfile(
file_path
):
return None

file_time = os.path.getmtime(file_path)
file_time = await aio_os.path.getmtime(file_path)
return time.time() - file_time

def is_cached(self, file_name: str, cache_time: int = 0) -> bool:
async def is_cached(self, file_name: str, cache_time: int = 0) -> bool:
"""Return True if cache file is exists."""
file_path = self._get_file_path(file_name)
if not os.path.exists(file_path) or not os.path.isfile(file_path):
if not await aio_os.path.exists(file_path) or not await aio_os.path.isfile(
file_path
):
return False

file_time = os.path.getmtime(file_path)
file_time = await aio_os.path.getmtime(file_path)
cache_time = max(cache_time, self._cache_time)
return (file_time + cache_time) > time.time()

def read_cache(self, file_name: str, cache_time: int = 0) -> Optional[Any]:
async def read_cache(self, file_name: str, cache_time: int = 0) -> Any | None:
"""Read cached data."""
file_path = self._get_file_path(file_name)
_LOGGER.debug("Read cache file %s", file_path)
if not self.is_cached(file_name, cache_time):
if not await self.is_cached(file_name, cache_time):
return None

with open(file_path, encoding="utf-8") as fp:
return fp.read()
async with aiofiles.open(file_path, encoding="utf-8") as fp:
return await fp.read()

def save_cache(self, file_name: str, content: Any) -> None:
async def save_cache(self, file_name: str, content: Any) -> None:
"""Save data to cache."""
if self._cache_dir:
if not os.path.exists(self._cache_dir):
os.makedirs(self._cache_dir)
if not await aio_os.path.exists(self._cache_dir):
await aio_os.makedirs(self._cache_dir)

file_path = self._get_file_path(file_name)
_LOGGER.debug("Store cache file %s", file_path)

with open(file_path, "w", encoding="utf-8") as fp:
fp.write(content)
async with aiofiles.open(file_path, "w", encoding="utf-8") as fp:
await fp.write(content)
3 changes: 2 additions & 1 deletion custom_components/gismeteo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/Limych/ha-gismeteo/issues",
"requirements": [
"beautifulsoup4~=4.12"
"beautifulsoup4~=4.12",
"aiofiles>=24.0.0"
],
"version": "3.0.0"
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
homeassistant>=2024.4.0
pip>=24.0
beautifulsoup4~=4.12
aiofiles>=24.0.0
20 changes: 10 additions & 10 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,41 @@ def test__get_file_path():
assert cache._get_file_path("file_name.ext") == "/some/dir/dmn.file_name.ext"


def test_is_cached(config, cache_dir):
async def test_is_cached(config, cache_dir):
"""Cache controller tests."""
config["clean_dir"] = False
cache = Cache(config)

for i in cache_dir["old"]:
assert cache.is_cached(i) is False
assert await cache.is_cached(i) is False

for i in cache_dir["new"]:
assert cache.is_cached(i) is True
assert await cache.is_cached(i) is True

for _ in range(8):
file_name = os.urandom(3).hex()
assert cache.is_cached(file_name) is False
assert await cache.is_cached(file_name) is False


def test_read_cache(config, cache_dir):
async def test_read_cache(config, cache_dir):
"""Cache controller tests."""
cache = Cache(config)

for i in cache_dir["old"]:
assert cache.read_cache(i) is None
assert await cache.read_cache(i) is None

for i, con in cache_dir["new"].items():
assert cache.read_cache(i) == con
assert await cache.read_cache(i) == con


def test_save_cache(config):
async def test_save_cache(config):
"""Cache controller tests."""
config["cache_dir"] = os.path.join(config["cache_dir"], os.urandom(3).hex())
cache = Cache(config)

for _ in range(8):
file_name = os.urandom(5).hex()
content = os.urandom(7).hex()
cache.save_cache(file_name, content)
await cache.save_cache(file_name, content)

assert cache.read_cache(file_name) == content
assert await cache.read_cache(file_name) == content

0 comments on commit 062413f

Please sign in to comment.