comparison env/lib/python3.9/site-packages/cachecontrol/serialize.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import base64
2 import io
3 import json
4 import zlib
5
6 from requests.structures import CaseInsensitiveDict
7
8 from .compat import HTTPResponse, pickle, text_type
9
10
11 def _b64_encode_bytes(b):
12 return base64.b64encode(b).decode("ascii")
13
14
15 def _b64_encode_str(s):
16 return _b64_encode_bytes(s.encode("utf8"))
17
18
19 def _b64_encode(s):
20 if isinstance(s, text_type):
21 return _b64_encode_str(s)
22 return _b64_encode_bytes(s)
23
24
25 def _b64_decode_bytes(b):
26 return base64.b64decode(b.encode("ascii"))
27
28
29 def _b64_decode_str(s):
30 return _b64_decode_bytes(s).decode("utf8")
31
32
33 class Serializer(object):
34
35 def dumps(self, request, response, body=None):
36 response_headers = CaseInsensitiveDict(response.headers)
37
38 if body is None:
39 body = response.read(decode_content=False)
40
41 # NOTE: 99% sure this is dead code. I'm only leaving it
42 # here b/c I don't have a test yet to prove
43 # it. Basically, before using
44 # `cachecontrol.filewrapper.CallbackFileWrapper`,
45 # this made an effort to reset the file handle. The
46 # `CallbackFileWrapper` short circuits this code by
47 # setting the body as the content is consumed, the
48 # result being a `body` argument is *always* passed
49 # into cache_response, and in turn,
50 # `Serializer.dump`.
51 response._fp = io.BytesIO(body)
52
53 data = {
54 "response": {
55 "body": _b64_encode_bytes(body),
56 "headers": dict(
57 (_b64_encode(k), _b64_encode(v))
58 for k, v in response.headers.items()
59 ),
60 "status": response.status,
61 "version": response.version,
62 "reason": _b64_encode_str(response.reason),
63 "strict": response.strict,
64 "decode_content": response.decode_content,
65 },
66 }
67
68 # Construct our vary headers
69 data["vary"] = {}
70 if "vary" in response_headers:
71 varied_headers = response_headers['vary'].split(',')
72 for header in varied_headers:
73 header = header.strip()
74 data["vary"][header] = request.headers.get(header, None)
75
76 # Encode our Vary headers to ensure they can be serialized as JSON
77 data["vary"] = dict(
78 (_b64_encode(k), _b64_encode(v) if v is not None else v)
79 for k, v in data["vary"].items()
80 )
81
82 return b",".join([
83 b"cc=2",
84 zlib.compress(
85 json.dumps(
86 data, separators=(",", ":"), sort_keys=True,
87 ).encode("utf8"),
88 ),
89 ])
90
91 def loads(self, request, data):
92 # Short circuit if we've been given an empty set of data
93 if not data:
94 return
95
96 # Determine what version of the serializer the data was serialized
97 # with
98 try:
99 ver, data = data.split(b",", 1)
100 except ValueError:
101 ver = b"cc=0"
102
103 # Make sure that our "ver" is actually a version and isn't a false
104 # positive from a , being in the data stream.
105 if ver[:3] != b"cc=":
106 data = ver + data
107 ver = b"cc=0"
108
109 # Get the version number out of the cc=N
110 ver = ver.split(b"=", 1)[-1].decode("ascii")
111
112 # Dispatch to the actual load method for the given version
113 try:
114 return getattr(self, "_loads_v{0}".format(ver))(request, data)
115 except AttributeError:
116 # This is a version we don't have a loads function for, so we'll
117 # just treat it as a miss and return None
118 return
119
120 def prepare_response(self, request, cached):
121 """Verify our vary headers match and construct a real urllib3
122 HTTPResponse object.
123 """
124 # Special case the '*' Vary value as it means we cannot actually
125 # determine if the cached response is suitable for this request.
126 if "*" in cached.get("vary", {}):
127 return
128
129 # Ensure that the Vary headers for the cached response match our
130 # request
131 for header, value in cached.get("vary", {}).items():
132 if request.headers.get(header, None) != value:
133 return
134
135 body_raw = cached["response"].pop("body")
136
137 headers = CaseInsensitiveDict(data=cached['response']['headers'])
138 if headers.get('transfer-encoding', '') == 'chunked':
139 headers.pop('transfer-encoding')
140
141 cached['response']['headers'] = headers
142
143 try:
144 body = io.BytesIO(body_raw)
145 except TypeError:
146 # This can happen if cachecontrol serialized to v1 format (pickle)
147 # using Python 2. A Python 2 str(byte string) will be unpickled as
148 # a Python 3 str (unicode string), which will cause the above to
149 # fail with:
150 #
151 # TypeError: 'str' does not support the buffer interface
152 body = io.BytesIO(body_raw.encode('utf8'))
153
154 return HTTPResponse(
155 body=body,
156 preload_content=False,
157 **cached["response"]
158 )
159
160 def _loads_v0(self, request, data):
161 # The original legacy cache data. This doesn't contain enough
162 # information to construct everything we need, so we'll treat this as
163 # a miss.
164 return
165
166 def _loads_v1(self, request, data):
167 try:
168 cached = pickle.loads(data)
169 except ValueError:
170 return
171
172 return self.prepare_response(request, cached)
173
174 def _loads_v2(self, request, data):
175 try:
176 cached = json.loads(zlib.decompress(data).decode("utf8"))
177 except ValueError:
178 return
179
180 # We need to decode the items that we've base64 encoded
181 cached["response"]["body"] = _b64_decode_bytes(
182 cached["response"]["body"]
183 )
184 cached["response"]["headers"] = dict(
185 (_b64_decode_str(k), _b64_decode_str(v))
186 for k, v in cached["response"]["headers"].items()
187 )
188 cached["response"]["reason"] = _b64_decode_str(
189 cached["response"]["reason"],
190 )
191 cached["vary"] = dict(
192 (_b64_decode_str(k), _b64_decode_str(v) if v is not None else v)
193 for k, v in cached["vary"].items()
194 )
195
196 return self.prepare_response(request, cached)