Is your feature request related to a problem? Please describe.
I'm making some sort of site with Pokémon data using Sanic that pulls PokéAPI data to generate pages, and your package was a good choice due to how well it composes with Sanic's preference for asynchronous request handlers.
Describe the solution you'd like
Unfortunately, because the default cache class does not interact with the file system in any way, the cache is always invalidated when the app closes. I would like for it to persist between app runs so people who visit the same page do not send another request against the PokéAPI, if there was any downtime in-between.
Describe alternatives you've considered
Both pokepy
and pokebase
have file system caching, but both of these packages are completely synchronous, which means the request can block the main loop, making my app unable to handle multiple requests in parallel.
Additional context
There should be a cache
keyword argument to AiopokeClient.__init__
, which should be set to the Cache
class, or its instance.
Changes to AiopokeClient.__init__
:
class AiopokeClient(metaclass=MetaClient):
http: HttpClient
- _cache: Cache
+ cache: Cache
- def __init__(self, *, session=None) -> None:
+ def __init__(self, *, session=None, cache=None) -> None:
self.http = HttpClient(session=session)
- self._cache = Cache()
+ if cache is None:
+ self.cache = Cache()
+ elif isinstance(cache, type):
+ self.cache = cache()
+ else:
+ self.cache = cache
Url.link(self)
Sprite.link(self)
Having the default for cache
be None
is to make sure we don't have mutable default arguments, which are considered bad practice.
An example implementation for a file system cache class could look like the one below. Shelves from the shelve
standard library module can store arbitrary data types as values, but can only use strings as keys. However, it's still suitable for our purposes:
import shelve
import os
def _shelf_items(shelf):
"""
Iterates through a shelve.Shelf's items.
This is a workaround because the dict attribute of shelf objects
does not have the .keys(), .values(), or .items() methods.
"""
k = shelf.dict.firstkey()
while k is not None:
k = k.decode(shelf.keyencoding)
yield (k, shelf[k])
k = shelf.dict.nextkey(k)
class FileSystemCache:
def __init__(self, filename="aiopoke_cache.dat"):
if os.path.exists(filename):
if not os.path.isfile(filename):
raise ValueError(f"{filename!r} is not a file")
# Do not open the file in write mode,
# otherwise, the cache will be overridden
else:
# There is no 'as' keyword here because creating the file is
# merely for side effects
# Must use 'x' argument (create). If it were used with the
# default, 'r', would error, saying the file does not exist
with open(filename, "x"):
pass
self.filename = filename
def get(self, id: Union[str, int]) -> Any:
with shelve.open(self.filename) as shelf:
return shelf.get(str(id))
def put(self, id: Union[str, int], obj: Any) -> None:
with shelve.open(self.filename, "w") as shelf:
shelf[str(id)] = obj
def has(self, obj: Any) -> bool:
with shelve.open(self.filename) as shelf:
for _, v in _shelf_items(shelf):
if obj == v:
return True
return False