# Note: Almost all serializer logic is covered by parametrized integration tests. # Any additional serializer-specific tests can go here. import gzip import json import pickle import sys from importlib import reload from unittest.mock import patch from uuid import uuid4 import pytest from cattr import BaseConverter, GenConverter from requests_cache import ( CachedResponse, CachedSession, CattrStage, SerializerPipeline, Stage, json_serializer, safe_pickle_serializer, utf8_encoder, ) from tests.conftest import skip_missing_deps def test_stdlib_json(): import requests_cache.serializers.preconf with patch.dict(sys.modules, {'ujson': None, 'cattr.preconf.ujson': None}): reload(requests_cache.serializers.preconf) from requests_cache.serializers.preconf import json as module_json assert module_json is json reload(requests_cache.serializers.preconf) @skip_missing_deps('ujson') def test_ujson(): import ujson from requests_cache.serializers.preconf import json as module_json assert module_json is ujson @skip_missing_deps('bson') def test_standalone_bson(): """Handle different method names for standalone bson codec vs pymongo""" import requests_cache.serializers.preconf # Can't easily install both pymongo and bson (standalone) for tests; # Using json module here since it has same functions as bson (standalone) with patch.dict(sys.modules, {'bson': json, 'pymongo': None}): reload(requests_cache.serializers.preconf) bson_functions = requests_cache.serializers.preconf._get_bson_functions() assert bson_functions == {'dumps': 'dumps', 'loads': 'loads'} reload(requests_cache.serializers.preconf) def test_optional_dependencies(): import requests_cache.serializers.preconf with patch.dict(sys.modules, {'bson': None, 'itsdangerous': None, 'yaml': None}): reload(requests_cache.serializers.preconf) from requests_cache.serializers.preconf import ( bson_serializer, safe_pickle_serializer, yaml_serializer, ) for obj in [bson_serializer, yaml_serializer]: with pytest.raises(ImportError): obj.dumps('') with pytest.raises(ImportError): obj.loads('') with pytest.raises(ImportError): safe_pickle_serializer('') reload(requests_cache.serializers.preconf) @skip_missing_deps('itsdangerous') def test_cache_signing(tempfile_path): from itsdangerous import Signer from itsdangerous.exc import BadSignature serializer = safe_pickle_serializer(secret_key=str(uuid4())) session = CachedSession(tempfile_path, serializer=serializer) assert isinstance(session.cache.responses.serializer.stages[-1].obj, Signer) # Simple serialize/deserialize round trip response = CachedResponse() session.cache.responses['key'] = response assert session.cache.responses['key'] == response # Without the same signing key, the item shouldn't be considered safe to deserialize serializer = safe_pickle_serializer(secret_key='a different key') session = CachedSession(tempfile_path, serializer=serializer) with pytest.raises(BadSignature): session.cache.responses['key'] def test_custom_serializer(tempfile_path): serializer = SerializerPipeline( [ json_serializer, # Serialize to a JSON string utf8_encoder, # Encode to bytes Stage(dumps=gzip.compress, loads=gzip.decompress), # Compress ] ) session = CachedSession(tempfile_path, serializer=serializer) response = CachedResponse() session.cache.responses['key'] = response assert session.cache.responses['key'] == response def test_plain_pickle(tempfile_path): """`requests.Response` modifies pickling behavior. If plain `pickle` is used as a serializer, serializing `CachedResponse` should still work as expected. """ session = CachedSession(tempfile_path, serializer=pickle) response = CachedResponse() session.cache.responses['key'] = response assert session.cache.responses['key'] == response assert session.cache.responses['key'].expires is None def test_cattrs_compat(): """CattrStage should be compatible with BaseConverter, which doesn't support the omit_if_default keyword arg. """ stage_1 = CattrStage() assert isinstance(stage_1.converter, GenConverter) stage_2 = CattrStage(factory=BaseConverter) assert isinstance(stage_2.converter, BaseConverter)