Merge lp:~cjwatson/lazr.restful/py3-pretty-print-helpers into lp:lazr.restful

Proposed by Colin Watson
Status: Merged
Merged at revision: 259
Proposed branch: lp:~cjwatson/lazr.restful/py3-pretty-print-helpers
Merge into: lp:lazr.restful
Diff against target: 263 lines (+90/-55)
5 files modified
NEWS.rst (+9/-0)
src/lazr/restful/docs/webservice.rst (+45/-38)
src/lazr/restful/example/base/tests/collection.txt (+10/-10)
src/lazr/restful/example/multiversion/tests/operation.txt (+6/-5)
src/lazr/restful/testing/webservice.py (+20/-2)
To merge this branch: bzr merge lp:~cjwatson/lazr.restful/py3-pretty-print-helpers
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+390200@code.launchpad.net

Commit message

Make pprint_entry/pprint_collection format strings in Python 3 style.

Description of the change

Change lazr.restful.testing.webservice.pprint_entry and lazr.restful.testing.webservice.pprint_collection to print text string representations in the Python 3 style ('text' rather than u'text') on both Python 2 and 3. This makes it easier to write bilingual doctests, although existing callers need to change.

Use these helpers in a few more doctests.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS.rst'
2--- NEWS.rst 2020-09-02 08:23:06 +0000
3+++ NEWS.rst 2020-09-02 22:01:19 +0000
4@@ -2,6 +2,15 @@
5 NEWS for lazr.restful
6 =====================
7
8+0.23.0
9+======
10+
11+Change ``lazr.restful.testing.webservice.pprint_entry`` and
12+``lazr.restful.testing.webservice.pprint_collection`` to print text string
13+representations in the Python 3 style (``'text'`` rather than ``u'text'``)
14+on both Python 2 and 3. This makes it easier to write bilingual doctests,
15+although existing callers need to change.
16+
17 0.22.2 (2020-09-02)
18 ===================
19
20
21=== modified file 'src/lazr/restful/docs/webservice.rst'
22--- src/lazr/restful/docs/webservice.rst 2020-08-21 16:16:09 +0000
23+++ src/lazr/restful/docs/webservice.rst 2020-09-02 22:01:19 +0000
24@@ -1409,21 +1409,22 @@
25 but since that is not a required field our application used the default
26 value (Brazilian) specified in ``CookbookFactoryOperation`` for it.
27
28- >>> sorted(feijoada.items())
29- [(u'author_link',
30- u'http://api.cookbooks.dev/beta/authors/Fernando%20Yokota'),
31- (u'comments_collection_link',
32- u'http://api.cookbooks.dev/beta/cookbooks/Feijoada/comments'),
33- (u'cover_link',
34- u'http://api.cookbooks.dev/beta/cookbooks/Feijoada/cover'),
35- (u'cuisine', u'Brazilian'),
36- (u'http_etag', u'...'),
37- (u'name', u'Feijoada'),
38- (u'recipes_collection_link',
39- u'http://api.cookbooks.dev/beta/cookbooks/Feijoada/recipes'),
40- (u'resource_type_link',
41- u'http://api.cookbooks.dev/beta/#cookbook'),
42- (u'self_link', u'http://api.cookbooks.dev/beta/cookbooks/Feijoada')]
43+ >>> from lazr.restful.testing.webservice import (
44+ ... pprint_collection,
45+ ... pprint_entry,
46+ ... )
47+
48+ >>> pprint_entry(feijoada)
49+ author_link: 'http://api.cookbooks.dev/beta/authors/Fernando%20Yokota'
50+ comments_collection_link:
51+ 'http://api.cookbooks.dev/beta/cookbooks/Feijoada/comments'
52+ cover_link: 'http://api.cookbooks.dev/beta/cookbooks/Feijoada/cover'
53+ cuisine: 'Brazilian'
54+ name: 'Feijoada'
55+ recipes_collection_link:
56+ 'http://api.cookbooks.dev/beta/cookbooks/Feijoada/recipes'
57+ resource_type_link: 'http://api.cookbooks.dev/beta/#cookbook'
58+ self_link: 'http://api.cookbooks.dev/beta/cookbooks/Feijoada'
59
60 You can also traverse from an entry to an item in a scoped collection:
61
62@@ -1431,16 +1432,14 @@
63 ... quote('/beta/cookbooks/The Joy of Cooking/recipes/Roast chicken'))
64 >>> chicken_recipe_resource = request.traverse(app)
65 >>> chicken_recipe = load_json(chicken_recipe_resource())
66- >>> sorted(chicken_recipe.items())
67- [(u'comments_collection_link',
68- u'http://api...Joy%20of%20Cooking/recipes/Roast%20chicken/comments'),
69- (u'cookbook_link',
70- u'http://api.cookbooks.dev/beta/cookbooks/The%20Joy%20of%20Cooking'),
71- (u'dish_link', u'http://api.cookbooks.dev/beta/dishes/Roast%20chicken'),
72- (u'http_etag', u'...'),
73- (u'instructions', u'Draw, singe, stuff, and truss...'),
74- (u'self_link',
75- u'http://api.../The%20Joy%20of%20Cooking/recipes/Roast%20chicken')]
76+ >>> pprint_entry(chicken_recipe)
77+ comments_collection_link:
78+ 'http://api...Joy%20of%20Cooking/recipes/Roast%20chicken/comments'
79+ cookbook_link:
80+ 'http://api.cookbooks.dev/beta/cookbooks/The%20Joy%20of%20Cooking'
81+ dish_link: 'http://api.cookbooks.dev/beta/dishes/Roast%20chicken'
82+ instructions: 'Draw, singe, stuff, and truss...'
83+ self_link: 'http://api.../The%20Joy%20of%20Cooking/recipes/Roast%20chicken'
84
85 Another example traversing to a comment:
86
87@@ -1457,12 +1456,11 @@
88 ... roast_chicken_comments_url + '/1')
89 >>> comment_one_resource = request.traverse(app)
90 >>> comment_one = load_json(comment_one_resource())
91- >>> sorted(comment_one.items())
92- [(u'http_etag', u'...'),
93- (u'resource_type_link', u'http://api.cookbooks.dev/beta/#comment'),
94- (u'self_link',
95- u'http://api...Joy%20of%20Cooking/recipes/Roast%20chicken/comments/1'),
96- (u'text', u'Clear and concise.')]
97+ >>> pprint_entry(comment_one)
98+ resource_type_link: 'http://api.cookbooks.dev/beta/#comment'
99+ self_link:
100+ 'http://api...Joy%20of%20Cooking/recipes/Roast%20chicken/comments/1'
101+ text: 'Clear and concise.'
102
103 An entry may expose a number of custom operations through GET. The
104 recipe entry exposes a custom GET operation called
105@@ -1579,11 +1577,14 @@
106 >>> recipes = DummyResultSet()
107 >>> request, operation = make_dummy_operation_request(recipes)
108 >>> response = operation()
109- >>> for key, value in sorted(simplejson.loads(response).items()):
110- ... print('%s: %s' % (key, value))
111- entries: [{...}, {...}]
112+ >>> pprint_collection(simplejson.loads(response))
113 start: ...
114 total_size: 2
115+ ---
116+ ...
117+ ---
118+ ...
119+ ---
120
121 When a named operation returns an object that has an ``ICollection``
122 implementation, the result is similar: we return a JSON hash describing one
123@@ -1591,11 +1592,17 @@
124
125 >>> request, operation = make_dummy_operation_request(DishSet())
126 >>> response = operation()
127- >>> for key, value in sorted(simplejson.loads(response).items()):
128- ... print('%s: %s' % (key, value))
129- entries: ...
130+ >>> pprint_collection(simplejson.loads(response))
131+ resource_type_link: 'http://api.cookbooks.dev/beta/#dishes'
132 start: ...
133- total_size: ...
134+ total_size: 3
135+ ---
136+ ...
137+ ---
138+ ...
139+ ---
140+ ...
141+ ---
142
143 If the return value can't be converted into JSON, you'll get an
144 exception.
145
146=== modified file 'src/lazr/restful/example/base/tests/collection.txt'
147--- src/lazr/restful/example/base/tests/collection.txt 2020-02-04 13:17:32 +0000
148+++ src/lazr/restful/example/base/tests/collection.txt 2020-09-02 22:01:19 +0000
149@@ -125,19 +125,19 @@
150 >>> url = quote("/cookbooks/The Joy of Cooking")
151 >>> cookbook = webservice.get(url).jsonBody()
152 >>> pprint_entry(cookbook)
153- confirmed: u'tag:launchpad.net:2008:redacted'
154- copyright_date: u'1995-01-01'
155- cover_link: u'http://.../cookbooks/The%20Joy%20of%20Cooking/cover'
156- cuisine: u'General'
157- description: u''
158+ confirmed: 'tag:launchpad.net:2008:redacted'
159+ copyright_date: '1995-01-01'
160+ cover_link: 'http://.../cookbooks/The%20Joy%20of%20Cooking/cover'
161+ cuisine: 'General'
162+ description: ''
163 last_printing: None
164- name: u'The Joy of Cooking'
165+ name: 'The Joy of Cooking'
166 price: 20
167- recipes_collection_link: u'http://.../cookbooks/The%20Joy%20of%20Cooking/recipes'
168- resource_type_link: u'http://...#cookbook'
169+ recipes_collection_link: 'http://.../cookbooks/The%20Joy%20of%20Cooking/recipes'
170+ resource_type_link: 'http://...#cookbook'
171 revision_number: 0
172- self_link: u'http://.../cookbooks/The%20Joy%20of%20Cooking'
173- web_link: u'http://dummyurl/'
174+ self_link: 'http://.../cookbooks/The%20Joy%20of%20Cooking'
175+ web_link: 'http://dummyurl/'
176
177 A collection may be scoped to an element:
178
179
180=== modified file 'src/lazr/restful/example/multiversion/tests/operation.txt'
181--- src/lazr/restful/example/multiversion/tests/operation.txt 2020-02-04 11:52:59 +0000
182+++ src/lazr/restful/example/multiversion/tests/operation.txt 2020-09-02 22:01:19 +0000
183@@ -12,7 +12,10 @@
184 collections always send a 'total_size' containing the total size of a
185 collection.
186
187- >>> from lazr.restful.testing.webservice import WebServiceCaller
188+ >>> from lazr.restful.testing.webservice import (
189+ ... pprint_collection,
190+ ... WebServiceCaller,
191+ ... )
192 >>> webservice = WebServiceCaller(domain='multiversion.dev')
193
194 In the example web service, named operations always send 'total_size'
195@@ -52,12 +55,10 @@
196 >>> print(sorted(get_collection('3.0', 'by_value', size=100).keys()))
197 [u'entries', u'start', u'total_size']
198
199- >>> for key, value in sorted(
200- ... get_collection('3.0', 'by_value', 'no-such-value').items()):
201- ... print('%s: %s' % (key, value))
202- entries: []
203+ >>> pprint_collection(get_collection('3.0', 'by_value', 'no-such-value'))
204 start: 0
205 total_size: 0
206+ ---
207
208 Mutators as named operations
209 ----------------------------
210
211=== modified file 'src/lazr/restful/testing/webservice.py'
212--- src/lazr/restful/testing/webservice.py 2020-08-17 11:46:51 +0000
213+++ src/lazr/restful/testing/webservice.py 2020-09-02 22:01:19 +0000
214@@ -13,6 +13,7 @@
215 'FakeResponse',
216 'IGenericEntry',
217 'IGenericCollection',
218+ 'pprint_collection',
219 'pprint_entry',
220 'simple_renderer',
221 'WebServiceTestCase',
222@@ -174,6 +175,23 @@
223 return default
224
225
226+def pformat_value(value):
227+ """Pretty-format a single value.
228+
229+ This is similar to `repr()`, but for doctest compatibility we format
230+ text and bytes in a way that looks the same on both Python 2 and 3.
231+ JSON strings are always Unicode, never bytes, so this doesn't introduce
232+ ambiguity.
233+ """
234+ if isinstance(value, six.text_type):
235+ if "'" in value and '"' not in value:
236+ return '"%s"' % value
237+ else:
238+ return "'%s'" % value.replace("'", "\\'")
239+ else:
240+ return repr(value)
241+
242+
243 def pprint_entry(json_body):
244 """Pretty-print a webservice entry JSON representation.
245
246@@ -182,7 +200,7 @@
247 """
248 for key, value in sorted(json_body.items()):
249 if key != 'http_etag':
250- print('%s: %r' % (key, value))
251+ print('%s: %s' % (key, pformat_value(value)))
252
253
254 def pprint_collection(json_body):
255@@ -191,7 +209,7 @@
256 if key == 'total_size_link':
257 continue
258 if key != 'entries':
259- print('%s: %r' % (key, value))
260+ print('%s: %s' % (key, pformat_value(value)))
261 print('---')
262 for entry in json_body['entries']:
263 pprint_entry(entry)

Subscribers

People subscribed via source and target branches