diff options
| author | Jordan Cook <jordan.cook@pioneer.com> | 2021-05-07 15:50:14 -0500 |
|---|---|---|
| committer | Jordan Cook <jordan.cook@pioneer.com> | 2021-06-11 11:13:16 -0500 |
| commit | d1d5b00b607ee41cb44b2ec0ab683bc2e8f3e81e (patch) | |
| tree | 9ae76e78874953b18b8f647950b55329ca2bbcd5 /requests_cache/serializers | |
| parent | d1e154aa8e6ba104320fd03b1023eefb16df3f57 (diff) | |
| download | requests-cache-d1d5b00b607ee41cb44b2ec0ab683bc2e8f3e81e.tar.gz | |
Experiment with adding a JSON serializer
Diffstat (limited to 'requests_cache/serializers')
| -rw-r--r-- | requests_cache/serializers/json.py | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/requests_cache/serializers/json.py b/requests_cache/serializers/json.py new file mode 100644 index 0000000..cc2975e --- /dev/null +++ b/requests_cache/serializers/json.py @@ -0,0 +1,78 @@ +# TODO: Handle CachedHTTPResponse, PreparedRequest, RequestsCookieJar, CachedResponse (history) +import json +from base64 import b64decode, b64encode +from datetime import datetime, timedelta +from json import JSONDecoder, JSONEncoder +from typing import Union + +from requests.cookies import RequestsCookieJar, cookiejar_from_dict +from requests.structures import CaseInsensitiveDict + +from ..response import CachedResponse + + +class ResponseJSONSerializer: + def dumps(self, response: CachedResponse) -> str: + """Serialize a CachedResponse into JSON""" + return json.dumps(response.to_dict(), cls=ResponseJSONEncoder, indent=2) + + def loads(self, obj: str) -> CachedResponse: + """Deserialize a CachedResponse from JSON""" + response = json.loads(obj, cls=ResponseJSONDecoder) + + response['_content'] = b64decode(response['_content'].encode()) + response['_raw_response_attrs'] = response.pop('raw') + response['_request_attrs'] = response.pop('request') + response['cookies'] = cookiejar_from_dict(response.get('cookies', {})) + response['headers'] = CaseInsensitiveDict(response.get('headers', {})) + response['history'] = [self.loads(redirect) for redirect in response.get('history', [])] + + return CachedResponse(**response) + + +class ResponseJSONEncoder(JSONEncoder): + """Serialize a CachedResponse as JSON""" + + def default(self, obj): + if isinstance(obj, bytes): + return b64encode(obj).decode() + if isinstance(obj, datetime): + return obj.isoformat() + elif isinstance(obj, RequestsCookieJar): + return dict(obj) + # elif isinstance(obj, CookieJar): + # cookies = RequestsCookieJar() + # cookies.update(obj) + # return dict(cookies) + elif isinstance(obj, timedelta): + return { + '__type__': 'timedelta', + 'days': obj.days, + 'seconds': obj.seconds, + 'microseconds': obj.microseconds, + } + return super().default(obj) + + +class ResponseJSONDecoder(JSONDecoder): + """Deserialize a CachedResponse from JSON""" + + def __init__(self, **kwargs): + super().__init__(object_hook=self.object_hook, **kwargs) + + def object_hook(self, obj): + """Check for and handle custom types before they get deserialized by JSONDecoder""" + if isinstance(obj, str): + return try_parse_isoformat(obj) + elif isinstance(obj, dict) and obj.get('__type__', None) == 'timedelta': + obj.pop('__type__') + return timedelta(**obj) + return obj + + +def try_parse_isoformat(obj: str) -> Union[datetime, str]: + """Attempt to parse an ISO-formatted datetime string; if it fails, just return the string""" + try: + return datetime.fromisoformat(obj) + except (AttributeError, TypeError, ValueError): + return obj |
