-
-
Notifications
You must be signed in to change notification settings - Fork 48
feat: allow passing serializer on cache creation and implement JSONSerializer #462
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,8 @@ class MemcachedCache(BaseCache): | |
| the keys in the same format as passed. Furthermore all get methods | ||
| silently ignore key errors to not cause problems when untrusted user data | ||
| is passed to the get methods which is often the case in web applications. | ||
| This cache doesn't have a serializer since the underlying memcached client | ||
| libraries handle serialization internally." | ||
|
Comment on lines
+32
to
+33
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could implement this but it's not simple so I left it as is. |
||
|
|
||
| :param servers: a list or tuple of server addresses or alternatively | ||
| a :class:`memcache.Client` or a compatible client. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| import datetime | ||
| import logging | ||
| import typing as _t | ||
|
|
||
| from cachelib.base import BaseCache | ||
| from cachelib.serializers import BaseSerializer | ||
| from cachelib.serializers import MongoDbSerializer | ||
|
|
||
|
|
||
| class MongoDbCache(BaseCache): | ||
|
|
@@ -19,10 +19,12 @@ class MongoDbCache(BaseCache): | |
| :param default_timeout: Set the timeout in seconds after which cache entries | ||
| expire | ||
| :param key_prefix: A prefix that should be added to all keys. | ||
|
|
||
| :param serializer: An optional serializer to use instead of the default | ||
| BaseSerializer. The serializer must implement the | ||
| dumps and loads methods. | ||
| """ | ||
|
|
||
| serializer = BaseSerializer() | ||
| serializer: BaseSerializer = MongoDbSerializer() | ||
|
|
||
| def __init__( | ||
| self, | ||
|
|
@@ -31,25 +33,29 @@ def __init__( | |
| collection: _t.Optional[str] = "cache-collection", | ||
| default_timeout: int = 300, | ||
| key_prefix: _t.Optional[str] = None, | ||
| serializer: _t.Optional[BaseSerializer] = None, | ||
| **kwargs: _t.Any, | ||
| ): | ||
| super().__init__(default_timeout) | ||
| try: | ||
| import pymongo | ||
| except ImportError: | ||
| logging.warning("no pymongo module found") | ||
| except ImportError as err: | ||
| raise RuntimeError("no pymongo module found") from err | ||
|
Comment on lines
+42
to
+43
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the only cache backend that doesn't raise the error like this. Also |
||
|
|
||
| if client is None or isinstance(client, str): | ||
| client = pymongo.MongoClient(host=client) | ||
| client = pymongo.MongoClient(host=client, **kwargs) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| self.client = client[db][collection] | ||
| index_info = self.client.index_information() | ||
| all_keys = { | ||
| subkey[0] for value in index_info.values() for subkey in value["key"] | ||
| } | ||
| if "id" not in all_keys: | ||
| self.client.create_index("id", unique=True) | ||
|
|
||
| self.key_prefix = key_prefix or "" | ||
| self.collection = collection | ||
| if serializer is not None: | ||
| self.serializer = serializer | ||
|
|
||
| def _utcnow(self) -> _t.Any: | ||
| """Return a tz-aware UTC datetime representing the current time""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import json | ||
| import logging | ||
| import pickle | ||
| import typing as _t | ||
|
|
@@ -11,10 +12,8 @@ class BaseSerializer: | |
| used only by FileSystemCache which dumps/loads to/from a file stream. | ||
| """ | ||
|
|
||
| def _warn(self, e: pickle.PickleError) -> None: | ||
| logging.warning( | ||
| f"An exception has been raised during a pickling operation: {e}" | ||
| ) | ||
| def _warn(self, e: Exception) -> None: | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. broaden exception to allow for pickle and json errors handling without getting type errors |
||
| logging.warning(f"An exception has been raised during an operation: {e}") | ||
|
|
||
| def dump( | ||
| self, value: int, f: _t.IO[bytes], protocol: int = pickle.HIGHEST_PROTOCOL | ||
|
|
@@ -119,9 +118,58 @@ class ValkeySerializer(BaseRedisSerializer): | |
| class DynamoDbSerializer(RedisSerializer): | ||
| """Default serializer for DynamoDbCache.""" | ||
|
|
||
| def loads(self, value: _t.Any) -> _t.Any: | ||
| """The reversal of :meth:`dump_object`. This might be called with | ||
| None. | ||
| """ | ||
| value = value.value | ||
| return super().loads(value) | ||
| pass | ||
|
|
||
|
|
||
| class MongoDbSerializer(BaseSerializer): | ||
| """Default serializer for MongoDbCache.""" | ||
|
|
||
| pass | ||
|
Comment on lines
+124
to
+127
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was not implemented for some reason |
||
|
|
||
|
|
||
| class JSONSerializer(BaseSerializer): | ||
| """Generic JSON serializer for caches. | ||
|
|
||
| Use this serializer if you want to serialize to JSON instead of pickle. | ||
| Note that JSON does not support serialization of Python bytes objects. | ||
| If you need to cache bytes, use the default pickle-based serializer. | ||
| This serializer is not used by default. | ||
| """ | ||
|
|
||
| def dump( | ||
| self, value: _t.Any, f: _t.IO[bytes], *args: _t.Any, **kwargs: _t.Any | ||
| ) -> None: | ||
| try: | ||
| f.write(json.dumps(value, *args, **kwargs).encode()) | ||
| except (TypeError, ValueError) as e: | ||
| self._warn(e) | ||
|
|
||
| def load(self, f: _t.IO[bytes], *args: _t.Any, **kwargs: _t.Any) -> _t.Any: | ||
| try: | ||
| data = json.loads(f.read(), *args, **kwargs) | ||
| except (TypeError, ValueError) as e: | ||
| self._warn(e) | ||
| return None | ||
| else: | ||
| return data | ||
|
|
||
| def dumps( | ||
| self, value: _t.Any, *args: _t.Any, **kwargs: _t.Any | ||
| ) -> _t.Optional[bytes]: | ||
| try: | ||
| serialized = json.dumps(value, *args, **kwargs).encode() | ||
| except (TypeError, ValueError) as e: | ||
| self._warn(e) | ||
| return None | ||
| return serialized | ||
|
|
||
| def loads( | ||
| self, bvalue: _t.Union[str, bytes, bytearray], *args: _t.Any, **kwargs: _t.Any | ||
| ) -> _t.Any: | ||
| try: | ||
| data = json.loads(bvalue, *args, **kwargs) | ||
| except (TypeError, ValueError) as e: | ||
| self._warn(e) | ||
| return None | ||
| else: | ||
| return data | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the JSON serializer will be broken for
dynamodbsince this unpacking was happening inside the serializer.loadsfunction I moved it here since it makes more sense and will allow other serializers to be used without special handling.This change doesn't affect end user in anything while allowing for using other serializers.