diff --git a/README.rst b/README.rst index 9b7c65e5d..f390abaa0 100644 --- a/README.rst +++ b/README.rst @@ -166,7 +166,7 @@ How does it work Aiocache provides 3 main entities: - **backends**: Allow you specify which backend you want to use for your cache. Currently supporting: SimpleMemoryCache, RedisCache using redis_ and MemCache using aiomcache_. -- **serializers**: Serialize and deserialize the data between your code and the backends. This allows you to save any Python object into your cache. Currently supporting: StringSerializer, PickleSerializer, JsonSerializer, and MsgPackSerializer. But you can also build custom ones. +- **serializers**: Serialize and deserialize the data between your code and the backends. This allows you to save any Python object into your cache. Currently supporting: StringSerializer, PickleSerializer, JsonSerializer, MsgPackSerializer, and YamlSerializer. But you can also build custom ones. - **plugins**: Implement a hooks system that allows to execute extra behavior before and after of each command. If you are missing an implementation of backend, serializer or plugin you think it could be interesting for the package, do not hesitate to open a new issue. diff --git a/aiocache/serializers/__init__.py b/aiocache/serializers/__init__.py index c7499335b..a6c079239 100644 --- a/aiocache/serializers/__init__.py +++ b/aiocache/serializers/__init__.py @@ -6,6 +6,7 @@ NullSerializer, PickleSerializer, StringSerializer, + YamlSerializer, ) logger = logging.getLogger(__name__) @@ -28,4 +29,5 @@ "PickleSerializer", "JsonSerializer", "MsgPackSerializer", + "YamlSerializer", ] diff --git a/aiocache/serializers/serializers.py b/aiocache/serializers/serializers.py index 58a5b61b1..7a61b9756 100644 --- a/aiocache/serializers/serializers.py +++ b/aiocache/serializers/serializers.py @@ -17,6 +17,12 @@ msgpack = None logger.debug("msgpack not installed, MsgPackSerializer unavailable") +try: + import yaml # noqa: I900 +except ImportError: + yaml = None + logger.debug("yaml not installed, YamlSerializer unavailable") + _NOT_SET = object() @@ -197,3 +203,35 @@ def loads(self, value): if value is None: return None return msgpack.loads(value, raw=raw, use_list=self.use_list) + + +class YamlSerializer(BaseSerializer): + """ + Transform data to YAML string with ``yaml.dump`` and ``yaml.load`` to retrieve it back. You need + to have ``yaml`` installed in order to be able to use this serializer. + """ + + def __init__(self, *args, **kwargs): + if not yaml: + raise RuntimeError("yaml not installed, YamlSerializer unavailable") + super().__init__(*args, **kwargs) + + def dumps(self, value): + """ + Serialize the received value using ``yaml.dump``. + + :param value: obj + :returns: str + """ + return yaml.dump(value) + + def loads(self, value): + """ + Deserialize value using ``yaml.load``. + + :param value: str + :returns: obj + """ + if value is None: + return None + return yaml.safe_load(value) diff --git a/docs/serializers.rst b/docs/serializers.rst index b68b20f6f..bd8ee04b9 100644 --- a/docs/serializers.rst +++ b/docs/serializers.rst @@ -54,6 +54,14 @@ MsgPackSerializer .. autoclass:: aiocache.serializers.MsgPackSerializer :members: +.. _yamlserializer: + +YamlSerializer +-------------- + +.. autoclass:: aiocache.serializers.YamlSerializer + :members: + In case the current serializers are not covering your needs, you can always define your custom serializer as shown in ``examples/serializer_class.py``: .. literalinclude:: ../examples/serializer_class.py @@ -66,4 +74,4 @@ You can also use marshmallow as your serializer (``examples/marshmallow_serializ :language: python :linenos: -By default cache backends assume they are working with ``str`` types. If your custom implementation transform data to bytes, you will need to set the class attribute ``encoding`` to ``None``. +By default cache backends assume they are working with ``str`` types. If your custom implementation transform data to bytes, you will need to set the class attribute ``encoding`` to ``None``. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 983c16943..f19920900 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ pytest-asyncio==0.25.3 pytest-cov==6.0.0 pytest-mock==3.14.0 redis==5.2.1 +pyyaml==6.0.2 \ No newline at end of file diff --git a/tests/ut/test_serializers.py b/tests/ut/test_serializers.py index 33835531b..77d7ffa8c 100644 --- a/tests/ut/test_serializers.py +++ b/tests/ut/test_serializers.py @@ -11,6 +11,7 @@ NullSerializer, PickleSerializer, StringSerializer, + YamlSerializer, ) @@ -18,6 +19,7 @@ TYPES = [1, 2.0, "hi", True, ["1", 1], {"key": "value"}, Dummy(1, 2)] JSON_TYPES = [1, 2.0, "hi", True, ["1", 1], {"key": "value"}] +YAML_TYPES = [1, 2.0, "hi", True, ["1", 1], {"key": "value"}] class TestNullSerializer: @@ -173,3 +175,43 @@ def test_dumps_and_loads_dict(self): "a": [1, 2, ["1", 2]], "b": {"b": 1, "c": [1, 2]}, } + + +class TestYamlSerializer: + def test_init(self): + serializer = YamlSerializer() + assert isinstance(serializer, BaseSerializer) + assert serializer.DEFAULT_ENCODING == "utf-8" + assert serializer.encoding == "utf-8" + + def test_init_fails_if_yaml_not_installed(self): + with mock.patch("aiocache.serializers.serializers.yaml", None): + with pytest.raises(RuntimeError): + YamlSerializer() + assert JsonSerializer(), "Other serializers should still initialize" + + @pytest.mark.parametrize("obj", YAML_TYPES) + def test_set_types(self, obj): + serializer = YamlSerializer() + assert serializer.loads(serializer.dumps(obj)) == obj + + def test_dumps(self): + serializer = YamlSerializer() + assert serializer.dumps({"hi": 1}) == "hi: 1\n" + + def test_dumps_with_none(self): + serializer = YamlSerializer() + assert serializer.dumps(None) == "null\n...\n" + + def test_loads(self): + serializer = YamlSerializer() + assert serializer.loads("hi: 1\n") == {"hi": 1} + + def test_loads_with_none(self): + serializer = YamlSerializer() + assert serializer.loads(None) is None + + def test_dumps_and_loads(self): + obj = {"hi": 1} + serializer = YamlSerializer() + assert serializer.loads(serializer.dumps(obj)) == obj