Merge ~cjwatson/lazr.restful:remove-simplejson into lazr.restful:main

Proposed by Colin Watson
Status: Merged
Merged at revision: 53c47ab5ed277aee75d207b47ccb21ba183ab2ac
Proposed branch: ~cjwatson/lazr.restful:remove-simplejson
Merge into: lazr.restful:main
Prerequisite: ~cjwatson/lazr.restful:remove-six
Diff against target: 1129 lines (+166/-139)
18 files modified
NEWS.rst (+5/-0)
setup.py (+0/-1)
src/lazr/restful/_operation.py (+2/-2)
src/lazr/restful/_resource.py (+43/-18)
src/lazr/restful/docs/multiversion.rst (+20/-22)
src/lazr/restful/docs/webservice-declarations.rst (+2/-2)
src/lazr/restful/docs/webservice.rst (+12/-10)
src/lazr/restful/example/base/tests/collection.txt (+2/-2)
src/lazr/restful/example/base/tests/entry.txt (+7/-9)
src/lazr/restful/example/base/tests/field.txt (+11/-11)
src/lazr/restful/example/base/tests/hostedfile.txt (+2/-2)
src/lazr/restful/example/base/tests/representation-cache.txt (+26/-26)
src/lazr/restful/example/multiversion/tests/introduction.txt (+2/-2)
src/lazr/restful/marshallers.py (+2/-2)
src/lazr/restful/publisher.py (+2/-2)
src/lazr/restful/tales.py (+2/-2)
src/lazr/restful/testing/webservice.py (+4/-4)
src/lazr/restful/tests/test_webservice.py (+22/-22)
Reviewer Review Type Date Requested Status
Andrey Fedoseev (community) Approve
Review via email: mp+414025@code.launchpad.net

Commit message

Remove simplejson dependency

Description of the change

The only things that `simplejson` still gave us compared with the standard library's `json` were more consistently accepting both bytes and text input across all supported Python versions, and `simplejson.encoder.JSONEncoderForHTML`. It isn't really worth a whole dependency just for those.

