summaryrefslogtreecommitdiff
path: root/requests_cache/serializers
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2021-05-07 15:50:14 -0500
committerJordan Cook <jordan.cook@pioneer.com>2021-06-11 11:13:16 -0500
commitd1d5b00b607ee41cb44b2ec0ab683bc2e8f3e81e (patch)
tree9ae76e78874953b18b8f647950b55329ca2bbcd5 /requests_cache/serializers
parentd1e154aa8e6ba104320fd03b1023eefb16df3f57 (diff)
downloadrequests-cache-d1d5b00b607ee41cb44b2ec0ab683bc2e8f3e81e.tar.gz
Experiment with adding a JSON serializer
Diffstat (limited to 'requests_cache/serializers')
-rw-r--r--requests_cache/serializers/json.py78
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