-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
517 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from __future__ import annotations | ||
|
||
from datetime import datetime, timedelta | ||
from typing import AsyncIterable, Generic, Iterable | ||
from urllib.parse import urlparse | ||
|
||
from django.core.cache import BaseCache, caches # type: ignore[import] | ||
from django.core.cache.backends.base import DEFAULT_TIMEOUT # type: ignore[import] | ||
|
||
from cachetory.interfaces.backends.async_ import AsyncBackend | ||
from cachetory.interfaces.backends.private import WireT | ||
from cachetory.private.datetime import make_time_to_live | ||
|
||
_SENTINEL = object() | ||
|
||
|
||
class DjangoBackend(AsyncBackend[WireT], Generic[WireT]): | ||
"""Django cache adapter.""" | ||
|
||
__slots__ = ("_cache",) | ||
|
||
@classmethod | ||
def from_url(cls, url: str) -> DjangoBackend[WireT]: | ||
return DjangoBackend(caches[urlparse(url).hostname]) | ||
|
||
def __init__(self, cache: BaseCache) -> None: | ||
self._cache = cache | ||
|
||
async def get(self, key: str) -> WireT: | ||
if (value := await self._cache.aget(key, _SENTINEL)) is not _SENTINEL: | ||
return value | ||
raise KeyError(key) | ||
|
||
async def get_many(self, *keys: str) -> AsyncIterable[tuple[str, WireT]]: | ||
for item in (await self._cache.aget_many(keys)).items(): | ||
yield item | ||
|
||
async def set( # noqa: A003 | ||
self, | ||
key: str, | ||
value: WireT, | ||
*, | ||
time_to_live: timedelta | None = None, | ||
if_not_exists: bool = False, | ||
) -> bool: | ||
timeout = self._to_timeout(time_to_live) | ||
if if_not_exists: | ||
await self._cache.aget_or_set(key, value, timeout) | ||
else: | ||
await self._cache.aset(key, value, timeout) | ||
return True | ||
|
||
async def set_many(self, items: Iterable[tuple[str, WireT]]) -> None: | ||
await self._cache.aset_many(dict(items)) | ||
|
||
async def delete(self, key: str) -> bool: | ||
return await self._cache.adelete(key) | ||
|
||
async def clear(self) -> None: | ||
await self._cache.aclear() | ||
|
||
async def expire_in(self, key: str, time_to_live: timedelta | None = None) -> None: | ||
await self._cache.atouch(key, self._to_timeout(time_to_live)) | ||
|
||
async def expire_at(self, key: str, deadline: datetime | None) -> None: | ||
await self.expire_in(key, make_time_to_live(deadline)) | ||
|
||
@staticmethod | ||
def _to_timeout(time_to_live: timedelta | None) -> float: | ||
return time_to_live.total_seconds() if time_to_live is not None else DEFAULT_TIMEOUT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from __future__ import annotations | ||
|
||
from datetime import datetime, timedelta | ||
from typing import Generic, Iterable | ||
from urllib.parse import urlparse | ||
|
||
from django.core.cache import BaseCache, caches # type: ignore[import] | ||
from django.core.cache.backends.base import DEFAULT_TIMEOUT # type: ignore[import] | ||
|
||
from cachetory.interfaces.backends.private import WireT | ||
from cachetory.interfaces.backends.sync import SyncBackend | ||
from cachetory.private.datetime import make_time_to_live | ||
|
||
_SENTINEL = object() | ||
|
||
|
||
class DjangoBackend(SyncBackend[WireT], Generic[WireT]): | ||
"""Django cache adapter.""" | ||
|
||
__slots__ = ("_cache",) | ||
|
||
@classmethod | ||
def from_url(cls, url: str) -> DjangoBackend[WireT]: | ||
return DjangoBackend(caches[urlparse(url).hostname]) | ||
|
||
def __init__(self, cache: BaseCache) -> None: | ||
self._cache = cache | ||
|
||
def get(self, key: str) -> WireT: | ||
if (value := self._cache.get(key, _SENTINEL)) is not _SENTINEL: | ||
return value | ||
raise KeyError(key) | ||
|
||
def get_many(self, *keys: str) -> Iterable[tuple[str, WireT]]: | ||
return self._cache.get_many(keys).items() | ||
|
||
def set( # noqa: A003 | ||
self, | ||
key: str, | ||
value: WireT, | ||
*, | ||
time_to_live: timedelta | None = None, | ||
if_not_exists: bool = False, | ||
) -> bool: | ||
timeout = self._to_timeout(time_to_live) | ||
if if_not_exists: | ||
self._cache.get_or_set(key, value, timeout) | ||
else: | ||
self._cache.set(key, value, timeout) | ||
return True | ||
|
||
def set_many(self, items: Iterable[tuple[str, WireT]]) -> None: | ||
self._cache.set_many(dict(items)) | ||
|
||
def delete(self, key: str) -> bool: | ||
return self._cache.delete(key) | ||
|
||
def clear(self) -> None: | ||
self._cache.clear() | ||
|
||
def expire_in(self, key: str, time_to_live: timedelta | None = None) -> None: | ||
self._cache.touch(key, self._to_timeout(time_to_live)) | ||
|
||
def expire_at(self, key: str, deadline: datetime | None) -> None: | ||
self.expire_in(key, make_time_to_live(deadline)) | ||
|
||
@staticmethod | ||
def _to_timeout(time_to_live: timedelta | None) -> float: | ||
return time_to_live.total_seconds() if time_to_live is not None else DEFAULT_TIMEOUT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,15 @@ | ||
from __future__ import annotations | ||
|
||
from datetime import datetime, timedelta, timezone | ||
from typing import Optional | ||
|
||
ZERO_TIMEDELTA = timedelta() | ||
|
||
|
||
def make_deadline(time_to_live: Optional[timedelta] = None) -> Optional[datetime]: | ||
def make_deadline(time_to_live: timedelta | None = None) -> datetime | None: | ||
return datetime.now(timezone.utc) + time_to_live if time_to_live is not None else None | ||
|
||
|
||
def make_time_to_live(deadline: datetime | None = None) -> timedelta | None: | ||
if deadline is None: | ||
return None | ||
return max(deadline - datetime.now(timezone.utc), ZERO_TIMEDELTA) |
Oops, something went wrong.