To post a comment you must log in.
Revision history for this message
Andrey Fedoseev (andrey-fedoseev) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/NEWS.rst b/NEWS.rst
2index d1817d2..cf82019 100644
3--- a/NEWS.rst
4+++ b/NEWS.rst
5@@ -2,6 +2,11 @@
6 NEWS for lazr.restful
7 =====================
8
9+2.0.1
10+=====
11+
12+- Remove simplejson dependency.
13+
14 2.0.0 (2022-06-01)
15 ==================
16
17diff --git a/setup.py b/setup.py
18index e8602b8..e763d70 100755
19--- a/setup.py
20+++ b/setup.py
21@@ -61,7 +61,6 @@ setup(
22 "martian>=0.11",
23 "pytz",
24 "setuptools",
25- "simplejson>=2.1.0",
26 "testtools",
27 "van.testing",
28 "zope.component [zcml]",
29diff --git a/src/lazr/restful/_operation.py b/src/lazr/restful/_operation.py
30index 7cab8ea..fdfc305 100644
31--- a/src/lazr/restful/_operation.py
32+++ b/src/lazr/restful/_operation.py
33@@ -2,7 +2,7 @@
34
35 """Base classes for one-off HTTP operations."""
36
37-import simplejson
38+import json
39
40 from zope.component import getMultiAdapter, getUtility, queryMultiAdapter
41 from zope.event import notify
42@@ -138,7 +138,7 @@ class ResourceOperation(BatchingResourceMixin):
43 # Serialize the result to JSON. Any embedded entries will be
44 # automatically serialized.
45 try:
46- result = simplejson.dumps(result, cls=ResourceJSONEncoder)
47+ result = json.dumps(result, cls=ResourceJSONEncoder)
48 except TypeError:
49 raise TypeError(
50 "Could not serialize object %s to JSON." % result
51diff --git a/src/lazr/restful/_resource.py b/src/lazr/restful/_resource.py
52index 89c005b..d6b1919 100644
53--- a/src/lazr/restful/_resource.py
54+++ b/src/lazr/restful/_resource.py
55@@ -31,10 +31,9 @@ from email.utils import formatdate
56 import copy
57 import hashlib
58 import html
59+import json
60 from operator import itemgetter
61 import os
62-import simplejson
63-import simplejson.encoder
64 import sys
65 import time
66
67@@ -149,7 +148,7 @@ def decode_value(value):
68 elif isinstance(value, bytes):
69 return value.decode("utf-8")
70 else:
71- return simplejson.dumps(value, cls=ResourceJSONEncoder)
72+ return json.dumps(value, cls=ResourceJSONEncoder)
73
74
75 def _default_html_renderer(value):
76@@ -184,10 +183,10 @@ class LazrPageTemplateFile(TrustedAppPT, PageTemplateFile):
77 pass
78
79
80-class ResourceJSONEncoder(simplejson.encoder.JSONEncoderForHTML):
81+class ResourceJSONEncoder(json.JSONEncoder):
82 """A JSON encoder for JSON-exposable resources like entry resources.
83
84- This class works with simplejson to encode objects as JSON if they
85+ This class works with json to encode objects as JSON if they
86 implement IJSONPublishable. All EntryResource subclasses, for
87 instance, should implement IJSONPublishable.
88 """
89@@ -196,15 +195,41 @@ class ResourceJSONEncoder(simplejson.encoder.JSONEncoderForHTML):
90 self.media_type = kwargs.pop("media_type", HTTPResource.JSON_TYPE)
91 super().__init__(*args, **kwargs)
92
93+ # Based on simplejson.encoder.JSONEncoderForHTML.encode.
94+ def encode(self, o):
95+ # Override JSONEncoder.encode because it has hacks for performance
96+ # that make things more complicated.
97+ chunks = self.iterencode(o, True)
98+ return "".join(chunks)
99+
100+ # Based on simplejson.encoder.JSONEncoderForHTML.iterencode.
101+ def iterencode(self, o, _one_shot=False):
102+ chunks = super().iterencode(o, _one_shot)
103+ for chunk in chunks:
104+ # These characters should be escaped in order to embed JSON
105+ # content in a <script> tag. They cannot be escaped with the
106+ # usual entities (e.g. &amp;) because they are not expanded
107+ # within <script> tags.
108+ chunk = chunk.replace("&", "\\u0026")
109+ chunk = chunk.replace("<", "\\u003c")
110+ chunk = chunk.replace(">", "\\u003e")
111+
112+ if not self.ensure_ascii:
113+ # The line separator and paragraph separator characters are
114+ # not valid in JavaScript strings.
115+ chunk = chunk.replace("\u2028", "\\u2028")
116+ chunk = chunk.replace("\u2029", "\\u2029")
117+
118+ yield chunk
119+
120 def default(self, obj):
121 """Convert the given object to a simple data structure."""
122 if isProxy(obj):
123 # We have a security-proxied version of a built-in
124 # type. We create a new version of the type by copying the
125 # proxied version's content. That way the container is not
126- # security proxied (and simplejson will know what do do
127- # with it), but the content will still be security
128- # wrapped.
129+ # security proxied (and json will know what to do with it), but
130+ # the content will still be security wrapped.
131 underlying_object = removeSecurityProxy(obj)
132 if isinstance(underlying_object, list):
133 return list(obj)
134@@ -609,7 +634,7 @@ class BatchingResourceMixin:
135 if not hasattr(entries, "__len__"):
136 entries = IFiniteSequence(entries)
137
138- return simplejson.dumps(len(entries))
139+ return json.dumps(len(entries))
140
141 def batch(self, entries, request):
142 """Prepare a batch from a (possibly huge) list of entries.
143@@ -661,7 +686,7 @@ class BatchingResourceMixin:
144 prev_url = navigator.prevBatchURL()
145 if prev_url != "":
146 batch["prev_collection_link"] = prev_url
147- json_string = simplejson.dumps(batch, cls=ResourceJSONEncoder)
148+ json_string = json.dumps(batch, cls=ResourceJSONEncoder)
149
150 # String together a bunch of entry representations, possibly
151 # obtained from a representation cache.
152@@ -1022,7 +1047,7 @@ class EntryManipulatingResource(ReadWriteResource):
153 self.request.response.setStatus(415)
154 return None, "Expected a media type of %s." % self.JSON_TYPE
155 try:
156- h = simplejson.loads(representation.decode("utf-8"))
157+ h = json.loads(representation.decode("utf-8"))
158 except ValueError:
159 self.request.response.setStatus(400)
160 return None, "Entity-body was not a well-formed JSON document."
161@@ -1352,7 +1377,7 @@ class EntryFieldResource(FieldUnmarshallerMixin, EntryManipulatingResource):
162 name, value = self._unmarshallField(
163 self.context.name, self.context.field, CLOSEUP_DETAIL
164 )
165- return simplejson.dumps(value)
166+ return json.dumps(value)
167 elif media_type == self.XHTML_TYPE:
168 name, value = self.unmarshallFieldToHTML(
169 self.context.name, self.context.field
170@@ -1770,7 +1795,7 @@ class EntryResource(
171 if representation is None:
172 # Either there is no active cache, or the representation
173 # wasn't in the cache.
174- representation = simplejson.dumps(
175+ representation = json.dumps(
176 self, cls=ResourceJSONEncoder, media_type=media_type
177 )
178 # If there's an active cache, and this representation
179@@ -1799,13 +1824,13 @@ class EntryResource(
180 # redact certain fields, and reserialize
181 # it. Hopefully this is faster than generating the
182 # representation from scratch!
183- json = simplejson.loads(representation)
184+ json_representation = json.loads(representation)
185 for field in redacted_fields:
186- json[field] = self.REDACTED_VALUE
187+ json_representation[field] = self.REDACTED_VALUE
188 # There's no need to use the ResourceJSONEncoder,
189 # because we loaded the cached representation
190 # using the standard decoder.
191- representation = simplejson.dumps(json)
192+ representation = json.dumps(json_representation)
193 return representation
194 elif media_type == self.XHTML_TYPE:
195 return self.toXHTML()
196@@ -1883,7 +1908,7 @@ class CollectionResource(
197 if request is None:
198 request = self.request
199 result = super().batch(entries, request)
200- result += ', "resource_type_link" : ' + simplejson.dumps(self.type_url)
201+ result += ', "resource_type_link" : ' + json.dumps(self.type_url)
202 return result
203
204 @property
205@@ -1992,7 +2017,7 @@ class ServiceRootResource(HTTPResource):
206 elif media_type == self.JSON_TYPE:
207 # Serve a JSON map containing links to all the top-level
208 # resources.
209- result = simplejson.dumps(self, cls=ResourceJSONEncoder)
210+ result = json.dumps(self, cls=ResourceJSONEncoder)
211
212 self.request.response.setHeader("Content-Type", media_type)
213 return result.encode("utf-8")
214diff --git a/src/lazr/restful/docs/multiversion.rst b/src/lazr/restful/docs/multiversion.rst
215index baeef02..d3c95d3 100644
216--- a/src/lazr/restful/docs/multiversion.rst
217+++ b/src/lazr/restful/docs/multiversion.rst
218@@ -679,10 +679,10 @@ Beta
219
220 Here's the service root resource.
221
222- >>> import simplejson
223+ >>> import json
224 >>> request = create_web_service_request('/beta/')
225 >>> resource = request.traverse(None)
226- >>> body = simplejson.loads(resource())
227+ >>> body = json.loads(resource().decode())
228 >>> print(sorted(body.keys()))
229 ['contacts_collection_link', 'resource_type_link']
230
231@@ -701,7 +701,7 @@ absoluteURL along with the request object, and get the correct URL.
232 >>> print(absoluteURL(contact_set, request))
233 http://api.multiversion.dev/beta/contact_list
234
235- >>> body = simplejson.loads(resource())
236+ >>> body = json.loads(resource())
237 >>> body['total_size']
238 3
239 >>> for link in sorted(
240@@ -728,7 +728,7 @@ object's URL.
241 http://api.multiversion.dev/beta/contact_list/Cleo%20Python
242
243 >>> from collections import OrderedDict
244- >>> body = simplejson.loads(resource(), object_pairs_hook=OrderedDict)
245+ >>> body = json.loads(resource().decode(), object_pairs_hook=OrderedDict)
246 >>> list(body)
247 ['self_link', 'resource_type_link', 'name', 'phone', 'fax', 'http_etag']
248 >>> print(body['name'])
249@@ -739,19 +739,18 @@ We can traverse through an entry to one of its fields.
250 >>> request_beta = create_web_service_request(
251 ... '/beta/contact_list/Cleo Python/fax')
252 >>> field = request_beta.traverse(None)
253- >>> print(simplejson.loads(field()))
254+ >>> print(json.loads(field().decode()))
255 111-2121
256
257 We can invoke a named operation, and it returns a total_size (because
258 'beta' is an earlier version than the
259 first_version_with_total_size_link).
260
261- >>> import simplejson
262 >>> request_beta = create_web_service_request(
263 ... '/beta/contact_list',
264 ... environ={'QUERY_STRING' : 'ws.op=findContacts&string=Cleo'})
265 >>> operation = request_beta.traverse(None)
266- >>> result = simplejson.loads(operation())
267+ >>> result = json.loads(operation())
268 >>> [contact['name'] for contact in result['entries']]
269 ['Cleo Python']
270
271@@ -763,7 +762,7 @@ first_version_with_total_size_link).
272 ... environ={'QUERY_STRING' : 'ws.op=findContacts&string=111'})
273
274 >>> operation = request_beta.traverse(None)
275- >>> result = simplejson.loads(operation())
276+ >>> result = json.loads(operation())
277 >>> [contact['fax'] for contact in result['entries']]
278 ['111-2121']
279
280@@ -772,10 +771,9 @@ first_version_with_total_size_link).
281
282 Here's the service root resource.
283
284- >>> import simplejson
285 >>> request = create_web_service_request('/1.0/')
286 >>> resource = request.traverse(None)
287- >>> body = simplejson.loads(resource())
288+ >>> body = json.loads(resource().decode())
289 >>> print(sorted(body.keys()))
290 ['contacts_collection_link', 'resource_type_link']
291
292@@ -802,7 +800,7 @@ Here's the contact list under its correct URL.
293 >>> print(absoluteURL(contact_set, request))
294 http://api.multiversion.dev/1.0/contacts
295
296- >>> body = simplejson.loads(resource())
297+ >>> body = json.loads(resource())
298 >>> body['total_size']
299 3
300 >>> for link in sorted(
301@@ -823,7 +821,7 @@ We can traverse through the collection to an entry.
302 Note that the 'fax' and 'phone' fields are now called 'fax_number' and
303 'phone_number'.
304
305- >>> body = simplejson.loads(resource(), object_pairs_hook=OrderedDict)
306+ >>> body = json.loads(resource().decode(), object_pairs_hook=OrderedDict)
307 >>> list(body)
308 ['self_link', 'resource_type_link', 'name', 'phone_number', 'fax_number',
309 'http_etag']
310@@ -835,7 +833,7 @@ We can traverse through an entry to one of its fields.
311 >>> request_10 = create_web_service_request(
312 ... '/1.0/contacts/Cleo Python/fax_number')
313 >>> field = request_10.traverse(None)
314- >>> print(simplejson.loads(field()))
315+ >>> print(json.loads(field().decode()))
316 111-2121
317
318 The fax field in '1.0' is called 'fax_number', and attempting
319@@ -858,7 +856,7 @@ first_version_with_total_size_link.
320 ... '/1.0/contacts',
321 ... environ={'QUERY_STRING' : 'ws.op=find&string=e&ws.size=2'})
322 >>> operation = request_10.traverse(None)
323- >>> result = simplejson.loads(operation())
324+ >>> result = json.loads(operation())
325 >>> [contact['name'] for contact in result['entries']]
326 ['Cleo Python', 'Oliver Bluth']
327
328@@ -874,7 +872,7 @@ first_version_with_total_size_link.
329 ... environ={'QUERY_STRING' :
330 ... 'string=e&ws.op=find&ws.show=total_size'})
331 >>> operation = size_request.traverse(None)
332- >>> result = simplejson.loads(operation())
333+ >>> result = json.loads(operation())
334 >>> print(result)
335 3
336
337@@ -885,7 +883,7 @@ instead of total_size_link, as a convenience.
338 ... '/1.0/contacts',
339 ... environ={'QUERY_STRING' : 'ws.op=find&string=111'})
340 >>> operation = request_10.traverse(None)
341- >>> result = simplejson.loads(operation())
342+ >>> result = json.loads(operation())
343 >>> [contact['fax_number'] for contact in result['entries']]
344 ['111-2121']
345 >>> result['total_size']
346@@ -907,7 +905,7 @@ Here's the service root resource.
347
348 >>> request = create_web_service_request('/dev/')
349 >>> resource = request.traverse(None)
350- >>> body = simplejson.loads(resource())
351+ >>> body = json.loads(resource().decode())
352 >>> print(sorted(body.keys()))
353 ['contacts_collection_link', 'resource_type_link']
354
355@@ -921,7 +919,7 @@ Here's the contact list.
356 >>> print(absoluteURL(contact_set, request_dev))
357 http://api.multiversion.dev/dev/contacts
358
359- >>> body = simplejson.loads(resource())
360+ >>> body = json.loads(resource())
361 >>> body['total_size']
362 2
363 >>> for link in sorted(
364@@ -942,7 +940,7 @@ Note that the published field names have changed between 'dev' and
365 '1.0'. The phone field is still 'phone_number', but the 'fax_number'
366 field is gone.
367
368- >>> body = simplejson.loads(resource(), object_pairs_hook=OrderedDict)
369+ >>> body = json.loads(resource().decode(), object_pairs_hook=OrderedDict)
370 >>> list(body)
371 ['self_link', 'resource_type_link', 'name', 'phone_number', 'http_etag']
372 >>> print(body['name'])
373@@ -953,7 +951,7 @@ We can traverse through an entry to one of its fields.
374 >>> request_dev = create_web_service_request(
375 ... '/dev/contacts/Cleo Python/name')
376 >>> field = request_dev.traverse(None)
377- >>> print(simplejson.loads(field()))
378+ >>> print(json.loads(field().decode()))
379 Cleo Python
380
381 We cannot use 'dev' to traverse to a field not published in the 'dev'
382@@ -981,7 +979,7 @@ We can invoke a named operation.
383 ... '/dev/contacts',
384 ... environ={'QUERY_STRING' : 'ws.op=find&string=Cleo'})
385 >>> operation = request_dev.traverse(None)
386- >>> result = simplejson.loads(operation())
387+ >>> result = json.loads(operation())
388 >>> [contact['name'] for contact in result['entries']]
389 ['Cleo Python']
390
391@@ -993,6 +991,6 @@ service doesn't search the fax field.
392 ... '/dev/contacts',
393 ... environ={'QUERY_STRING' : 'ws.op=find&string=111'})
394 >>> operation = request_dev.traverse(None)
395- >>> result = simplejson.loads(operation())
396+ >>> result = json.loads(operation())
397 >>> [entry for entry in result['entries']]
398 []
399diff --git a/src/lazr/restful/docs/webservice-declarations.rst b/src/lazr/restful/docs/webservice-declarations.rst
400index 447aa49..be31dde 100644
401--- a/src/lazr/restful/docs/webservice-declarations.rst
402+++ b/src/lazr/restful/docs/webservice-declarations.rst
403@@ -1187,9 +1187,9 @@ Now we can create a fake request that invokes the named operation.
404 Since the method is declared as returning a list of objects, the
405 return value is a dictionary containing a batched list.
406
407- >>> import simplejson
408+ >>> import json
409 >>> for key, value in sorted(
410- ... simplejson.loads(read_method_adapter.call(text='')).items()):
411+ ... json.loads(read_method_adapter.call(text='')).items()):
412 ... print('%s: %s' % (key, value))
413 entries: []
414 start: 0
415diff --git a/src/lazr/restful/docs/webservice.rst b/src/lazr/restful/docs/webservice.rst
416index d10d4a3..477b762 100644
417--- a/src/lazr/restful/docs/webservice.rst
418+++ b/src/lazr/restful/docs/webservice.rst
419@@ -1087,9 +1087,9 @@ The response document is a JSON document full of links to the
420 top-level collections of authors, cookbooks, and dishes. It's the
421 'home page' for the web service.
422
423- >>> import simplejson
424+ >>> import json
425 >>> response = app(request)
426- >>> representation = simplejson.loads(response)
427+ >>> representation = json.loads(response.decode())
428
429 >>> print(representation["authors_collection_link"])
430 http://api.cookbooks.dev/beta/authors
431@@ -1164,7 +1164,9 @@ parsed with standard tools.
432
433 >>> def load_json(s):
434 ... """Convert a JSON string to Unicode and then load it."""
435- ... return simplejson.loads(s)
436+ ... if isinstance(s, bytes):
437+ ... s = s.decode()
438+ ... return json.loads(s)
439
440 >>> representation = load_json(collection())
441 >>> print(representation['resource_type_link'])
442@@ -1589,7 +1591,7 @@ a JSON hash containing a list of entries.
443 >>> recipes = StubResultSet()
444 >>> request, operation = make_sample_operation_request(recipes)
445 >>> response = operation()
446- >>> pprint_collection(simplejson.loads(response))
447+ >>> pprint_collection(json.loads(response))
448 start: ...
449 total_size: 2
450 ---
451@@ -1604,7 +1606,7 @@ batch from the collection.
452
453 >>> request, operation = make_sample_operation_request(DishSet())
454 >>> response = operation()
455- >>> pprint_collection(simplejson.loads(response))
456+ >>> pprint_collection(json.loads(response))
457 resource_type_link: 'http://api.cookbooks.dev/beta/#dishes'
458 start: ...
459 total_size: 3
460@@ -1818,7 +1820,7 @@ server. The server will know that the client isn't actually trying to
461 set the field value to 'tag:launchpad.net:2008:redacted'.
462
463 >>> headers = {'CONTENT_TYPE' : 'application/json'}
464- >>> body = simplejson.dumps(author).encode()
465+ >>> body = json.dumps(author).encode()
466 >>> put_request = create_web_service_request(
467 ... beard_url, body=body, environ=headers, method='PUT')
468 >>> print(put_request.traverse(app)().decode())
469@@ -1831,7 +1833,7 @@ PUT, even when its current value is redacted.
470 >>> author['favorite_recipe_link'] = 'http://' + quote(
471 ... 'api.cookbooks.dev/beta/cookbooks/'
472 ... 'The Joy of Cooking/recipes/Roast chicken')
473- >>> body = simplejson.dumps(author).encode()
474+ >>> body = json.dumps(author).encode()
475 >>> put_request = create_web_service_request(
476 ... beard_url, body=body, environ=headers, method='PUT')
477 >>> print(put_request.traverse(app)().decode())
478@@ -1851,7 +1853,7 @@ permission to see:
479
480 >>> author['favorite_recipe_link'] = (
481 ... 'http://api.cookbooks.dev' + private_recipe_url)
482- >>> body = simplejson.dumps(author).encode()
483+ >>> body = json.dumps(author).encode()
484 >>> put_request = create_web_service_request(
485 ... beard_url, body=body, environ=headers, method='PUT')
486 >>> print(put_request.traverse(app)())
487@@ -2019,7 +2021,7 @@ server. Note also that another modification event is sent out and
488 intercepted by the modified_cookbook() listener.
489
490 >>> joy['name'] = 'The Joy of Cooking'
491- >>> body = simplejson.dumps(joy).encode()
492+ >>> body = json.dumps(joy).encode()
493 >>> put_request = create_web_service_request(
494 ... '/beta/cookbooks/The%20Joy%20of%20Cooking%20%28revised%29',
495 ... body=body, environ=headers, method='PUT')
496@@ -2058,7 +2060,7 @@ their URLs, we make the change by modifying the cookbook's
497 ... representation = {'author_link' : new_author_link}
498 ... resource = create_web_service_request(
499 ... '/beta/cookbooks/The%20Joy%20of%20Cooking',
500- ... body=simplejson.dumps(representation).encode(),
501+ ... body=json.dumps(representation).encode(),
502 ... environ=headers, method='PATCH', hostname=host).traverse(app)
503 ... result = resource()
504 ... if isinstance(result, bytes):
505diff --git a/src/lazr/restful/example/base/tests/collection.txt b/src/lazr/restful/example/base/tests/collection.txt
506index 44065aa..2660d29 100644
507--- a/src/lazr/restful/example/base/tests/collection.txt
508+++ b/src/lazr/restful/example/base/tests/collection.txt
509@@ -165,10 +165,10 @@ custom operation to be invoked is named in the query string's
510 'ws.op' argument. Here's a custom operation on the collection of
511 cookbooks, called 'find_recipes'.
512
513- >>> import simplejson
514+ >>> import json
515 >>> def search_recipes(text, vegetarian=False, start=0, size=2):
516 ... args = ("&search=%s&vegetarian=%s&ws.start=%s&ws.size=%s" %
517- ... (quote(text), simplejson.dumps(vegetarian), start, size))
518+ ... (quote(text), json.dumps(vegetarian), start, size))
519 ... return webservice.get(
520 ... "/cookbooks?ws.op=find_recipes&%s" % args).jsonBody()
521
522diff --git a/src/lazr/restful/example/base/tests/entry.txt b/src/lazr/restful/example/base/tests/entry.txt
523index decab39..045bd14 100644
524--- a/src/lazr/restful/example/base/tests/entry.txt
525+++ b/src/lazr/restful/example/base/tests/entry.txt
526@@ -279,10 +279,10 @@ Custom operations may have error handling.
527 The 'New' trick can't be used on this cookbook because its
528 name already starts with 'The New'.
529
530- >>> import simplejson
531+ >>> import json
532 >>> ignore = webservice.patch(
533 ... new_joy_url, 'application/json',
534- ... simplejson.dumps({"name": "The Joy of Cooking"}))
535+ ... json.dumps({"name": "The Joy of Cooking"}))
536
537 Trying to invoke a nonexistent custom operation yields an error.
538
539@@ -306,8 +306,7 @@ entry's state, in which case the client should use PUT.
540 ... new_headers = {'Content-type': 'application/json'}
541 ... if headers is not None:
542 ... new_headers.update(headers)
543- ... return webservice(
544- ... url, method, simplejson.dumps(representation), headers)
545+ ... return webservice(url, method, json.dumps(representation), headers)
546
547 >>> def modify_cookbook(cookbook, representation, method, headers=None):
548 ... "A helper function to PUT or PATCH a cookbook."
549@@ -509,7 +508,7 @@ will be the new representation, modified by both client and server.
550
551 >>> greens = webservice(
552 ... greens_url, "PATCH",
553- ... simplejson.dumps({'description' : ' A description '}),
554+ ... json.dumps({'description' : ' A description '}),
555 ... {'Content-type': 'application/json'}).jsonBody()
556 >>> print(greens['description'])
557 A description
558@@ -519,7 +518,7 @@ will be the new representation, modified by both client and server.
559 The canonicalization works for PUT requests as well.
560
561 >>> greens['description'] = " Another description "
562- >>> greens = webservice(greens_url, "PUT", simplejson.dumps(greens),
563+ >>> greens = webservice(greens_url, "PUT", json.dumps(greens),
564 ... {'Content-type': 'application/json'}).jsonBody()
565 >>> print(greens['description'])
566 Another description
567@@ -775,8 +774,7 @@ for.
568
569 >>> def modify_dish(url, recipe, new_dish_url):
570 ... recipe['dish_link'] = new_dish_url
571- ... return webservice.put(
572- ... url, 'application/json', simplejson.dumps(recipe))
573+ ... return webservice.put(url, 'application/json', json.dumps(recipe))
574
575 >>> new_dish = webservice.get(quote('/dishes/Baked beans')).jsonBody()
576 >>> new_dish_url = new_dish['self_link']
577@@ -1011,7 +1009,7 @@ A recipe has a 'dish_link', but it doesn't have a 'dish' directly.
578
579 >>> url = quote('/cookbooks/The Joy of Cooking/Roast chicken')
580 >>> print(webservice.patch(url, 'application/json',
581- ... simplejson.dumps({'dish' : 'placeholder'})))
582+ ... json.dumps({'dish' : 'placeholder'})))
583 HTTP/1.1 400 Bad Request
584 ...
585 dish: You tried to modify a nonexistent attribute.
586diff --git a/src/lazr/restful/example/base/tests/field.txt b/src/lazr/restful/example/base/tests/field.txt
587index 0f6951f..ca6ec54 100644
588--- a/src/lazr/restful/example/base/tests/field.txt
589+++ b/src/lazr/restful/example/base/tests/field.txt
590@@ -12,11 +12,11 @@ field resource itself, rather than PUT/PATCH to the entry.
591 >>> cookbook_url = quote("/cookbooks/The Joy of Cooking")
592 >>> field_url = cookbook_url + "/description"
593
594- >>> import simplejson
595+ >>> import json
596 >>> def set_description(description):
597 ... """Sets the description for "The Joy of Cooking"."""
598 ... return webservice(field_url, 'PATCH',
599- ... simplejson.dumps(description)).jsonBody()
600+ ... json.dumps(description)).jsonBody()
601
602 >>> print(set_description("New description"))
603 New description
604@@ -30,7 +30,7 @@ field resource itself, rather than PUT/PATCH to the entry.
605
606 PATCH on a field resource works identically to PUT.
607
608- >>> representation = simplejson.dumps('<b>Bold description</b>')
609+ >>> representation = json.dumps('<b>Bold description</b>')
610 >>> print(webservice.put(field_url, 'application/json',
611 ... representation).jsonBody())
612 <b>Bold description</b>
613@@ -50,7 +50,7 @@ the link, rather than the actual object.
614 Changing a field resource that contains a link works the same way as
615 changing a field resource that contains a scalar value.
616
617- >>> new_value = simplejson.dumps(
618+ >>> new_value = json.dumps(
619 ... webservice.get(cookbook_url).jsonBody()['self_link'])
620 >>> print(new_value)
621 "http://.../cookbooks/The%20Joy%20of%20Cooking"
622@@ -68,7 +68,7 @@ the entry as a whole or just modifying a single field.
623
624 >>> date_field_url = cookbook_url + "/copyright_date"
625 >>> print(webservice.put(date_field_url, 'application/json',
626- ... simplejson.dumps("string")))
627+ ... json.dumps("string")))
628 HTTP/1.1 400 Bad Request
629 ...
630 copyright_date: Value doesn't look like a date.
631@@ -103,7 +103,7 @@ a cookbook's name), the field's URL will change. You'll be redirected
632 to the new field URL.
633
634 >>> name_url = cookbook_url + "/name"
635- >>> representation = simplejson.dumps("The Joy of Cooking Extreme")
636+ >>> representation = json.dumps("The Joy of Cooking Extreme")
637 >>> print(webservice.put(name_url, 'application/json',
638 ... representation))
639 HTTP/1.1 301 Moved Permanently
640@@ -124,7 +124,7 @@ Note that the entry's URL has also changed.
641
642 Cleanup.
643
644- >>> representation = simplejson.dumps("The Joy of Cooking")
645+ >>> representation = json.dumps("The Joy of Cooking")
646 >>> new_name_url = new_cookbook_url + "/name"
647 >>> print(webservice.put(new_name_url, 'application/json',
648 ... representation))
649@@ -240,7 +240,7 @@ do.
650 The first attempt to modify the field succeeds, because the ETag
651 provided in If-Match is the one we just got from a GET request.
652
653- >>> representation = simplejson.dumps("New description")
654+ >>> representation = json.dumps("New description")
655 >>> print(webservice.put(field_url, 'application/json',
656 ... representation,
657 ... headers={'If-Match': cookbook_etag}))
658@@ -348,13 +348,13 @@ the "include=lp_html" parameter on the application/json media type.
659
660 The cookbook's description is a normal JSON representation...
661
662- >>> json = response.jsonBody()
663- >>> print(json['description'])
664+ >>> json_body = response.jsonBody()
665+ >>> print(json_body['description'])
666 Description
667
668 ...but the JSON dictionary will include a 'lp_html' sub-dictionary...
669
670- >>> html = json['lp_html']
671+ >>> html = json_body['lp_html']
672
673 ...which includes HTML representations of the fields with HTML
674 representations:
675diff --git a/src/lazr/restful/example/base/tests/hostedfile.txt b/src/lazr/restful/example/base/tests/hostedfile.txt
676index 53ff057..6cafe8d 100644
677--- a/src/lazr/restful/example/base/tests/hostedfile.txt
678+++ b/src/lazr/restful/example/base/tests/hostedfile.txt
679@@ -154,9 +154,9 @@ owns the file.
680
681 >>> greens['cover_link'] = 'http://google.com/logo.png'
682
683- >>> import simplejson
684+ >>> import json
685 >>> print(webservice.put(greens_url, 'application/json',
686- ... simplejson.dumps(greens)))
687+ ... json.dumps(greens)))
688 HTTP/1.1 400 Bad Request
689 ...
690 cover_link: To modify this field you need to send a PUT request to its
691diff --git a/src/lazr/restful/example/base/tests/representation-cache.txt b/src/lazr/restful/example/base/tests/representation-cache.txt
692index 0b6a340..d67ee0b 100644
693--- a/src/lazr/restful/example/base/tests/representation-cache.txt
694+++ b/src/lazr/restful/example/base/tests/representation-cache.txt
695@@ -41,14 +41,15 @@ under a key derived from the object whose representation it is, the
696 media type of the representation, and a web service version name.
697
698 >>> from lazr.restful.example.base.root import C4 as greens_object
699- >>> json = "application/json"
700- >>> print(cache.get(greens_object, json, "devel"))
701+ >>> json_ct = "application/json"
702+ >>> print(cache.get(greens_object, json_ct, "devel"))
703 None
704- >>> print(cache.get(greens_object, json, "devel", "missing"))
705+ >>> print(cache.get(greens_object, json_ct, "devel", "missing"))
706 missing
707
708- >>> cache.set(greens_object, json, "devel", "This is the 'devel' value.")
709- >>> print(cache.get(greens_object, json, "devel"))
710+ >>> cache.set(
711+ ... greens_object, json_ct, "devel", "This is the 'devel' value.")
712+ >>> print(cache.get(greens_object, json_ct, "devel"))
713 This is the 'devel' value.
714 >>> sorted(dictionary.keys())
715 ['http://cookbooks.dev/devel/cookbooks/Everyday%20Greens,application/json']
716@@ -56,8 +57,8 @@ media type of the representation, and a web service version name.
717 This allows different representations of the same object to be stored
718 for different versions.
719
720- >>> cache.set(greens_object, json, "1.0", "This is the '1.0' value.")
721- >>> print(cache.get(greens_object, json, "1.0"))
722+ >>> cache.set(greens_object, json_ct, "1.0", "This is the '1.0' value.")
723+ >>> print(cache.get(greens_object, json_ct, "1.0"))
724 This is the '1.0' value.
725 >>> sorted(dictionary.keys())
726 ['http://cookbooks.dev/1.0/cookbooks/Everyday%20Greens,application/json',
727@@ -68,9 +69,9 @@ Deleting an object from the cache will remove all its representations.
728 >>> cache.delete(greens_object)
729 >>> sorted(dictionary.keys())
730 []
731- >>> print(cache.get(greens_object, json, "devel"))
732+ >>> print(cache.get(greens_object, json_ct, "devel"))
733 None
734- >>> print(cache.get(greens_object, json, "1.0"))
735+ >>> print(cache.get(greens_object, json_ct, "1.0"))
736 None
737
738 DO_NOT_CACHE
739@@ -93,8 +94,8 @@ object+type+version combination will not be cached at all.
740 The key_for() implementation defined above will not cache a
741 representation of the string "Don't cache this."
742
743- >>> test_cache.set("Cache this.", json, "1.0", "representation")
744- >>> test_cache.set("Don't cache this.", json, "1.0", "representation")
745+ >>> test_cache.set("Cache this.", json_ct, "1.0", "representation")
746+ >>> test_cache.set("Don't cache this.", json_ct, "1.0", "representation")
747 >>> for key in test_dict:
748 ... print(key)
749 Cache this.,application/json,1.0
750@@ -102,7 +103,7 @@ representation of the string "Don't cache this."
751 ...UNLESS the representation being cached is for the version "cache
752 everything!"
753
754- >>> test_cache.set("Don't cache this.", json, "cache everything!",
755+ >>> test_cache.set("Don't cache this.", json_ct, "cache everything!",
756 ... "representation")
757 >>> for key in sorted(test_dict.keys()):
758 ... print(key)
759@@ -114,7 +115,7 @@ retrievable.
760
761 >>> bad_key = "Don't cache this.,application/json,1.0"
762 >>> test_dict[bad_key] = "This representation should not be cached."
763- >>> print(test_cache.get("Don't cache this.", json, "1.0"))
764+ >>> print(test_cache.get("Don't cache this.", json_ct, "1.0"))
765 None
766
767 A representation cache
768@@ -146,8 +147,8 @@ Note that the cache key incorporates the web service version name
769
770 Associated with the key is a string: the JSON representation of the object.
771
772- >>> import simplejson
773- >>> print(simplejson.loads(dictionary[the_only_key])['self_link'])
774+ >>> import json
775+ >>> print(json.loads(dictionary[the_only_key])['self_link'])
776 http://cookbooks.dev/devel/recipes/1
777
778 If we get a representation of the same resource from a different web
779@@ -179,7 +180,7 @@ previously cached representation.
780 >>> print(old_instructions)
781 You can always judge...
782 >>> response = webservice.patch(recipe_url, 'application/json',
783- ... simplejson.dumps(dict(instructions="New instructions")),
784+ ... json.dumps(dict(instructions="New instructions")),
785 ... api_version='devel')
786 >>> print(response.status)
787 209
788@@ -190,8 +191,8 @@ The modified representation is immediately available in the cache.
789
790 >>> from lazr.restful.example.base.root import RECIPES
791 >>> recipe = [recipe for recipe in RECIPES if recipe.id == 1][0]
792- >>> cached_representation = cache.get(recipe, json, 'devel')
793- >>> print(simplejson.loads(cached_representation)['instructions'])
794+ >>> cached_representation = cache.get(recipe, json_ct, 'devel')
795+ >>> print(json.loads(cached_representation)['instructions'])
796 New instructions
797
798 Cleanup.
799@@ -207,7 +208,7 @@ POST did.
800 >>> from lazr.restful.example.base.root import COOKBOOKS
801 >>> cookbook = [cookbook for cookbook in COOKBOOKS
802 ... if cookbook.name == "Everyday Greens"][0]
803- >>> cache.set(cookbook, json, 'devel', "Sample value.")
804+ >>> cache.set(cookbook, json_ct, 'devel', "Sample value.")
805 >>> print(list(dictionary.keys())[0])
806 http://.../devel/cookbooks/Everyday%20Greens,application/json
807
808@@ -233,13 +234,13 @@ To remove an object's representation from the cache, we pass it into
809 the cache's delete() method.
810
811 >>> ignore = webservice.get(recipe_url, api_version='devel')
812- >>> print(cache.get(recipe, json, 'devel'))
813+ >>> print(cache.get(recipe, json_ct, 'devel'))
814 {...}
815 >>> cache.delete(recipe)
816
817 This deletes all the relevant representations.
818
819- >>> print(cache.get(recipe, json, 'devel'))
820+ >>> print(cache.get(recipe, json_ct, 'devel'))
821 None
822 >>> list(dictionary)
823 []
824@@ -267,10 +268,9 @@ What if a full representation is in the cache, and the user requests a
825 representation that must be redacted? Let's put some semi-fake data in
826 the cache and find out.
827
828- >>> import simplejson
829 >>> greens['name'] = "This comes from the cache; it is not generated."
830 >>> greens['confirmed'] = True
831- >>> cache.set(greens_object, json, 'devel', simplejson.dumps(greens))
832+ >>> cache.set(greens_object, json_ct, 'devel', json.dumps(greens))
833
834 When we GET the corresponding resource, we get a representation that
835 definitely comes from the cache, not the original data source.
836@@ -342,7 +342,7 @@ First, we'll hack the cached representation of a single recipe.
837 >>> recipe = webservice.get("/recipes/1").jsonBody()
838 >>> recipe['instructions'] = "This representation is from the cache."
839 >>> [recipe_key] = dictionary.keys()
840- >>> dictionary[recipe_key] = simplejson.dumps(recipe)
841+ >>> dictionary[recipe_key] = json.dumps(recipe)
842
843 Now, we get the collection of recipes.
844
845@@ -374,9 +374,9 @@ If we request the collection again, all the entry representations will
846 come from the cache.
847
848 >>> for key in dictionary.keys():
849- ... value = simplejson.loads(dictionary[key])
850+ ... value = json.loads(dictionary[key])
851 ... value['instructions'] = "This representation is from the cache."
852- ... dictionary[key] = simplejson.dumps(value)
853+ ... dictionary[key] = json.dumps(value)
854
855 >>> recipes = webservice.get("/recipes").jsonBody()['entries']
856 >>> for instructions in (
857diff --git a/src/lazr/restful/example/multiversion/tests/introduction.txt b/src/lazr/restful/example/multiversion/tests/introduction.txt
858index f01eba6..fd85c3b 100644
859--- a/src/lazr/restful/example/multiversion/tests/introduction.txt
860+++ b/src/lazr/restful/example/multiversion/tests/introduction.txt
861@@ -124,7 +124,7 @@ interesting properties.
862 The 'comment' field is not modified directly, but by internal mutator
863 methods which append some useless text to your comment.
864
865- >>> import simplejson
866+ >>> import json
867 >>> def get_comment(version):
868 ... response = webservice.get("/pairs/foo", api_version=version)
869 ... return response.jsonBody()['comment']
870@@ -132,7 +132,7 @@ methods which append some useless text to your comment.
871 >>> def change_comment(comment, version, get_comment_afterwards=True):
872 ... ignored = webservice.patch(
873 ... "/pairs/foo/", 'application/json',
874- ... simplejson.dumps({"comment": comment}),
875+ ... json.dumps({"comment": comment}),
876 ... api_version=version)
877 ... if get_comment_afterwards:
878 ... return get_comment(version)
879diff --git a/src/lazr/restful/marshallers.py b/src/lazr/restful/marshallers.py
880index 2ca4e34..1f28fdd 100644
881--- a/src/lazr/restful/marshallers.py
882+++ b/src/lazr/restful/marshallers.py
883@@ -24,11 +24,11 @@ __all__ = [
884 from collections import OrderedDict
885 from datetime import datetime
886 from io import BytesIO
887+import json
888 import re
889 from urllib.parse import unquote
890
891 import pytz
892-import simplejson
893
894 from zope.datetime import (
895 DateTimeError,
896@@ -190,7 +190,7 @@ class SimpleFieldMarshaller:
897 v = v.decode("utf8") # assume utf8
898 elif not isinstance(v, str):
899 v = str(v)
900- value = simplejson.loads(v)
901+ value = json.loads(v)
902 except (ValueError, TypeError):
903 # Pass the value as is. This saves client from having to encode
904 # strings.
905diff --git a/src/lazr/restful/publisher.py b/src/lazr/restful/publisher.py
906index 67e4ea0..2680999 100644
907--- a/src/lazr/restful/publisher.py
908+++ b/src/lazr/restful/publisher.py
909@@ -12,13 +12,13 @@ __all__ = [
910 "WebServiceRequestTraversal",
911 ]
912
913+import json
914 from urllib.parse import (
915 quote,
916 urlsplit,
917 urlunsplit,
918 )
919
920-import simplejson
921 from zope.component import (
922 adapter,
923 getMultiAdapter,
924@@ -213,7 +213,7 @@ class WebServicePublicationMixin:
925 (notification.level, notification.message)
926 for notification in notifications_provider.notifications
927 ]
928- json_notifications = simplejson.dumps(notifications)
929+ json_notifications = json.dumps(notifications)
930 request.response.setHeader("X-Lazr-Notifications", json_notifications)
931
932 def callObject(self, request, object):
933diff --git a/src/lazr/restful/tales.py b/src/lazr/restful/tales.py
934index 6b975fe..74e51d2 100644
935--- a/src/lazr/restful/tales.py
936+++ b/src/lazr/restful/tales.py
937@@ -26,9 +26,9 @@
938
939 """Implementation of the ws: namespace in TALES."""
940
941+import json
942 import operator
943 import re
944-import simplejson
945 import textwrap
946 from urllib.parse import quote
947
948@@ -339,7 +339,7 @@ class WebLayerAPI:
949 else:
950 # Just dump it as JSON+XHTML
951 resource = self.context
952- return simplejson.dumps(
953+ return json.dumps(
954 resource,
955 cls=ResourceJSONEncoder,
956 media_type=EntryResource.JSON_TYPE,
957diff --git a/src/lazr/restful/testing/webservice.py b/src/lazr/restful/testing/webservice.py
958index af7496b..1de10b6 100644
959--- a/src/lazr/restful/testing/webservice.py
960+++ b/src/lazr/restful/testing/webservice.py
961@@ -21,9 +21,9 @@ __all__ = [
962 from collections import OrderedDict
963 from email.utils import quote as email_quote
964 import io
965+import json
966 import random
967 import re
968-import simplejson
969 import sys
970 from types import ModuleType
971 import unittest
972@@ -455,7 +455,7 @@ class WebServiceCaller:
973 buf.write(value.read())
974 else:
975 if not isinstance(value, str):
976- value = simplejson.dumps(value)
977+ value = json.dumps(value)
978 lines = re.split(r"\r\n|\r|\n", value)
979 for line in lines[:-1]:
980 buf.write(line.encode())
981@@ -523,7 +523,7 @@ class WebServiceCaller:
982 This may mean turning the value into a JSON string.
983 """
984 if not isinstance(value, str):
985- value = simplejson.dumps(value)
986+ value = json.dumps(value)
987 return quote(value)
988
989 def _make_request_with_entity_body(
990@@ -583,7 +583,7 @@ class WebServiceResponseWrapper(ProxyBase):
991 if isinstance(body, bytes):
992 body = body.decode()
993 try:
994- return simplejson.loads(body, object_pairs_hook=OrderedDict)
995+ return json.loads(body, object_pairs_hook=OrderedDict)
996 except ValueError:
997 # Return a useful ValueError that displays the problematic
998 # string, instead of one that just says the string wasn't
999diff --git a/src/lazr/restful/tests/test_webservice.py b/src/lazr/restful/tests/test_webservice.py
1000index b762b2c..3d1e9a0 100644
1001--- a/src/lazr/restful/tests/test_webservice.py
1002+++ b/src/lazr/restful/tests/test_webservice.py
1003@@ -7,14 +7,14 @@ from io import (
1004 BytesIO,
1005 StringIO,
1006 )
1007-from lxml import etree
1008 import collections
1009+import json
1010 import logging
1011 import random
1012 import re
1013-import simplejson
1014 import unittest
1015
1016+from lxml import etree
1017 from zope.component import (
1018 eventtesting,
1019 getGlobalSiteManager,
1020@@ -455,8 +455,8 @@ class TestEntryWrite(EntryTestCase):
1021
1022 with self.entry_resource(HasOneField, "") as resource:
1023 existing_web_link = resource.toDataForJSON()["web_link"]
1024- representation = simplejson.loads(
1025- resource.applyChanges({"web_link": existing_web_link})
1026+ representation = json.loads(
1027+ resource.applyChanges({"web_link": existing_web_link}).decode()
1028 )
1029 self.assertEqual(representation["web_link"], existing_web_link)
1030
1031@@ -467,7 +467,7 @@ class TestEntryWrite(EntryTestCase):
1032
1033 with self.entry_resource(HasOneField, "") as resource:
1034 existing_representation = resource.toDataForJSON()
1035- representation = simplejson.loads(resource.applyChanges({}))
1036+ representation = json.loads(resource.applyChanges({}).decode())
1037 self.assertEqual(representation, existing_representation)
1038
1039 def test_applyChanges_returns_modified_representation_on_change(self):
1040@@ -490,8 +490,8 @@ class TestEntryWrite(EntryTestCase):
1041 "initial value", resource.toDataForJSON()["field"]
1042 )
1043 # This returns the changed value.
1044- representation = simplejson.loads(
1045- resource.applyChanges(dict(field="new value"))
1046+ representation = json.loads(
1047+ resource.applyChanges(dict(field="new value")).decode()
1048 )
1049 self.assertEqual("new value", representation["field"])
1050
1051@@ -594,13 +594,13 @@ class JSONPlusHTMLRepresentationTest(EntryTestCase):
1052 self.default_media_type = "application/json"
1053 self.register_html_field_renderer()
1054 with self.resource() as resource:
1055- json = simplejson.loads(resource.do_GET())
1056- self.assertFalse("lp_html" in json)
1057+ json_representation = json.loads(resource.do_GET().decode())
1058+ self.assertFalse("lp_html" in json_representation)
1059
1060 def test_entry_with_no_html_renderers_omits_lp_html(self):
1061 with self.resource() as resource:
1062- json = simplejson.loads(resource.do_GET())
1063- self.assertFalse("lp_html" in json)
1064+ json_representation = json.loads(resource.do_GET().decode())
1065+ self.assertFalse("lp_html" in json_representation)
1066 self.assertEqual(
1067 resource.request.response.getHeader("Content-Type"),
1068 "application/json",
1069@@ -609,8 +609,8 @@ class JSONPlusHTMLRepresentationTest(EntryTestCase):
1070 def test_field_specific_html_renderer_shows_up_in_lp_html(self):
1071 self.register_html_field_renderer("a_field")
1072 with self.resource() as resource:
1073- json = simplejson.loads(resource.do_GET())
1074- html = json["lp_html"]
1075+ json_representation = json.loads(resource.do_GET().decode())
1076+ html = json_representation["lp_html"]
1077 self.assertEqual(
1078 html["a_field"], simple_renderer(resource.entry.a_field)
1079 )
1080@@ -622,8 +622,8 @@ class JSONPlusHTMLRepresentationTest(EntryTestCase):
1081 def test_html_renderer_for_class_renders_all_fields_of_that_class(self):
1082 self.register_html_field_renderer()
1083 with self.resource() as resource:
1084- json = simplejson.loads(resource.do_GET())
1085- html = json["lp_html"]
1086+ json_representation = json.loads(resource.do_GET().decode())
1087+ html = json_representation["lp_html"]
1088 self.assertEqual(
1089 html["a_field"], simple_renderer(resource.entry.a_field)
1090 )
1091@@ -643,23 +643,23 @@ class JSONPlusHTMLRepresentationTest(EntryTestCase):
1092 # The lp_html portion of the representation is ignored during
1093 # writes.
1094 self.register_html_field_renderer()
1095- json = None
1096+ json_representation = None
1097 with self.resource() as resource:
1098 json_plus_xhtml = resource.JSON_PLUS_XHTML_TYPE
1099- json = simplejson.loads(
1100+ json_representation = json.loads(
1101 str(resource._representation(json_plus_xhtml))
1102 )
1103- resource.applyChanges(json, json_plus_xhtml)
1104+ resource.applyChanges(json_representation, json_plus_xhtml)
1105 self.assertEqual(resource.request.response.getStatus(), 209)
1106
1107 def test_acceptchanges_does_not_ignore_lp_html_for_bare_json_type(self):
1108 self.register_html_field_renderer()
1109- json = None
1110+ json_representation = None
1111 with self.resource() as resource:
1112- json = simplejson.loads(
1113+ json_representation = json.loads(
1114 str(resource._representation(resource.JSON_PLUS_XHTML_TYPE))
1115 )
1116- resource.applyChanges(json, resource.JSON_TYPE)
1117+ resource.applyChanges(json_representation, resource.JSON_TYPE)
1118 self.assertEqual(resource.request.response.getStatus(), 400)
1119
1120
1121@@ -1124,7 +1124,7 @@ class NotificationsProviderTest(EntryTestCase):
1122 "X-Lazr-Notifications"
1123 )
1124 self.assertFalse(notifications is None)
1125- notifications = simplejson.loads(notifications)
1126+ notifications = json.loads(notifications)
1127 expected_notifications = [
1128 [logging.INFO, "Informational"],
1129 [logging.WARNING, "Warning"],

Subscribers

People subscribed via source and target branches