Skip to content
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

async cache #200

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
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
Loading