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

Proposed by Colin Watson
Status: Merged
Merged at revision: 232
Proposed branch: lp:~cjwatson/lazr.restful/py3-print
Merge into: lp:lazr.restful
Diff against target: 4807 lines (+722/-600)
75 files modified
src/lazr/restful/__init__.py (+2/-0)
src/lazr/restful/_bytestorage.py (+2/-0)
src/lazr/restful/_operation.py (+2/-0)
src/lazr/restful/_resource.py (+2/-0)
src/lazr/restful/debug.py (+4/-3)
src/lazr/restful/declarations.py (+2/-0)
src/lazr/restful/directives/__init__.py (+2/-0)
src/lazr/restful/docs/absoluteurl.rst (+10/-10)
src/lazr/restful/docs/checker-utilities.rst (+3/-3)
src/lazr/restful/docs/debug.rst (+4/-4)
src/lazr/restful/docs/django.rst (+3/-3)
src/lazr/restful/docs/interface.rst (+4/-4)
src/lazr/restful/docs/multiversion.rst (+33/-33)
src/lazr/restful/docs/utils.rst (+28/-28)
src/lazr/restful/docs/webservice-declarations.rst (+94/-94)
src/lazr/restful/docs/webservice-error.rst (+5/-5)
src/lazr/restful/docs/webservice-marshallers.rst (+27/-27)
src/lazr/restful/docs/webservice-request.rst (+3/-3)
src/lazr/restful/docs/webservice.rst (+41/-41)
src/lazr/restful/error.py (+2/-0)
src/lazr/restful/example/base/filemanager.py (+2/-0)
src/lazr/restful/example/base/interfaces.py (+2/-0)
src/lazr/restful/example/base/root.py (+2/-0)
src/lazr/restful/example/base/security.py (+2/-0)
src/lazr/restful/example/base/subscribers.py (+2/-0)
src/lazr/restful/example/base/tests/collection.txt (+16/-16)
src/lazr/restful/example/base/tests/entry.txt (+130/-130)
src/lazr/restful/example/base/tests/field.txt (+47/-47)
src/lazr/restful/example/base/tests/hostedfile.txt (+21/-21)
src/lazr/restful/example/base/tests/redirect.txt (+5/-5)
src/lazr/restful/example/base/tests/representation-cache.txt (+33/-33)
src/lazr/restful/example/base/tests/root.txt (+12/-12)
src/lazr/restful/example/base/tests/service.txt (+12/-12)
src/lazr/restful/example/base/tests/test_integration.py (+8/-1)
src/lazr/restful/example/base/tests/wadl.txt (+8/-8)
src/lazr/restful/example/base/traversal.py (+2/-0)
src/lazr/restful/example/base_extended/README.txt (+1/-1)
src/lazr/restful/example/base_extended/comments.py (+2/-0)
src/lazr/restful/example/base_extended/tests/test_integration.py (+8/-1)
src/lazr/restful/example/multiversion/resources.py (+2/-0)
src/lazr/restful/example/multiversion/root.py (+2/-0)
src/lazr/restful/example/multiversion/tests/introduction.txt (+26/-26)
src/lazr/restful/example/multiversion/tests/operation.txt (+13/-13)
src/lazr/restful/example/multiversion/tests/test_integration.py (+8/-1)
src/lazr/restful/example/multiversion/tests/wadl.txt (+7/-7)
src/lazr/restful/example/wsgi/resources.py (+2/-0)
src/lazr/restful/example/wsgi/root.py (+2/-0)
src/lazr/restful/example/wsgi/run.py (+3/-0)
src/lazr/restful/example/wsgi/tests/introduction.txt (+2/-2)
src/lazr/restful/example/wsgi/tests/test_integration.py (+8/-1)
src/lazr/restful/fields.py (+2/-0)
src/lazr/restful/interface.py (+2/-0)
src/lazr/restful/interfaces/__init__.py (+2/-0)
src/lazr/restful/interfaces/_fields.py (+2/-0)
src/lazr/restful/interfaces/_rest.py (+2/-0)
src/lazr/restful/jsoncache.py (+2/-0)
src/lazr/restful/marshallers.py (+2/-0)
src/lazr/restful/metazcml.py (+2/-0)
src/lazr/restful/publisher.py (+2/-0)
src/lazr/restful/security.py (+2/-0)
src/lazr/restful/simple.py (+2/-0)
src/lazr/restful/tales.py (+2/-0)
src/lazr/restful/testing/event.py (+2/-0)
src/lazr/restful/testing/helpers.py (+2/-0)
src/lazr/restful/testing/tales.py (+2/-0)
src/lazr/restful/testing/webservice.py (+6/-4)
src/lazr/restful/tests/test_declarations.py (+2/-0)
src/lazr/restful/tests/test_docs.py (+7/-1)
src/lazr/restful/tests/test_error.py (+2/-0)
src/lazr/restful/tests/test_etag.py (+2/-0)
src/lazr/restful/tests/test_navigation.py (+2/-0)
src/lazr/restful/tests/test_utils.py (+2/-0)
src/lazr/restful/tests/test_webservice.py (+2/-0)
src/lazr/restful/utils.py (+2/-0)
src/lazr/restful/wsgi.py (+2/-0)
To merge this branch: bzr merge lp:~cjwatson/lazr.restful/py3-print
Reviewer Review Type Date Requested Status
Kristian Glass (community) Approve
Review via email: mp+378511@code.launchpad.net

Commit message

Apply absolute_import and print_function __future__ imports.

Description of the change

There's some minor fiddling to get doctests set up properly, but aside from that this is a more or less mechanical translation to Python 3's print syntax.

To post a comment you must log in.
Revision history for this message
Kristian Glass (doismellburning) wrote :

I skimmed and sample-reviewed this and what I saw looked good - given the nature of it, I'd happily approve merging it on that basis

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/__init__.py'
--- src/lazr/restful/__init__.py 2009-08-05 17:34:30 +0000
+++ src/lazr/restful/__init__.py 2020-02-04 11:56:15 +0000
@@ -17,6 +17,8 @@
1717
18# pylint: disable-msg=W040118# pylint: disable-msg=W0401
1919
20from __future__ import absolute_import, print_function
21
20import pkg_resources22import pkg_resources
21__version__ = pkg_resources.resource_string("lazr.restful", "version.txt").strip()23__version__ = pkg_resources.resource_string("lazr.restful", "version.txt").strip()
2224
2325
=== modified file 'src/lazr/restful/_bytestorage.py'
--- src/lazr/restful/_bytestorage.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/_bytestorage.py 2020-02-04 11:56:15 +0000
@@ -16,6 +16,8 @@
1616
17"""Classes for a resource that implements a binary file repository."""17"""Classes for a resource that implements a binary file repository."""
1818
19from __future__ import absolute_import, print_function
20
19__metaclass__ = type21__metaclass__ = type
20__all__ = [22__all__ = [
21 'ByteStorageResource',23 'ByteStorageResource',
2224
=== modified file 'src/lazr/restful/_operation.py'
--- src/lazr/restful/_operation.py 2018-04-23 12:42:00 +0000
+++ src/lazr/restful/_operation.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Base classes for one-off HTTP operations."""3"""Base classes for one-off HTTP operations."""
44
5from __future__ import absolute_import, print_function
6
5import simplejson7import simplejson
68
7from zope.component import getMultiAdapter, getUtility, queryMultiAdapter9from zope.component import getMultiAdapter, getUtility, queryMultiAdapter
810
=== modified file 'src/lazr/restful/_resource.py'
--- src/lazr/restful/_resource.py 2019-11-12 14:26:41 +0000
+++ src/lazr/restful/_resource.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Base classes for HTTP resources."""3"""Base classes for HTTP resources."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7__all__ = [9__all__ = [
810
=== modified file 'src/lazr/restful/debug.py'
--- src/lazr/restful/debug.py 2009-04-06 11:59:33 +0000
+++ src/lazr/restful/debug.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Module docstring goes here."""3"""Module docstring goes here."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 "debug_proxy",9 "debug_proxy",
@@ -64,7 +66,7 @@
64def debug_proxy(obj):66def debug_proxy(obj):
65 """Return informative text about the proxies wrapping obj.67 """Return informative text about the proxies wrapping obj.
6668
67 Usually used like print debug_proxy(obj).69 Usually used like print(debug_proxy(obj)).
68 """70 """
69 if not zope.proxy.isProxy(obj):71 if not zope.proxy.isProxy(obj):
70 return "%r doesn't have any proxies." % obj72 return "%r doesn't have any proxies." % obj
@@ -73,6 +75,5 @@
73 if not zope.proxy.isProxy(proxy):75 if not zope.proxy.isProxy(proxy):
74 break76 break
75 printer = proxy_formatters.get(type(proxy), default_proxy_formatter)77 printer = proxy_formatters.get(type(proxy), default_proxy_formatter)
76 print >>buf, printer(proxy)78 print(printer(proxy), file=buf)
77 return buf.getvalue()79 return buf.getvalue()
78
7980
=== modified file 'src/lazr/restful/declarations.py'
--- src/lazr/restful/declarations.py 2018-09-28 15:46:34 +0000
+++ src/lazr/restful/declarations.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Declaration helpers to define a web service."""3"""Declaration helpers to define a web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'COLLECTION_TYPE',9 'COLLECTION_TYPE',
810
=== modified file 'src/lazr/restful/directives/__init__.py'
--- src/lazr/restful/directives/__init__.py 2018-04-23 12:42:00 +0000
+++ src/lazr/restful/directives/__init__.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Martian directives used in lazr.restful."""3"""Martian directives used in lazr.restful."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = ['request_class',8__all__ = ['request_class',
7 'publication_class',9 'publication_class',
810
=== modified file 'src/lazr/restful/docs/absoluteurl.rst'
--- src/lazr/restful/docs/absoluteurl.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/absoluteurl.rst 2020-02-04 11:56:15 +0000
@@ -56,13 +56,13 @@
56Calling the RootResourceAbsoluteURL will give the service root's56Calling the RootResourceAbsoluteURL will give the service root's
57absolute URL.57absolute URL.
5858
59 >>> print adapter()59 >>> print(adapter())
60 https://hostname:1000/root_uri_prefix/active_version/60 https://hostname:1000/root_uri_prefix/active_version/
6161
62Converting the adapter to a string will give the same result, but62Converting the adapter to a string will give the same result, but
63without the trailing slash.63without the trailing slash.
6464
65 >>> print str(adapter)65 >>> print(str(adapter))
66 https://hostname:1000/root_uri_prefix/active_version66 https://hostname:1000/root_uri_prefix/active_version
6767
68(This is useful for the recursive case. When finding the URL to a68(This is useful for the recursive case. When finding the URL to a
@@ -74,11 +74,11 @@
74Changing the web service configuration changes the generated URLs.74Changing the web service configuration changes the generated URLs.
7575
76 >>> configuration.use_https = False76 >>> configuration.use_https = False
77 >>> print getMultiAdapter((resource, request), IAbsoluteURL)()77 >>> print(getMultiAdapter((resource, request), IAbsoluteURL)())
78 http://hostname:1000/root_uri_prefix/active_version/78 http://hostname:1000/root_uri_prefix/active_version/
7979
80 >>> configuration.port = None80 >>> configuration.port = None
81 >>> print getMultiAdapter((resource, request), IAbsoluteURL)()81 >>> print(getMultiAdapter((resource, request), IAbsoluteURL)())
82 http://hostname/root_uri_prefix/active_version/82 http://hostname/root_uri_prefix/active_version/
8383
84The URL generated includes a version identifier taken from the84The URL generated includes a version identifier taken from the
@@ -87,7 +87,7 @@
87 >>> request.annotations[request.VERSION_ANNOTATION] = (87 >>> request.annotations[request.VERSION_ANNOTATION] = (
88 ... 'latest_version')88 ... 'latest_version')
89 >>> adapter = getMultiAdapter((resource, request), IAbsoluteURL)89 >>> adapter = getMultiAdapter((resource, request), IAbsoluteURL)
90 >>> print adapter()90 >>> print(adapter())
91 http://hostname/root_uri_prefix/latest_version/91 http://hostname/root_uri_prefix/latest_version/
9292
93For purposes of URL generation, the annotation doesn't have to be a93For purposes of URL generation, the annotation doesn't have to be a
@@ -96,7 +96,7 @@
96 >>> request.annotations[request.VERSION_ANNOTATION] = (96 >>> request.annotations[request.VERSION_ANNOTATION] = (
97 ... 'no_such_version')97 ... 'no_such_version')
98 >>> adapter = getMultiAdapter((resource, request), IAbsoluteURL)98 >>> adapter = getMultiAdapter((resource, request), IAbsoluteURL)
99 >>> print adapter()99 >>> print(adapter())
100 http://hostname/root_uri_prefix/no_such_version/100 http://hostname/root_uri_prefix/no_such_version/
101101
102(However, the lazr.restful traversal code will reject an invalid102(However, the lazr.restful traversal code will reject an invalid
@@ -168,7 +168,7 @@
168followed by two from the ChildResource itself.168followed by two from the ChildResource itself.
169169
170 >>> resource = ChildResource()170 >>> resource = ChildResource()
171 >>> print str(getMultiAdapter((resource, request), IAbsoluteURL))171 >>> print(str(getMultiAdapter((resource, request), IAbsoluteURL)))
172 http://dummyurl/child-part1/child-part2172 http://dummyurl/child-part1/child-part2
173173
174Now let's put an object underneath the child resource that implements174Now let's put an object underneath the child resource that implements
@@ -184,8 +184,8 @@
184resource, the two from the ChildResource, and one from the184resource, the two from the ChildResource, and one from the
185GrandchildResource itself.185GrandchildResource itself.
186186
187 >>> print str(getMultiAdapter(187 >>> print(str(getMultiAdapter(
188 ... (GrandchildResource(), request), IAbsoluteURL))188 ... (GrandchildResource(), request), IAbsoluteURL)))
189 http://dummyurl/child-part1/child-part2/grandchild189 http://dummyurl/child-part1/child-part2/grandchild
190190
191Edge cases and error handling191Edge cases and error handling
@@ -195,7 +195,7 @@
195It even escapes slashes, if a slash shows up inside a path part.195It even escapes slashes, if a slash shows up inside a path part.
196196
197 >>> resource.__path_parts__ = ["!foo!", "bar/baz"]197 >>> resource.__path_parts__ = ["!foo!", "bar/baz"]
198 >>> print str(getMultiAdapter((resource, request), IAbsoluteURL))198 >>> print(str(getMultiAdapter((resource, request), IAbsoluteURL)))
199 http://dummyurl/%21foo%21/bar%2Fbaz199 http://dummyurl/%21foo%21/bar%2Fbaz
200200
201If the __path_parts__ is not iterable, an attempt to get the URL201If the __path_parts__ is not iterable, an attempt to get the URL
202202
=== modified file 'src/lazr/restful/docs/checker-utilities.rst'
--- src/lazr/restful/docs/checker-utilities.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/checker-utilities.rst 2020-02-04 11:56:15 +0000
@@ -46,7 +46,7 @@
4646
47ProxyFactory wraps the content using the defined checker.47ProxyFactory wraps the content using the defined checker.
4848
49 >>> print debug_proxy(ProxyFactory(content))49 >>> print(debug_proxy(ProxyFactory(content)))
50 zope.security._proxy._Proxy (using zope.security.checker.Checker)50 zope.security._proxy._Proxy (using zope.security.checker.Checker)
51 public: aMethod, a_field, a_read_only_field, an_attr51 public: aMethod, a_field, a_read_only_field, an_attr
5252
@@ -55,7 +55,7 @@
5555
56 >>> undefineChecker(MyContent)56 >>> undefineChecker(MyContent)
57 >>> protect_schema(MyContent, MySchema, read_permission='lazr.View')57 >>> protect_schema(MyContent, MySchema, read_permission='lazr.View')
58 >>> print debug_proxy(ProxyFactory(content))58 >>> print(debug_proxy(ProxyFactory(content)))
59 zope.security._proxy._Proxy (using zope.security.checker.Checker)59 zope.security._proxy._Proxy (using zope.security.checker.Checker)
60 lazr.View: aMethod, a_field, a_read_only_field, an_attr60 lazr.View: aMethod, a_field, a_read_only_field, an_attr
6161
@@ -64,7 +64,7 @@
6464
65 >>> undefineChecker(MyContent)65 >>> undefineChecker(MyContent)
66 >>> protect_schema(MyContent, MySchema, write_permission='lazr.Edit')66 >>> protect_schema(MyContent, MySchema, write_permission='lazr.Edit')
67 >>> print debug_proxy(ProxyFactory(content))67 >>> print(debug_proxy(ProxyFactory(content)))
68 zope.security._proxy._Proxy (using zope.security.checker.Checker)68 zope.security._proxy._Proxy (using zope.security.checker.Checker)
69 lazr.Edit (set): a_field, an_attr69 lazr.Edit (set): a_field, an_attr
70 public: aMethod, a_field, a_read_only_field, an_attr70 public: aMethod, a_field, a_read_only_field, an_attr
7171
=== modified file 'src/lazr/restful/docs/debug.rst'
--- src/lazr/restful/docs/debug.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/debug.rst 2020-02-04 11:56:15 +0000
@@ -15,14 +15,14 @@
1515
16In the simple case, where no proxy is used, as simple message is output:16In the simple case, where no proxy is used, as simple message is output:
1717
18 >>> print debug_proxy(1)18 >>> print(debug_proxy(1))
19 1 doesn't have any proxies.19 1 doesn't have any proxies.
2020
21Otherwise, it prints the proxy type for each proxy used:21Otherwise, it prints the proxy type for each proxy used:
2222
23 >>> from zope.proxy import ProxyBase23 >>> from zope.proxy import ProxyBase
2424
25 >>> print debug_proxy(ProxyBase(1))25 >>> print(debug_proxy(ProxyBase(1)))
26 zope.proxy.ProxyBase26 zope.proxy.ProxyBase
2727
28If more than one proxy is used, information about the proxies will be28If more than one proxy is used, information about the proxies will be
@@ -31,7 +31,7 @@
31 >>> class SimpleProxy(ProxyBase):31 >>> class SimpleProxy(ProxyBase):
32 ... """A very simple proxy."""32 ... """A very simple proxy."""
3333
34 >>> print debug_proxy(SimpleProxy(ProxyBase(1)))34 >>> print(debug_proxy(SimpleProxy(ProxyBase(1))))
35 SimpleProxy35 SimpleProxy
36 zope.proxy.ProxyBase36 zope.proxy.ProxyBase
3737
@@ -45,7 +45,7 @@
45 ... {'value': CheckerPublic,45 ... {'value': CheckerPublic,
46 ... 'some_other_value': 'lazr.Edit'})46 ... 'some_other_value': 'lazr.Edit'})
47 >>> p = ProxyFactory(1, checker)47 >>> p = ProxyFactory(1, checker)
48 >>> print debug_proxy(p)48 >>> print(debug_proxy(p))
49 zope.security._proxy._Proxy (using zope.security.checker.Checker)49 zope.security._proxy._Proxy (using zope.security.checker.Checker)
50 lazr.Edit: __add__50 lazr.Edit: __add__
51 lazr.Edit (set): some_other_value51 lazr.Edit (set): some_other_value
5252
=== modified file 'src/lazr/restful/docs/django.rst'
--- src/lazr/restful/docs/django.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/django.rst 2020-02-04 11:56:15 +0000
@@ -166,7 +166,7 @@
166 >>> from zope.location.interfaces import ILocation166 >>> from zope.location.interfaces import ILocation
167 >>> resource = SubordinateResource()167 >>> resource = SubordinateResource()
168 >>> as_location = ILocation(resource)168 >>> as_location = ILocation(resource)
169 >>> print as_location.__name__169 >>> print(as_location.__name__)
170 myname170 myname
171171
172It's also possible to adapt a Django model object to IAbsoluteURL and172It's also possible to adapt a Django model object to IAbsoluteURL and
@@ -176,7 +176,7 @@
176 >>> from zope.traversing.browser.interfaces import IAbsoluteURL176 >>> from zope.traversing.browser.interfaces import IAbsoluteURL
177 >>> from lazr.restful.simple import Request177 >>> from lazr.restful.simple import Request
178 >>> request = Request("", {})178 >>> request = Request("", {})
179 >>> print str(getMultiAdapter((resource, request), IAbsoluteURL))179 >>> print(str(getMultiAdapter((resource, request), IAbsoluteURL)))
180 http://dummyurl/myname180 http://dummyurl/myname
181181
182182
@@ -227,7 +227,7 @@
227 >>> len(sequence)227 >>> len(sequence)
228 3228 3
229229
230 >>> print sequence[1]230 >>> print(sequence[1])
231 bar231 bar
232232
233 >>> sequence[1:3]233 >>> sequence[1:3]
234234
=== modified file 'src/lazr/restful/docs/interface.rst'
--- src/lazr/restful/docs/interface.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/interface.rst 2020-02-04 11:56:15 +0000
@@ -52,9 +52,9 @@
5252
53 >>> sorted(MyForm1.names())53 >>> sorted(MyForm1.names())
54 ['age', 'name']54 ['age', 'name']
55 >>> print MyForm1.get('age').title55 >>> print(MyForm1.get('age').title)
56 Your age:56 Your age:
57 >>> print MyForm1.get('name').title57 >>> print(MyForm1.get('name').title)
58 Your name:58 Your name:
5959
60The interface attribute points to the correct interface:60The interface attribute points to the correct interface:
@@ -64,7 +64,7 @@
6464
65And the original field wasn't updated:65And the original field wasn't updated:
6666
67 >>> print MyModel.get('age').title67 >>> print(MyModel.get('age').title)
68 Number of years since birth68 Number of years since birth
69 >>> MyModel.get('name').interface is MyModel69 >>> MyModel.get('name').interface is MyModel
70 True70 True
@@ -132,7 +132,7 @@
132132
133The interface attribute is cleared:133The interface attribute is cleared:
134134
135 >>> print illuminated.interface135 >>> print(illuminated.interface)
136 None136 None
137137
138It also supports the Field ordering (the copied field will have an138It also supports the Field ordering (the copied field will have an
139139
=== modified file 'src/lazr/restful/docs/multiversion.rst'
--- src/lazr/restful/docs/multiversion.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/multiversion.rst 2020-02-04 11:56:15 +0000
@@ -433,19 +433,19 @@
433 >>> request_beta = create_web_service_request('/beta/')433 >>> request_beta = create_web_service_request('/beta/')
434 >>> alsoProvides(request_beta, IBetaVersion)434 >>> alsoProvides(request_beta, IBetaVersion)
435 >>> beta_entry = getMultiAdapter((C1, request_beta), IEntry)435 >>> beta_entry = getMultiAdapter((C1, request_beta), IEntry)
436 >>> print beta_entry.fax436 >>> print(beta_entry.fax)
437 111-2121437 111-2121
438438
439 >>> request_10 = create_web_service_request('/1.0/')439 >>> request_10 = create_web_service_request('/1.0/')
440 >>> alsoProvides(request_10, I10Version)440 >>> alsoProvides(request_10, I10Version)
441 >>> one_oh_entry = getMultiAdapter((C1, request_10), IEntry)441 >>> one_oh_entry = getMultiAdapter((C1, request_10), IEntry)
442 >>> print one_oh_entry.fax_number442 >>> print(one_oh_entry.fax_number)
443 111-2121443 111-2121
444444
445 >>> request_dev = create_web_service_request('/dev/')445 >>> request_dev = create_web_service_request('/dev/')
446 >>> alsoProvides(request_dev, IDevVersion)446 >>> alsoProvides(request_dev, IDevVersion)
447 >>> dev_entry = getMultiAdapter((C1, request_dev), IEntry)447 >>> dev_entry = getMultiAdapter((C1, request_dev), IEntry)
448 >>> print dev_entry.fax448 >>> print(dev_entry.fax)
449 Traceback (most recent call last):449 Traceback (most recent call last):
450 ...450 ...
451 AttributeError: 'ContactEntryDev' object has no attribute 'fax'451 AttributeError: 'ContactEntryDev' object has no attribute 'fax'
@@ -636,17 +636,17 @@
636 ... RootResourceAbsoluteURL, [cls, IBrowserRequest])636 ... RootResourceAbsoluteURL, [cls, IBrowserRequest])
637637
638 >>> beta_request = create_web_service_request('/beta/')638 >>> beta_request = create_web_service_request('/beta/')
639 >>> print beta_request.traverse(None)639 >>> print(beta_request.traverse(None))
640 <BetaServiceRootResource object...>640 <BetaServiceRootResource object...>
641641
642 >>> print absoluteURL(beta_app, beta_request)642 >>> print(absoluteURL(beta_app, beta_request))
643 http://api.multiversion.dev/beta/643 http://api.multiversion.dev/beta/
644644
645 >>> dev_request = create_web_service_request('/dev/')645 >>> dev_request = create_web_service_request('/dev/')
646 >>> print dev_request.traverse(None)646 >>> print(dev_request.traverse(None))
647 <PostBetaServiceRootResource object...>647 <PostBetaServiceRootResource object...>
648648
649 >>> print absoluteURL(dev_app, dev_request)649 >>> print(absoluteURL(dev_app, dev_request))
650 http://api.multiversion.dev/dev/650 http://api.multiversion.dev/dev/
651651
652Request lifecycle652Request lifecycle
@@ -668,7 +668,7 @@
668 <BetaServiceRootResource object ...>668 <BetaServiceRootResource object ...>
669 >>> IBetaVersion.providedBy(request_beta)669 >>> IBetaVersion.providedBy(request_beta)
670 True670 True
671 >>> print request_beta.version671 >>> print(request_beta.version)
672 beta672 beta
673673
674Using the web service674Using the web service
@@ -686,10 +686,10 @@
686 >>> request = create_web_service_request('/beta/')686 >>> request = create_web_service_request('/beta/')
687 >>> resource = request.traverse(None)687 >>> resource = request.traverse(None)
688 >>> body = simplejson.loads(resource())688 >>> body = simplejson.loads(resource())
689 >>> print sorted(body.keys())689 >>> print(sorted(body.keys()))
690 ['contacts_collection_link', 'resource_type_link']690 ['contacts_collection_link', 'resource_type_link']
691691
692 >>> print body['contacts_collection_link']692 >>> print(body['contacts_collection_link'])
693 http://api.multiversion.dev/beta/contact_list693 http://api.multiversion.dev/beta/contact_list
694694
695Here's the contact list.695Here's the contact list.
@@ -701,7 +701,7 @@
701but since we happen to know which object it is, we can pass it into701but since we happen to know which object it is, we can pass it into
702absoluteURL along with the request object, and get the correct URL.702absoluteURL along with the request object, and get the correct URL.
703703
704 >>> print absoluteURL(contact_set, request)704 >>> print(absoluteURL(contact_set, request))
705 http://api.multiversion.dev/beta/contact_list705 http://api.multiversion.dev/beta/contact_list
706706
707 >>> body = simplejson.loads(resource())707 >>> body = simplejson.loads(resource())
@@ -709,7 +709,7 @@
709 3709 3
710 >>> for link in sorted(710 >>> for link in sorted(
711 ... [contact['self_link'] for contact in body['entries']]):711 ... [contact['self_link'] for contact in body['entries']]):
712 ... print link712 ... print(link)
713 http://api.multiversion.dev/beta/contact_list/Cleo%20Python713 http://api.multiversion.dev/beta/contact_list/Cleo%20Python
714 http://api.multiversion.dev/beta/contact_list/Fax-your-order%20Pizza714 http://api.multiversion.dev/beta/contact_list/Fax-your-order%20Pizza
715 http://api.multiversion.dev/beta/contact_list/Oliver%20Bluth715 http://api.multiversion.dev/beta/contact_list/Oliver%20Bluth
@@ -725,15 +725,15 @@
725pass it into absoluteURL along with this request object, and get the725pass it into absoluteURL along with this request object, and get the
726object's URL.726object's URL.
727727
728 >>> print C1.name728 >>> print(C1.name)
729 Cleo Python729 Cleo Python
730 >>> print absoluteURL(C1, request_beta)730 >>> print(absoluteURL(C1, request_beta))
731 http://api.multiversion.dev/beta/contact_list/Cleo%20Python731 http://api.multiversion.dev/beta/contact_list/Cleo%20Python
732732
733 >>> body = simplejson.loads(resource())733 >>> body = simplejson.loads(resource())
734 >>> sorted(body.keys())734 >>> sorted(body.keys())
735 ['fax', 'http_etag', 'name', 'phone', 'resource_type_link', 'self_link']735 ['fax', 'http_etag', 'name', 'phone', 'resource_type_link', 'self_link']
736 >>> print body['name']736 >>> print(body['name'])
737 Cleo Python737 Cleo Python
738738
739We can traverse through an entry to one of its fields.739We can traverse through an entry to one of its fields.
@@ -741,7 +741,7 @@
741 >>> request_beta = create_web_service_request(741 >>> request_beta = create_web_service_request(
742 ... '/beta/contact_list/Cleo Python/fax')742 ... '/beta/contact_list/Cleo Python/fax')
743 >>> field = request_beta.traverse(None)743 >>> field = request_beta.traverse(None)
744 >>> print simplejson.loads(field())744 >>> print(simplejson.loads(field()))
745 111-2121745 111-2121
746746
747We can invoke a named operation, and it returns a total_size (because747We can invoke a named operation, and it returns a total_size (because
@@ -778,13 +778,13 @@
778 >>> request = create_web_service_request('/1.0/')778 >>> request = create_web_service_request('/1.0/')
779 >>> resource = request.traverse(None)779 >>> resource = request.traverse(None)
780 >>> body = simplejson.loads(resource())780 >>> body = simplejson.loads(resource())
781 >>> print sorted(body.keys())781 >>> print(sorted(body.keys()))
782 ['contacts_collection_link', 'resource_type_link']782 ['contacts_collection_link', 'resource_type_link']
783783
784Note that 'contacts_collection_link' points to a different URL in784Note that 'contacts_collection_link' points to a different URL in
785'1.0' than in 'dev'.785'1.0' than in 'dev'.
786786
787 >>> print body['contacts_collection_link']787 >>> print(body['contacts_collection_link'])
788 http://api.multiversion.dev/1.0/contacts788 http://api.multiversion.dev/1.0/contacts
789789
790An attempt to use the 'beta' name of the contact list in the '1.0' web790An attempt to use the 'beta' name of the contact list in the '1.0' web
@@ -800,7 +800,7 @@
800800
801 >>> request = create_web_service_request('/1.0/contacts')801 >>> request = create_web_service_request('/1.0/contacts')
802 >>> resource = request.traverse(None)802 >>> resource = request.traverse(None)
803 >>> print absoluteURL(contact_set, request)803 >>> print(absoluteURL(contact_set, request))
804 http://api.multiversion.dev/1.0/contacts804 http://api.multiversion.dev/1.0/contacts
805805
806 >>> body = simplejson.loads(resource())806 >>> body = simplejson.loads(resource())
@@ -808,7 +808,7 @@
808 3808 3
809 >>> for link in sorted(809 >>> for link in sorted(
810 ... [contact['self_link'] for contact in body['entries']]):810 ... [contact['self_link'] for contact in body['entries']]):
811 ... print link811 ... print(link)
812 http://api.multiversion.dev/1.0/contacts/Cleo%20Python812 http://api.multiversion.dev/1.0/contacts/Cleo%20Python
813 http://api.multiversion.dev/1.0/contacts/Fax-your-order%20Pizza813 http://api.multiversion.dev/1.0/contacts/Fax-your-order%20Pizza
814 http://api.multiversion.dev/1.0/contacts/Oliver%20Bluth814 http://api.multiversion.dev/1.0/contacts/Oliver%20Bluth
@@ -818,7 +818,7 @@
818 >>> request_10 = create_web_service_request(818 >>> request_10 = create_web_service_request(
819 ... '/1.0/contacts/Cleo Python')819 ... '/1.0/contacts/Cleo Python')
820 >>> resource = request_10.traverse(None)820 >>> resource = request_10.traverse(None)
821 >>> print absoluteURL(C1, request_10)821 >>> print(absoluteURL(C1, request_10))
822 http://api.multiversion.dev/1.0/contacts/Cleo%20Python822 http://api.multiversion.dev/1.0/contacts/Cleo%20Python
823823
824Note that the 'fax' and 'phone' fields are now called 'fax_number' and824Note that the 'fax' and 'phone' fields are now called 'fax_number' and
@@ -828,7 +828,7 @@
828 >>> sorted(body.keys())828 >>> sorted(body.keys())
829 ['fax_number', 'http_etag', 'name', 'phone_number',829 ['fax_number', 'http_etag', 'name', 'phone_number',
830 'resource_type_link', 'self_link']830 'resource_type_link', 'self_link']
831 >>> print body['name']831 >>> print(body['name'])
832 Cleo Python832 Cleo Python
833833
834We can traverse through an entry to one of its fields.834We can traverse through an entry to one of its fields.
@@ -836,7 +836,7 @@
836 >>> request_10 = create_web_service_request(836 >>> request_10 = create_web_service_request(
837 ... '/1.0/contacts/Cleo Python/fax_number')837 ... '/1.0/contacts/Cleo Python/fax_number')
838 >>> field = request_10.traverse(None)838 >>> field = request_10.traverse(None)
839 >>> print simplejson.loads(field())839 >>> print(simplejson.loads(field()))
840 111-2121840 111-2121
841841
842The fax field in '1.0' is called 'fax_number', and attempting842The fax field in '1.0' is called 'fax_number', and attempting
@@ -867,7 +867,7 @@
867 ...867 ...
868 KeyError: 'total_size'868 KeyError: 'total_size'
869869
870 >>> print result['total_size_link']870 >>> print(result['total_size_link'])
871 http://.../1.0/contacts?string=e&ws.op=find&ws.show=total_size871 http://.../1.0/contacts?string=e&ws.op=find&ws.show=total_size
872 >>> size_request = create_web_service_request(872 >>> size_request = create_web_service_request(
873 ... '/1.0/contacts',873 ... '/1.0/contacts',
@@ -875,7 +875,7 @@
875 ... 'string=e&ws.op=find&ws.show=total_size'})875 ... 'string=e&ws.op=find&ws.show=total_size'})
876 >>> operation = size_request.traverse(None)876 >>> operation = size_request.traverse(None)
877 >>> result = simplejson.loads(operation())877 >>> result = simplejson.loads(operation())
878 >>> print result878 >>> print(result)
879 3879 3
880880
881If the resultset fits on a single page, total_size will be provided881If the resultset fits on a single page, total_size will be provided
@@ -897,7 +897,7 @@
897 ... '/1.0/contacts',897 ... '/1.0/contacts',
898 ... environ={'QUERY_STRING' : 'ws.op=findContacts&string=Cleo'})898 ... environ={'QUERY_STRING' : 'ws.op=findContacts&string=Cleo'})
899 >>> operation = request_10.traverse(None)899 >>> operation = request_10.traverse(None)
900 >>> print operation()900 >>> print(operation())
901 No such operation: findContacts901 No such operation: findContacts
902902
903Dev903Dev
@@ -908,17 +908,17 @@
908 >>> request = create_web_service_request('/dev/')908 >>> request = create_web_service_request('/dev/')
909 >>> resource = request.traverse(None)909 >>> resource = request.traverse(None)
910 >>> body = simplejson.loads(resource())910 >>> body = simplejson.loads(resource())
911 >>> print sorted(body.keys())911 >>> print(sorted(body.keys()))
912 ['contacts_collection_link', 'resource_type_link']912 ['contacts_collection_link', 'resource_type_link']
913913
914 >>> print body['contacts_collection_link']914 >>> print(body['contacts_collection_link'])
915 http://api.multiversion.dev/dev/contacts915 http://api.multiversion.dev/dev/contacts
916916
917Here's the contact list.917Here's the contact list.
918918
919 >>> request_dev = create_web_service_request('/dev/contacts')919 >>> request_dev = create_web_service_request('/dev/contacts')
920 >>> resource = request_dev.traverse(None)920 >>> resource = request_dev.traverse(None)
921 >>> print absoluteURL(contact_set, request_dev)921 >>> print(absoluteURL(contact_set, request_dev))
922 http://api.multiversion.dev/dev/contacts922 http://api.multiversion.dev/dev/contacts
923923
924 >>> body = simplejson.loads(resource())924 >>> body = simplejson.loads(resource())
@@ -926,7 +926,7 @@
926 2926 2
927 >>> for link in sorted(927 >>> for link in sorted(
928 ... [contact['self_link'] for contact in body['entries']]):928 ... [contact['self_link'] for contact in body['entries']]):
929 ... print link929 ... print(link)
930 http://api.multiversion.dev/dev/contacts/Cleo%20Python930 http://api.multiversion.dev/dev/contacts/Cleo%20Python
931 http://api.multiversion.dev/dev/contacts/Oliver%20Bluth931 http://api.multiversion.dev/dev/contacts/Oliver%20Bluth
932932
@@ -935,7 +935,7 @@
935 >>> request_dev = create_web_service_request(935 >>> request_dev = create_web_service_request(
936 ... '/dev/contacts/Cleo Python')936 ... '/dev/contacts/Cleo Python')
937 >>> resource = request_dev.traverse(None)937 >>> resource = request_dev.traverse(None)
938 >>> print absoluteURL(C1, request_dev)938 >>> print(absoluteURL(C1, request_dev))
939 http://api.multiversion.dev/dev/contacts/Cleo%20Python939 http://api.multiversion.dev/dev/contacts/Cleo%20Python
940940
941Note that the published field names have changed between 'dev' and941Note that the published field names have changed between 'dev' and
@@ -945,7 +945,7 @@
945 >>> body = simplejson.loads(resource())945 >>> body = simplejson.loads(resource())
946 >>> sorted(body.keys())946 >>> sorted(body.keys())
947 ['http_etag', 'name', 'phone_number', 'resource_type_link', 'self_link']947 ['http_etag', 'name', 'phone_number', 'resource_type_link', 'self_link']
948 >>> print body['name']948 >>> print(body['name'])
949 Cleo Python949 Cleo Python
950950
951We can traverse through an entry to one of its fields.951We can traverse through an entry to one of its fields.
@@ -953,7 +953,7 @@
953 >>> request_dev = create_web_service_request(953 >>> request_dev = create_web_service_request(
954 ... '/dev/contacts/Cleo Python/name')954 ... '/dev/contacts/Cleo Python/name')
955 >>> field = request_dev.traverse(None)955 >>> field = request_dev.traverse(None)
956 >>> print simplejson.loads(field())956 >>> print(simplejson.loads(field()))
957 Cleo Python957 Cleo Python
958958
959We cannot use 'dev' to traverse to a field not published in the 'dev'959We cannot use 'dev' to traverse to a field not published in the 'dev'
960960
=== modified file 'src/lazr/restful/docs/utils.rst'
--- src/lazr/restful/docs/utils.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/utils.rst 2020-02-04 11:56:15 +0000
@@ -39,9 +39,9 @@
39 Traceback (most recent call last):39 Traceback (most recent call last):
40 ...40 ...
41 KeyError: 'key'41 KeyError: 'key'
42 >>> print stack.get('key')42 >>> print(stack.get('key'))
43 None43 None
44 >>> print stack.get('key', 'default')44 >>> print(stack.get('key', 'default'))
45 default45 default
4646
47 >>> del stack['key']47 >>> del stack['key']
@@ -54,9 +54,9 @@
5454
55 >>> stack.push('dict #1')55 >>> stack.push('dict #1')
56 >>> stack['key'] = 'value'56 >>> stack['key'] = 'value'
57 >>> print stack['key']57 >>> print(stack['key'])
58 value58 value
59 >>> print stack.get('key', 'default')59 >>> print(stack.get('key', 'default'))
60 value60 value
6161
62 >>> 'key' in stack62 >>> 'key' in stack
@@ -70,9 +70,9 @@
70dictionary.70dictionary.
7171
72 >>> pair = stack.pop()72 >>> pair = stack.pop()
73 >>> print pair.version73 >>> print(pair.version)
74 dict #174 dict #1
75 >>> print pair.object75 >>> print(pair.object)
76 {'key': 'value'}76 {'key': 'value'}
7777
78 >>> stack.push('dict #1')78 >>> stack.push('dict #1')
@@ -84,9 +84,9 @@
84objects in the first.84objects in the first.
8585
86 >>> stack.push('dict #2')86 >>> stack.push('dict #2')
87 >>> print stack['key']87 >>> print(stack['key'])
88 value88 value
89 >>> print stack['key2']89 >>> print(stack['key2'])
90 {'key': 'value'}90 {'key': 'value'}
9191
92Every dict is initialized with a deep copy of the one below it.92Every dict is initialized with a deep copy of the one below it.
@@ -111,7 +111,7 @@
111You can find the dict for a given name with dict_for_name():111You can find the dict for a given name with dict_for_name():
112112
113 >>> for key, value in sorted(stack.dict_for_name('dict #1').items()):113 >>> for key, value in sorted(stack.dict_for_name('dict #1').items()):
114 ... print "%s: %s" % (key, value)114 ... print("%s: %s" % (key, value))
115 key: value115 key: value
116 key2: {'key': 'value'}116 key2: {'key': 'value'}
117117
@@ -128,7 +128,7 @@
128VersionedDict not to copy in values from the next lowest dictionary128VersionedDict not to copy in values from the next lowest dictionary
129in the stack.129in the stack.
130130
131 >>> print stack['key']131 >>> print(stack['key'])
132 Second dict value132 Second dict value
133133
134 >>> stack.push("An empty dictionary", empty=True)134 >>> stack.push("An empty dictionary", empty=True)
@@ -153,15 +153,15 @@
153 KeyError: 'key2'153 KeyError: 'key2'
154154
155 >>> stack['key'] = 'Brand new value'155 >>> stack['key'] = 'Brand new value'
156 >>> print stack['key']156 >>> print(stack['key'])
157 Brand new value157 Brand new value
158158
159If you pop the formerly empty dictionary off the stack...159If you pop the formerly empty dictionary off the stack...
160160
161 >>> pair = stack.pop()161 >>> pair = stack.pop()
162 >>> print pair.version162 >>> print(pair.version)
163 An empty dictionary163 An empty dictionary
164 >>> print pair.object164 >>> print(pair.object)
165 {'key': 'Brand new value'}165 {'key': 'Brand new value'}
166166
167...'key' and 'key2' are visible again.167...'key' and 'key2' are visible again.
@@ -195,15 +195,15 @@
195 ... 'TwoFields', ITwoFields,195 ... 'TwoFields', ITwoFields,
196 ... {'field_1': 'foo', 'field_2': 'bar', 'a_method': result})196 ... {'field_1': 'foo', 'field_2': 'bar', 'a_method': result})
197197
198 >>> print implementation.__name__198 >>> print(implementation.__name__)
199 TwoFields199 TwoFields
200 >>> ITwoFields.implementedBy(implementation)200 >>> ITwoFields.implementedBy(implementation)
201 True201 True
202 >>> print implementation.field_1202 >>> print(implementation.field_1)
203 foo203 foo
204 >>> print implementation.field_2204 >>> print(implementation.field_2)
205 bar205 bar
206 >>> print implementation().a_method()206 >>> print(implementation().a_method())
207 result207 result
208208
209If one of the interface's attributes is not defined in the dictionary,209If one of the interface's attributes is not defined in the dictionary,
@@ -214,7 +214,7 @@
214 >>> implementation = implement_from_dict(214 >>> implementation = implement_from_dict(
215 ... 'TwoFields', ITwoFields, {})215 ... 'TwoFields', ITwoFields, {})
216216
217 >>> print implementation.field_1217 >>> print(implementation.field_1)
218 field_1 default218 field_1 default
219219
220If an attribute is not present in the dictionary and its interface220If an attribute is not present in the dictionary and its interface
@@ -241,7 +241,7 @@
241 >>> implementation = implement_from_dict(241 >>> implementation = implement_from_dict(
242 ... 'TwoFields', ITwoFields, {}, superclass=TwoFieldsSuperclass)242 ... 'TwoFields', ITwoFields, {}, superclass=TwoFieldsSuperclass)
243243
244 >>> print implementation().a_method()244 >>> print(implementation().a_method())
245 superclass result245 superclass result
246246
247make_identifier_safe247make_identifier_safe
@@ -251,22 +251,22 @@
251string that can be used as a Python identifier.251string that can be used as a Python identifier.
252252
253 >>> from lazr.restful.utils import make_identifier_safe253 >>> from lazr.restful.utils import make_identifier_safe
254 >>> print make_identifier_safe("already_a_valid_IDENTIFIER_444")254 >>> print(make_identifier_safe("already_a_valid_IDENTIFIER_444"))
255 already_a_valid_IDENTIFIER_444255 already_a_valid_IDENTIFIER_444
256256
257 >>> print make_identifier_safe("!starts_with_punctuation")257 >>> print(make_identifier_safe("!starts_with_punctuation"))
258 _starts_with_punctuation258 _starts_with_punctuation
259259
260 >>> print make_identifier_safe("_!contains!pu-nc.tuation")260 >>> print(make_identifier_safe("_!contains!pu-nc.tuation"))
261 __contains_pu_nc_tuation261 __contains_pu_nc_tuation
262262
263 >>> print make_identifier_safe("contains\nnewline")263 >>> print(make_identifier_safe("contains\nnewline"))
264 contains_newline264 contains_newline
265265
266 >>> print make_identifier_safe("")266 >>> print(make_identifier_safe(""))
267 _267 _
268268
269 >>> print make_identifier_safe(None)269 >>> print(make_identifier_safe(None))
270 Traceback (most recent call last):270 Traceback (most recent call last):
271 ...271 ...
272 ValueError: Cannot make None value identifier-safe.272 ValueError: Cannot make None value identifier-safe.
@@ -354,9 +354,9 @@
354code.354code.
355355
356 >>> from lazr.restful.utils import safe_js_escape356 >>> from lazr.restful.utils import safe_js_escape
357 >>> print safe_js_escape('John "nasty" O\'Brien')357 >>> print(safe_js_escape('John "nasty" O\'Brien'))
358 "John &quot;nasty&quot; O'Brien"358 "John &quot;nasty&quot; O'Brien"
359 >>> print safe_js_escape("John O\'Brien")359 >>> print(safe_js_escape("John O\'Brien"))
360 "John O'Brien"360 "John O'Brien"
361 >>> print safe_js_escape("John <strong>O\'Brien</strong>")361 >>> print(safe_js_escape("John <strong>O\'Brien</strong>"))
362 "John &lt;strong&gt;O'Brien&lt;/strong&gt;"362 "John &lt;strong&gt;O'Brien&lt;/strong&gt;"
363363
=== modified file 'src/lazr/restful/docs/webservice-declarations.rst'
--- src/lazr/restful/docs/webservice-declarations.rst 2019-11-04 17:40:25 +0000
+++ src/lazr/restful/docs/webservice-declarations.rst 2020-02-04 11:56:15 +0000
@@ -63,11 +63,11 @@
63 ... return repr(value)63 ... return repr(value)
64 ... tag = element.queryTaggedValue('lazr.restful.exported')64 ... tag = element.queryTaggedValue('lazr.restful.exported')
65 ... if tag is None:65 ... if tag is None:
66 ... print "tag 'lazr.restful.exported' is not present"66 ... print("tag 'lazr.restful.exported' is not present")
67 ... else:67 ... else:
68 ... print "\n".join(68 ... print("\n".join(
69 ... "%s: %s" %(key, format_value(value))69 ... "%s: %s" %(key, format_value(value))
70 ... for key, value in sorted(tag.items()))70 ... for key, value in sorted(tag.items())))
71 >>> print_export_tag(IBook)71 >>> print_export_tag(IBook)
72 _as_of_was_used: False72 _as_of_was_used: False
73 contributes_to: None73 contributes_to: None
@@ -465,7 +465,7 @@
465465
466 >>> for name, param in sorted(IBookSetOnSteroids['new'].getTaggedValue(466 >>> for name, param in sorted(IBookSetOnSteroids['new'].getTaggedValue(
467 ... 'lazr.restful.exported')['params'].items()):467 ... 'lazr.restful.exported')['params'].items()):
468 ... print "%s: %s" % (name, param.__name__)468 ... print("%s: %s" % (name, param.__name__))
469 author: author469 author: author
470 base_price: price470 base_price: price
471 title: title471 title: title
@@ -791,7 +791,7 @@
791 ... """Sends a message to another named object."""791 ... """Sends a message to another named object."""
792792
793 >>> for name in sorted(IUser.names(True)):793 >>> for name in sorted(IUser.names(True)):
794 ... print '== %s ==' % name794 ... print('== %s ==' % name)
795 ... print_export_tag(IUser[name])795 ... print_export_tag(IUser[name])
796 == name ==796 == name ==
797 as: 'name'797 as: 'name'
@@ -937,7 +937,7 @@
937 >>> def dump_entry_interface(entry_interface):937 >>> def dump_entry_interface(entry_interface):
938 ... for name, field in sorted(938 ... for name, field in sorted(
939 ... entry_interface.namesAndDescriptions()):939 ... entry_interface.namesAndDescriptions()):
940 ... print "%s: %s" % (name, field.__class__.__name__)940 ... print("%s: %s" % (name, field.__class__.__name__))
941 >>> dump_entry_interface(entry_interface)941 >>> dump_entry_interface(entry_interface)
942 author: TextLine942 author: TextLine
943 price: Float943 price: Float
@@ -945,7 +945,7 @@
945945
946The field __name__ attribute contains the exported name:946The field __name__ attribute contains the exported name:
947947
948 >>> print entry_interface['price'].__name__948 >>> print(entry_interface['price'].__name__)
949 price949 price
950950
951Associated with the interface through tags are automatically-generated951Associated with the interface through tags are automatically-generated
@@ -953,9 +953,9 @@
953953
954 >>> from lazr.restful.interfaces import LAZR_WEBSERVICE_NAME954 >>> from lazr.restful.interfaces import LAZR_WEBSERVICE_NAME
955 >>> tags = entry_interface.queryTaggedValue(LAZR_WEBSERVICE_NAME)955 >>> tags = entry_interface.queryTaggedValue(LAZR_WEBSERVICE_NAME)
956 >>> print tags['singular']956 >>> print(tags['singular'])
957 book957 book
958 >>> print tags['plural']958 >>> print(tags['plural'])
959 books959 books
960960
961It's an error to use generate_entry_interfaces() on an interface that961It's an error to use generate_entry_interfaces() on an interface that
@@ -989,7 +989,7 @@
989adapter.989adapter.
990990
991 >>> [factory] = entry_adapter_factories991 >>> [factory] = entry_adapter_factories
992 >>> print factory.version992 >>> print(factory.version)
993 beta993 beta
994 >>> entry_adapter_factory = factory.object994 >>> entry_adapter_factory = factory.object
995995
@@ -1000,7 +1000,7 @@
10001000
1001The resulting class is named based on the interface:1001The resulting class is named based on the interface:
10021002
1003 >>> print entry_adapter_factory.__name__1003 >>> print(entry_adapter_factory.__name__)
1004 BookEntry_betaAdapter1004 BookEntry_betaAdapter
10051005
1006Its docstring is also copied over from the original interface:1006Its docstring is also copied over from the original interface:
@@ -1109,8 +1109,8 @@
1109 ... """Simple ICheckedOutBookSet implementation."""1109 ... """Simple ICheckedOutBookSet implementation."""
1110 ...1110 ...
1111 ... def getByTitle(self, title, user):1111 ... def getByTitle(self, title, user):
1112 ... print '%s searched for checked out book matching "%s".' % (1112 ... print('%s searched for checked out book matching "%s".' % (
1113 ... user, title)1113 ... user, title))
11141114
1115 >>> checked_out_adapter = generate_collection_adapter(1115 >>> checked_out_adapter = generate_collection_adapter(
1116 ... ICheckedOutBookSet)(CheckedOutBookSet(), request)1116 ... ICheckedOutBookSet)(CheckedOutBookSet(), request)
@@ -1173,7 +1173,7 @@
1173 >>> def print_params(params):1173 >>> def print_params(params):
1174 ... """Print the name and type of the defined parameters."""1174 ... """Print the name and type of the defined parameters."""
1175 ... for param in sorted(params, key=attrgetter('__name__')):1175 ... for param in sorted(params, key=attrgetter('__name__')):
1176 ... print "%s: %s" % (param.__name__, param.__class__.__name__)1176 ... print("%s: %s" % (param.__name__, param.__class__.__name__))
1177 >>> print_params(read_method_adapter_factory.params)1177 >>> print_params(read_method_adapter_factory.params)
1178 text: TextLine1178 text: TextLine
11791179
@@ -1208,7 +1208,7 @@
1208 >>> import simplejson1208 >>> import simplejson
1209 >>> for key, value in sorted(1209 >>> for key, value in sorted(
1210 ... simplejson.loads(read_method_adapter.call(text='')).items()):1210 ... simplejson.loads(read_method_adapter.call(text='')).items()):
1211 ... print '%s: %s' % (key, value)1211 ... print('%s: %s' % (key, value))
1212 entries: []1212 entries: []
1213 start: 01213 start: 0
1214 total_size: 01214 total_size: 0
@@ -1225,7 +1225,7 @@
12251225
1226The generated adapter class name is POST_<interface>_<operation>_beta.1226The generated adapter class name is POST_<interface>_<operation>_beta.
12271227
1228 >>> print write_method_adapter_factory.__name__1228 >>> print(write_method_adapter_factory.__name__)
1229 POST_IBookOnSteroids_checkout_beta1229 POST_IBookOnSteroids_checkout_beta
12301230
1231The adapter's params property also contains the available parameters1231The adapter's params property also contains the available parameters
@@ -1236,8 +1236,8 @@
1236 >>> @implementer(IBookOnSteroids)1236 >>> @implementer(IBookOnSteroids)
1237 ... class BookOnSteroids(Book):1237 ... class BookOnSteroids(Book):
1238 ... def checkout(self, who, kind):1238 ... def checkout(self, who, kind):
1239 ... print "%s did a %s check out of '%s'." % (1239 ... print("%s did a %s check out of '%s'." % (
1240 ... who, kind, self.title)1240 ... who, kind, self.title))
12411241
1242 >>> write_method_adapter = write_method_adapter_factory(1242 >>> write_method_adapter = write_method_adapter_factory(
1243 ... BookOnSteroids(1243 ... BookOnSteroids(
@@ -1275,7 +1275,7 @@
1275The generated adapter class name is also1275The generated adapter class name is also
1276POST_<interface>_<operation>_beta.1276POST_<interface>_<operation>_beta.
12771277
1278 >>> print write_method_adapter_factory.__name__1278 >>> print(write_method_adapter_factory.__name__)
1279 POST_IBookOnSteroids_checkout_beta1279 POST_IBookOnSteroids_checkout_beta
12801280
1281The adapter's params property also contains the available parameters.1281The adapter's params property also contains the available parameters.
@@ -1321,7 +1321,7 @@
1321 >>> response = factory_method_adapter.request.response1321 >>> response = factory_method_adapter.request.response
1322 >>> response.status1322 >>> response.status
1323 2011323 201
1324 >>> print response.headers['Location']1324 >>> print(response.headers['Location'])
1325 http://api.example.org/books/Eyeless%20in%20Gaza1325 http://api.example.org/books/Eyeless%20in%20Gaza
13261326
1327The generate_operation_adapter() function can only be called on an1327The generate_operation_adapter() function can only be called on an
@@ -1350,7 +1350,7 @@
1350The generated adapter class name is1350The generated adapter class name is
1351DELETE_<interface>_<operation>_beta.1351DELETE_<interface>_<operation>_beta.
13521352
1353 >>> print destructor_method_adapter_factory.__name__1353 >>> print(destructor_method_adapter_factory.__name__)
1354 DELETE_IBookOnSteroids_destroy_beta1354 DELETE_IBookOnSteroids_destroy_beta
13551355
1356Destructor1356Destructor
@@ -1451,7 +1451,7 @@
1451 ... IHasText, [], [hastext_entry_interface])1451 ... IHasText, [], [hastext_entry_interface])
14521452
1453 >>> obj = HasText()1453 >>> obj = HasText()
1454 >>> print hastext_entry_adapter_factory.version1454 >>> print(hastext_entry_adapter_factory.version)
1455 beta1455 beta
1456 >>> hastext_entry_adapter = hastext_entry_adapter_factory.object(1456 >>> hastext_entry_adapter = hastext_entry_adapter_factory.object(
1457 ... obj, request)1457 ... obj, request)
@@ -1625,10 +1625,10 @@
1625 ... ICachedBookSet['getAllBooks'])1625 ... ICachedBookSet['getAllBooks'])
1626 >>> read_method_adapter = read_method_adapter_factory(1626 >>> read_method_adapter = read_method_adapter_factory(
1627 ... CachedBookSet(['Cool book']), request)1627 ... CachedBookSet(['Cool book']), request)
1628 >>> print read_method_adapter.call()1628 >>> print(read_method_adapter.call())
1629 ['Cool book']1629 ['Cool book']
1630 >>> for name, value in sorted(request.response.headers.items()):1630 >>> for name, value in sorted(request.response.headers.items()):
1631 ... print '%s: %s' % (name, value)1631 ... print('%s: %s' % (name, value))
1632 Cache-control: max-age=601632 Cache-control: max-age=60
1633 Content-Type: application/json1633 Content-Type: application/json
16341634
@@ -1708,11 +1708,11 @@
1708 >>> interface = IMultiVersionCollection1708 >>> interface = IMultiVersionCollection
1709 >>> adapter_earliest_factory = generate_collection_adapter(1709 >>> adapter_earliest_factory = generate_collection_adapter(
1710 ... interface, None)1710 ... interface, None)
1711 >>> print adapter_earliest_factory.__name__1711 >>> print(adapter_earliest_factory.__name__)
1712 MultiVersionCollectionCollectionAdapter___Earliest1712 MultiVersionCollectionCollectionAdapter___Earliest
17131713
1714 >>> collection_earliest = adapter_earliest_factory(data_object, request)1714 >>> collection_earliest = adapter_earliest_factory(data_object, request)
1715 >>> print collection_earliest.find()1715 >>> print(collection_earliest.find())
1716 ['you', 'passed', 'in', 'pre-1.0 value']1716 ['you', 'passed', 'in', 'pre-1.0 value']
17171717
1718Passing in '1.0' gets us the collection as it appears in the 1.01718Passing in '1.0' gets us the collection as it appears in the 1.0
@@ -1721,22 +1721,22 @@
1721slightly different.1721slightly different.
17221722
1723 >>> adapter_10_factory = generate_collection_adapter(interface, '1.0')1723 >>> adapter_10_factory = generate_collection_adapter(interface, '1.0')
1724 >>> print adapter_10_factory.__name__1724 >>> print(adapter_10_factory.__name__)
1725 MultiVersionCollectionCollectionAdapter_1_01725 MultiVersionCollectionCollectionAdapter_1_0
17261726
1727 >>> collection_10 = adapter_10_factory(data_object, request)1727 >>> collection_10 = adapter_10_factory(data_object, request)
1728 >>> print collection_10.find()1728 >>> print(collection_10.find())
1729 ['you', 'passed', 'in', '1.0 value']1729 ['you', 'passed', 'in', '1.0 value']
17301730
1731Passing in '2.0' gets us a collection with totally different contents,1731Passing in '2.0' gets us a collection with totally different contents,
1732because a totally different method is being called.1732because a totally different method is being called.
17331733
1734 >>> adapter_20_factory = generate_collection_adapter(interface, '2.0')1734 >>> adapter_20_factory = generate_collection_adapter(interface, '2.0')
1735 >>> print adapter_20_factory.__name__1735 >>> print(adapter_20_factory.__name__)
1736 MultiVersionCollectionCollectionAdapter_2_01736 MultiVersionCollectionCollectionAdapter_2_0
17371737
1738 >>> collection_20 = adapter_20_factory(data_object, request)1738 >>> collection_20 = adapter_20_factory(data_object, request)
1739 >>> print collection_20.find()1739 >>> print(collection_20.find())
1740 ['contents', 'for', 'version', '2.0']1740 ['contents', 'for', 'version', '2.0']
17411741
1742An error occurs when we try to generate an adapter for a version1742An error occurs when we try to generate an adapter for a version
@@ -1792,7 +1792,7 @@
1792 ... IMultiVersionEntry, [], *versions)1792 ... IMultiVersionEntry, [], *versions)
17931793
1794 >>> for version, interface in versions_and_interfaces:1794 >>> for version, interface in versions_and_interfaces:
1795 ... print version1795 ... print(version)
1796 beta1796 beta
1797 1.01797 1.0
1798 2.01798 2.0
@@ -1850,7 +1850,7 @@
1850 ... IMultiVersionEntry, [], versions_and_interfaces)1850 ... IMultiVersionEntry, [], versions_and_interfaces)
18511851
1852 >>> for version, adapter in entry_adapters:1852 >>> for version, adapter in entry_adapters:
1853 ... print version1853 ... print(version)
1854 beta1854 beta
1855 1.01855 1.0
1856 2.01856 2.0
@@ -1862,22 +1862,22 @@
1862Here's the 'beta' version of the object:1862Here's the 'beta' version of the object:
18631863
1864 >>> object_beta = adapter_beta(data_object, request)1864 >>> object_beta = adapter_beta(data_object, request)
1865 >>> print object_beta.field1865 >>> print(object_beta.field)
1866 field value1866 field value
1867 >>> print object_beta.field31867 >>> print(object_beta.field3)
1868 field 3 value1868 field 3 value
1869 >>> print object_beta.unchanging_name1869 >>> print(object_beta.unchanging_name)
1870 unchanging value1870 unchanging value
18711871
1872The 'field4' field is not available in the 'beta' version under any name.1872The 'field4' field is not available in the 'beta' version under any name.
18731873
1874 >>> print object_beta.field41874 >>> print(object_beta.field4)
1875 Traceback (most recent call last):1875 Traceback (most recent call last):
1876 ...1876 ...
1877 AttributeError: 'MultiVersionEntryEntry_betaAdapter' object has no1877 AttributeError: 'MultiVersionEntryEntry_betaAdapter' object has no
1878 attribute 'field4'1878 attribute 'field4'
18791879
1880 >>> print object_beta.new_in_101880 >>> print(object_beta.new_in_10)
1881 Traceback (most recent call last):1881 Traceback (most recent call last):
1882 ...1882 ...
1883 AttributeError: 'MultiVersionEntryEntry_betaAdapter' object has no1883 AttributeError: 'MultiVersionEntryEntry_betaAdapter' object has no
@@ -1887,11 +1887,11 @@
1887now available as 'new_in_10'.1887now available as 'new_in_10'.
18881888
1889 >>> object_10 = adapter_10(data_object, request)1889 >>> object_10 = adapter_10(data_object, request)
1890 >>> print object_10.field1890 >>> print(object_10.field)
1891 field value1891 field value
1892 >>> print object_10.unchanging_name1892 >>> print(object_10.unchanging_name)
1893 unchanging value1893 unchanging value
1894 >>> print object_10.new_in_101894 >>> print(object_10.new_in_10)
1895 1.01895 1.0
18961896
1897 >>> object_10.field31897 >>> object_10.field3
@@ -1903,26 +1903,26 @@
1903Here's the '2.0' version. 'field3' is back, but now it's called '20_name'.1903Here's the '2.0' version. 'field3' is back, but now it's called '20_name'.
19041904
1905 >>> object_20 = adapter_20(data_object, request)1905 >>> object_20 = adapter_20(data_object, request)
1906 >>> print object_20.field1906 >>> print(object_20.field)
1907 field value1907 field value
1908 >>> print object_20.unchanging_name1908 >>> print(object_20.unchanging_name)
1909 unchanging value1909 unchanging value
1910 >>> print getattr(object_20, '20_name')1910 >>> print(getattr(object_20, '20_name'))
1911 field 3 value1911 field 3 value
1912 >>> print object_20.new_in_101912 >>> print(object_20.new_in_10)
1913 1.01913 1.0
19141914
1915Here's the '3.0' version. 'field3' has been renamed to '30_name' and1915Here's the '3.0' version. 'field3' has been renamed to '30_name' and
1916'field4' has been renamed to 'renamed_in_30'1916'field4' has been renamed to 'renamed_in_30'
19171917
1918 >>> object_30 = adapter_30(data_object, request)1918 >>> object_30 = adapter_30(data_object, request)
1919 >>> print object_30.field1919 >>> print(object_30.field)
1920 field value1920 field value
1921 >>> print object_30.unchanging_name1921 >>> print(object_30.unchanging_name)
1922 unchanging value1922 unchanging value
1923 >>> print getattr(object_30, '30_name')1923 >>> print(getattr(object_30, '30_name'))
1924 field 3 value1924 field 3 value
1925 >>> print object_30.renamed_in_301925 >>> print(object_30.renamed_in_30)
1926 1.01926 1.0
19271927
1928 >>> getattr(object_30, '20_name')1928 >>> getattr(object_30, '20_name')
@@ -1958,13 +1958,13 @@
1958 >>> foo, bar = generate_entry_interfaces(1958 >>> foo, bar = generate_entry_interfaces(
1959 ... IAmbiguousMultiVersion, [], 'foo', 'bar')1959 ... IAmbiguousMultiVersion, [], 'foo', 'bar')
19601960
1961 >>> print foo.version1961 >>> print(foo.version)
1962 foo1962 foo
1963 >>> dump_entry_interface(foo.object)1963 >>> dump_entry_interface(foo.object)
1964 field2: TextLine1964 field2: TextLine
1965 foo_name: TextLine1965 foo_name: TextLine
19661966
1967 >>> print bar.version1967 >>> print(bar.version)
1968 bar1968 bar
1969 >>> dump_entry_interface(bar.object)1969 >>> dump_entry_interface(bar.object)
1970 bar_name: TextLine1970 bar_name: TextLine
@@ -1986,13 +1986,13 @@
1986 >>> bar, foo = generate_entry_interfaces(1986 >>> bar, foo = generate_entry_interfaces(
1987 ... IAmbiguousMultiVersion, [], 'bar', 'foo')1987 ... IAmbiguousMultiVersion, [], 'bar', 'foo')
19881988
1989 >>> print bar.version1989 >>> print(bar.version)
1990 bar1990 bar
1991 >>> dump_entry_interface(bar.object)1991 >>> dump_entry_interface(bar.object)
1992 bar_name: TextLine1992 bar_name: TextLine
1993 field1: TextLine1993 field1: TextLine
19941994
1995 >>> print foo.version1995 >>> print(foo.version)
1996 foo1996 foo
1997 >>> dump_entry_interface(foo.object)1997 >>> dump_entry_interface(foo.object)
1998 bar_name: TextLine1998 bar_name: TextLine
@@ -2167,11 +2167,11 @@
21672167
2168 >>> method = IMultiVersionMethod['a_method']2168 >>> method = IMultiVersionMethod['a_method']
2169 >>> adapter_earliest_factory = generate_operation_adapter(method, None)2169 >>> adapter_earliest_factory = generate_operation_adapter(method, None)
2170 >>> print adapter_earliest_factory.__name__2170 >>> print(adapter_earliest_factory.__name__)
2171 GET_IMultiVersionMethod_a_method_beta2171 GET_IMultiVersionMethod_a_method_beta
21722172
2173 >>> method_earliest = adapter_earliest_factory(data_object, request)2173 >>> method_earliest = adapter_earliest_factory(data_object, request)
2174 >>> print method_earliest.call(required="foo")2174 >>> print(method_earliest.call(required="foo"))
2175 Required value: foo. Fixed value: pre-1.0 value. User: A user.2175 Required value: foo. Fixed value: pre-1.0 value. User: A user.
21762176
2177Passing in '1.0' or '2.0' gets us the method as it appears in the2177Passing in '1.0' or '2.0' gets us the method as it appears in the
@@ -2180,26 +2180,26 @@
21801.0 is 'new_name', not 'a_method'.21801.0 is 'new_name', not 'a_method'.
21812181
2182 >>> adapter_10_factory = generate_operation_adapter(method, '1.0')2182 >>> adapter_10_factory = generate_operation_adapter(method, '1.0')
2183 >>> print adapter_10_factory.__name__2183 >>> print(adapter_10_factory.__name__)
2184 GET_IMultiVersionMethod_new_name_1_02184 GET_IMultiVersionMethod_new_name_1_0
21852185
2186 >>> method_10 = adapter_10_factory(data_object, request)2186 >>> method_10 = adapter_10_factory(data_object, request)
2187 >>> print method_10.call(required="bar")2187 >>> print(method_10.call(required="bar"))
2188 Required value: bar. Fixed value: 1.0 value. User: A user.2188 Required value: bar. Fixed value: 1.0 value. User: A user.
21892189
2190 >>> adapter_20_factory = generate_operation_adapter(method, '2.0')2190 >>> adapter_20_factory = generate_operation_adapter(method, '2.0')
2191 >>> print adapter_20_factory.__name__2191 >>> print(adapter_20_factory.__name__)
2192 GET_IMultiVersionMethod_new_name_2_02192 GET_IMultiVersionMethod_new_name_2_0
21932193
2194 >>> method_20 = adapter_20_factory(data_object, request)2194 >>> method_20 = adapter_20_factory(data_object, request)
2195 >>> print method_20.call(required="baz")2195 >>> print(method_20.call(required="baz"))
2196 Required value: baz. Fixed value: 2.0 value. User: A user.2196 Required value: baz. Fixed value: 2.0 value. User: A user.
21972197
2198 >>> adapter_30_factory = generate_operation_adapter(method, '3.0')2198 >>> adapter_30_factory = generate_operation_adapter(method, '3.0')
2199 >>> print adapter_30_factory.__name__2199 >>> print(adapter_30_factory.__name__)
2200 GET_IMultiVersionMethod_new_name_3_02200 GET_IMultiVersionMethod_new_name_3_0
2201 >>> method_30 = adapter_30_factory(data_object, request)2201 >>> method_30 = adapter_30_factory(data_object, request)
2202 >>> print method_30.call(required="baz")2202 >>> print(method_30.call(required="baz"))
2203 Required value: baz. Fixed value: 2.0 value. User: A user.2203 Required value: baz. Fixed value: 2.0 value. User: A user.
22042204
2205An error occurs when we try to generate an adapter for a version2205An error occurs when we try to generate an adapter for a version
@@ -2228,9 +2228,9 @@
2228arguments ('2.0 value' and REQUEST_USER) from the 2.0 version, but it2228arguments ('2.0 value' and REQUEST_USER) from the 2.0 version, but it
2229also sets a new value for 'cache_for'.2229also sets a new value for 'cache_for'.
22302230
2231 >>> print dictionary['as']2231 >>> print(dictionary['as'])
2232 new_name2232 new_name
2233 >>> print pformat(dictionary['call_with'])2233 >>> print(pformat(dictionary['call_with']))
2234 {'fixed': '2.0 value',2234 {'fixed': '2.0 value',
2235 'user': <class '...REQUEST_USER'>}2235 'user': <class '...REQUEST_USER'>}
2236 >>> dictionary['cache_for']2236 >>> dictionary['cache_for']
@@ -2242,9 +2242,9 @@
2242value for 'cache_for' from version 1.0.2242value for 'cache_for' from version 1.0.
22432243
2244 >>> ignored = dictionary.pop()2244 >>> ignored = dictionary.pop()
2245 >>> print dictionary['as']2245 >>> print(dictionary['as'])
2246 new_name2246 new_name
2247 >>> print pformat(dictionary['call_with'])2247 >>> print(pformat(dictionary['call_with']))
2248 {'fixed': '2.0 value',2248 {'fixed': '2.0 value',
2249 'user': <class '...REQUEST_USER'>}2249 'user': <class '...REQUEST_USER'>}
2250 >>> dictionary['cache_for']2250 >>> dictionary['cache_for']
@@ -2253,7 +2253,7 @@
2253The published name of the 'required' argument is 'required_argument',2253The published name of the 'required' argument is 'required_argument',
2254not 'required'.2254not 'required'.
22552255
2256 >>> print dictionary['params']['required'].__name__2256 >>> print(dictionary['params']['required'].__name__)
2257 required_argument2257 required_argument
22582258
2259Let's pop the 2.0 version off the stack. Now we can see how the method2259Let's pop the 2.0 version off the stack. Now we can see how the method
@@ -2262,12 +2262,12 @@
2262is fixed to the string '1.0 value'.2262is fixed to the string '1.0 value'.
22632263
2264 >>> ignored = dictionary.pop()2264 >>> ignored = dictionary.pop()
2265 >>> print dictionary['as']2265 >>> print(dictionary['as'])
2266 new_name2266 new_name
2267 >>> print pformat(dictionary['call_with'])2267 >>> print(pformat(dictionary['call_with']))
2268 {'fixed': '1.0 value',2268 {'fixed': '1.0 value',
2269 'user': <class '...REQUEST_USER'>}2269 'user': <class '...REQUEST_USER'>}
2270 >>> print dictionary['params']['required'].__name__2270 >>> print(dictionary['params']['required'].__name__)
2271 required_argument2271 required_argument
2272 >>> dictionary['cache_for']2272 >>> dictionary['cache_for']
2273 1002273 100
@@ -2278,11 +2278,11 @@
2278'fixed' argument is fixed to the string 'pre-1.0 value'.2278'fixed' argument is fixed to the string 'pre-1.0 value'.
22792279
2280 >>> ignored = dictionary.pop()2280 >>> ignored = dictionary.pop()
2281 >>> print dictionary['as']2281 >>> print(dictionary['as'])
2282 a_method2282 a_method
2283 >>> print dictionary['params']['required'].__name__2283 >>> print(dictionary['params']['required'].__name__)
2284 required2284 required
2285 >>> print pformat(dictionary['call_with'])2285 >>> print(pformat(dictionary['call_with']))
2286 {'fixed': 'pre-1.0 value',2286 {'fixed': 'pre-1.0 value',
2287 'user': <class '...REQUEST_USER'>}2287 'user': <class '...REQUEST_USER'>}
2288 >>> dictionary['cache_for']2288 >>> dictionary['cache_for']
@@ -2313,7 +2313,7 @@
2313The method is not present in 2.0:2313The method is not present in 2.0:
23142314
2315 >>> version, attrs = dictionary.pop()2315 >>> version, attrs = dictionary.pop()
2316 >>> print version2316 >>> print(version)
2317 2.02317 2.0
2318 >>> sorted(attrs.items())2318 >>> sorted(attrs.items())
2319 [('type', 'removed_operation')]2319 [('type', 'removed_operation')]
@@ -2321,20 +2321,20 @@
2321It is present in 1.0:2321It is present in 1.0:
23222322
2323 >>> version, attrs = dictionary.pop()2323 >>> version, attrs = dictionary.pop()
2324 >>> print version2324 >>> print(version)
2325 1.02325 1.0
2326 >>> print attrs['type']2326 >>> print(attrs['type'])
2327 read_operation2327 read_operation
2328 >>> print attrs['params']['arg']2328 >>> print(attrs['params']['arg'])
2329 <zope.schema._field.Float object...>2329 <zope.schema._field.Float object...>
23302330
2331But it's not present in the unnamed pre-1.0 version, since it hadn't2331But it's not present in the unnamed pre-1.0 version, since it hadn't
2332been defined yet:2332been defined yet:
23332333
2334 >>> pre_10 = dictionary.pop()2334 >>> pre_10 = dictionary.pop()
2335 >>> print pre_10.version2335 >>> print(pre_10.version)
2336 None2336 None
2337 >>> print pre_10.object2337 >>> print(pre_10.object)
2338 {'type': 'removed_operation'}2338 {'type': 'removed_operation'}
23392339
2340The @operation_removed_in_version declaration can also be used to2340The @operation_removed_in_version declaration can also be used to
@@ -2372,22 +2372,22 @@
2372takes two TextLine arguments and has no special return value.2372takes two TextLine arguments and has no special return value.
23732373
2374 >>> version, attrs = dictionary.pop()2374 >>> version, attrs = dictionary.pop()
2375 >>> print version2375 >>> print(version)
2376 1.02376 1.0
2377 >>> print attrs['type']2377 >>> print(attrs['type'])
2378 write_operation2378 write_operation
2379 >>> attrs['params']['arg']2379 >>> attrs['params']['arg']
2380 <zope.schema._bootstrapfields.TextLine object...>2380 <zope.schema._bootstrapfields.TextLine object...>
2381 >>> attrs['params']['arg2']2381 >>> attrs['params']['arg2']
2382 <zope.schema._bootstrapfields.TextLine object...>2382 <zope.schema._bootstrapfields.TextLine object...>
2383 >>> print attrs.get('return_type')2383 >>> print(attrs.get('return_type'))
2384 None2384 None
23852385
2386In the unnamed pre-1.0 version, the 'method' operation is a read2386In the unnamed pre-1.0 version, the 'method' operation is a read
2387operation that takes a single Float argument and returns a collection.2387operation that takes a single Float argument and returns a collection.
23882388
2389 >>> version, attrs = dictionary.pop()2389 >>> version, attrs = dictionary.pop()
2390 >>> print attrs['type']2390 >>> print(attrs['type'])
2391 read_operation2391 read_operation
23922392
2393 >>> attrs['params']['arg']2393 >>> attrs['params']['arg']
@@ -2577,24 +2577,24 @@
2577 >>> from zope.security.checker import ProxyFactory2577 >>> from zope.security.checker import ProxyFactory
25782578
2579 # ProxyFactory wraps the content using the defined checker.2579 # ProxyFactory wraps the content using the defined checker.
2580 >>> print debug_proxy(ProxyFactory(entry_adapter))2580 >>> print(debug_proxy(ProxyFactory(entry_adapter)))
2581 zope.security._proxy._Proxy (using zope.security.checker.Checker)2581 zope.security._proxy._Proxy (using zope.security.checker.Checker)
2582 public: author, price, schema, title2582 public: author, price, schema, title
2583 public (set): author, price, schema, title2583 public (set): author, price, schema, title
25842584
2585 >>> print debug_proxy(ProxyFactory(collection_adapter))2585 >>> print(debug_proxy(ProxyFactory(collection_adapter)))
2586 zope.security._proxy._Proxy (using zope.security.checker.Checker)2586 zope.security._proxy._Proxy (using zope.security.checker.Checker)
2587 public: entry_schema, find2587 public: entry_schema, find
25882588
2589 >>> print debug_proxy(ProxyFactory(read_method_adapter))2589 >>> print(debug_proxy(ProxyFactory(read_method_adapter)))
2590 zope.security._proxy._Proxy (using zope.security.checker.Checker)2590 zope.security._proxy._Proxy (using zope.security.checker.Checker)
2591 public: __call__, return_type, send_modification_event2591 public: __call__, return_type, send_modification_event
25922592
2593 >>> print debug_proxy(ProxyFactory(write_method_adapter))2593 >>> print(debug_proxy(ProxyFactory(write_method_adapter)))
2594 zope.security._proxy._Proxy (using zope.security.checker.Checker)2594 zope.security._proxy._Proxy (using zope.security.checker.Checker)
2595 public: __call__, send_modification_event2595 public: __call__, send_modification_event
25962596
2597 >>> print debug_proxy(ProxyFactory(factory_method_adapter))2597 >>> print(debug_proxy(ProxyFactory(factory_method_adapter)))
2598 zope.security._proxy._Proxy (using zope.security.checker.Checker)2598 zope.security._proxy._Proxy (using zope.security.checker.Checker)
2599 public: __call__, send_modification_event2599 public: __call__, send_modification_event
26002600
@@ -2624,7 +2624,7 @@
2624 >>> verifyObject(IEntry, entry_adapter)2624 >>> verifyObject(IEntry, entry_adapter)
2625 True2625 True
26262626
2627 >>> print entry_adapter.schema.__name__2627 >>> print(entry_adapter.schema.__name__)
2628 IBookEntry_beta2628 IBookEntry_beta
2629 >>> verifyObject(entry_adapter.schema, entry_adapter)2629 >>> verifyObject(entry_adapter.schema, entry_adapter)
2630 True2630 True
@@ -2874,7 +2874,7 @@
2874The operation is not available in 'beta', because it hasn't been2874The operation is not available in 'beta', because it hasn't been
2875defined yet.2875defined yet.
28762876
2877 >>> print operation_for(context, 'beta', 'set_value').__class__.__name__2877 >>> print(operation_for(context, 'beta', 'set_value').__class__.__name__)
2878 Traceback (most recent call last):2878 Traceback (most recent call last):
2879 ...2879 ...
2880 ComponentLookupError: ...2880 ComponentLookupError: ...
@@ -2884,10 +2884,10 @@
28841.0. This is because the operation doesn't become a mutator operation28841.0. This is because the operation doesn't become a mutator operation
2885until 3.0.2885until 3.0.
28862886
2887 >>> print operation_for(context, '1.0', 'set_value').__class__.__name__2887 >>> print(operation_for(context, '1.0', 'set_value').__class__.__name__)
2888 POST_IOperationPromotedToMutator_set_value_1_02888 POST_IOperationPromotedToMutator_set_value_1_0
28892889
2890 >>> print operation_for(context, '2.0', 'set_value').__class__.__name__2890 >>> print(operation_for(context, '2.0', 'set_value').__class__.__name__)
2891 POST_IOperationPromotedToMutator_set_value_1_02891 POST_IOperationPromotedToMutator_set_value_1_0
28922892
2893The operation is not available in 3.0, the version in which it becomes2893The operation is not available in 3.0, the version in which it becomes
@@ -2904,7 +2904,7 @@
2904 >>> request_30 = request_for('3.0')2904 >>> request_30 = request_for('3.0')
2905 >>> entry = getMultiAdapter((context, request_30), IEntry)2905 >>> entry = getMultiAdapter((context, request_30), IEntry)
2906 >>> entry.field = 'foo'2906 >>> entry.field = 'foo'
2907 >>> print entry.field2907 >>> print(entry.field)
2908 !foo!2908 !foo!
29092909
2910You can immediately reinstate a mutator operation as a named operation2910You can immediately reinstate a mutator operation as a named operation
@@ -2952,15 +2952,15 @@
2952The mutator is accessible for version 1.0, as you'd expect.2952The mutator is accessible for version 1.0, as you'd expect.
29532953
2954 >>> context = MutatorPlusNamedOperation()2954 >>> context = MutatorPlusNamedOperation()
2955 >>> print operation_for(context, '1.0', 'set_value').__class__.__name__2955 >>> print(operation_for(context, '1.0', 'set_value').__class__.__name__)
2956 POST_IMutatorPlusNamedOperationEntry_set_value_1_02956 POST_IMutatorPlusNamedOperationEntry_set_value_1_0
29572957
2958The named operations that replace the mutator in versions 2.0 and 3.0 are2958The named operations that replace the mutator in versions 2.0 and 3.0 are
2959also accessible.2959also accessible.
29602960
2961 >>> print operation_for(context, '2.0', 'set_value').__class__.__name__2961 >>> print(operation_for(context, '2.0', 'set_value').__class__.__name__)
2962 POST_IMutatorPlusNamedOperationEntry_set_value_2_02962 POST_IMutatorPlusNamedOperationEntry_set_value_2_0
2963 >>> print operation_for(context, '3.0', 'set_value').__class__.__name__2963 >>> print(operation_for(context, '3.0', 'set_value').__class__.__name__)
2964 POST_IMutatorPlusNamedOperationEntry_set_value_3_02964 POST_IMutatorPlusNamedOperationEntry_set_value_3_0
29652965
2966So, in the version that gets rid of named operations for mutator methods,2966So, in the version that gets rid of named operations for mutator methods,
29672967
=== modified file 'src/lazr/restful/docs/webservice-error.rst'
--- src/lazr/restful/docs/webservice-error.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/webservice-error.rst 2020-02-04 11:56:15 +0000
@@ -81,19 +81,19 @@
81When the request contains an OOPSID, it will be set in the X-Lazr-OopsId81When the request contains an OOPSID, it will be set in the X-Lazr-OopsId
82header:82header:
8383
84 >>> print request.response.headers.get('X-Lazr-OopsId')84 >>> print(request.response.headers.get('X-Lazr-OopsId'))
85 None85 None
86 >>> request = FakeRequest()86 >>> request = FakeRequest()
87 >>> request.oopsid = 'OOPS-001'87 >>> request.oopsid = 'OOPS-001'
88 >>> ignored = render_error_view(InvalidInput('bad email'), request)88 >>> ignored = render_error_view(InvalidInput('bad email'), request)
89 >>> print request.response.headers['X-Lazr-OopsId']89 >>> print(request.response.headers['X-Lazr-OopsId'])
90 OOPS-00190 OOPS-001
9191
92Even if show_tracebacks is set to true, non-5xx error codes will not92Even if show_tracebacks is set to true, non-5xx error codes will not
93produce a traceback.93produce a traceback.
9494
95 >>> webservice_configuration.show_tracebacks = True95 >>> webservice_configuration.show_tracebacks = True
96 >>> print render_error_view(InvalidInput('bad email'), request)96 >>> print(render_error_view(InvalidInput('bad email'), request))
97 bad email97 bad email
9898
9999
@@ -111,7 +111,7 @@
111so there's no point in hiding the exception message. When tracebacks are111so there's no point in hiding the exception message. When tracebacks are
112shown, the view puts a traceback dump in the response.112shown, the view puts a traceback dump in the response.
113113
114 >>> print render_error_view(ServerError('DB crash'), request)114 >>> print(render_error_view(ServerError('DB crash'), request))
115 DB crash115 DB crash
116 <BLANKLINE>116 <BLANKLINE>
117 Traceback (most recent call last):117 Traceback (most recent call last):
@@ -122,7 +122,7 @@
122will see the exception class name instead of a message.122will see the exception class name instead of a message.
123123
124 >>> webservice_configuration.show_tracebacks = False124 >>> webservice_configuration.show_tracebacks = False
125 >>> print render_error_view(ServerError('DB crash'), request)125 >>> print(render_error_view(ServerError('DB crash'), request))
126 ServerError126 ServerError
127127
128128
129129
=== modified file 'src/lazr/restful/docs/webservice-marshallers.rst'
--- src/lazr/restful/docs/webservice-marshallers.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/webservice-marshallers.rst 2020-02-04 11:56:15 +0000
@@ -60,7 +60,7 @@
60 u'unicode\u2122'60 u'unicode\u2122'
61 >>> marshaller.marshall_from_json_data("")61 >>> marshaller.marshall_from_json_data("")
62 ''62 ''
63 >>> print marshaller.marshall_from_json_data(None)63 >>> print(marshaller.marshall_from_json_data(None))
64 None64 None
6565
66marshall_from_request()66marshall_from_request()
@@ -73,7 +73,7 @@
73SimpleFieldMarshaller tries first to parse the value as a JSON-encoded73SimpleFieldMarshaller tries first to parse the value as a JSON-encoded
74string, the resulting value is passed on to marshall_from_json_data().74string, the resulting value is passed on to marshall_from_json_data().
7575
76 >>> print marshaller.marshall_from_request("null")76 >>> print(marshaller.marshall_from_request("null"))
77 None77 None
78 >>> marshaller.marshall_from_request("true")78 >>> marshaller.marshall_from_request("true")
79 True79 True
@@ -121,9 +121,9 @@
121the SimpleFieldMarshaller implementation, the value is returned121the SimpleFieldMarshaller implementation, the value is returned
122unchanged.122unchanged.
123123
124 >>> print marshaller.unmarshall(None, 'foo')124 >>> print(marshaller.unmarshall(None, 'foo'))
125 foo125 foo
126 >>> print marshaller.unmarshall(None, None)126 >>> print(marshaller.unmarshall(None, None))
127 None127 None
128128
129When a more detailed representation is needed, unmarshall_to_closeup()129When a more detailed representation is needed, unmarshall_to_closeup()
@@ -172,7 +172,7 @@
172172
173None is passed through though.173None is passed through though.
174174
175 >>> print marshaller.marshall_from_json_data(None)175 >>> print(marshaller.marshall_from_json_data(None))
176 None176 None
177177
178Booleans are encoded using the standard JSON representation of 'true' or178Booleans are encoded using the standard JSON representation of 'true' or
@@ -209,7 +209,7 @@
209209
210None is passed through though.210None is passed through though.
211211
212 >>> print marshaller.marshall_from_json_data(None)212 >>> print(marshaller.marshall_from_json_data(None))
213 None213 None
214214
215Integers are encoded using strings when in a request.215Integers are encoded using strings when in a request.
@@ -268,7 +268,7 @@
268268
269None is passed through though.269None is passed through though.
270270
271 >>> print marshaller.marshall_from_json_data(None)271 >>> print(marshaller.marshall_from_json_data(None))
272 None272 None
273273
274And integers are automatically converted to a float.274And integers are automatically converted to a float.
@@ -392,7 +392,7 @@
392392
393None is passed through though.393None is passed through though.
394394
395 >>> print marshaller.marshall_from_json_data(None)395 >>> print(marshaller.marshall_from_json_data(None))
396 None396 None
397397
398When coming from the request, everything is interpreted as a unicode398When coming from the request, everything is interpreted as a unicode
@@ -409,7 +409,7 @@
409409
410Except that 'null' still returns None.410Except that 'null' still returns None.
411411
412 >>> print marshaller.marshall_from_request('null')412 >>> print(marshaller.marshall_from_request('null'))
413 None413 None
414414
415Bytes415Bytes
@@ -436,7 +436,7 @@
436436
437Again, except for None which is passed through.437Again, except for None which is passed through.
438438
439 >>> print marshaller.marshall_from_json_data(None)439 >>> print(marshaller.marshall_from_json_data(None))
440 None440 None
441441
442When coming over the request, the value is also converted into a UTF-8442When coming over the request, the value is also converted into a UTF-8
@@ -451,7 +451,7 @@
451451
452But again, None is returned as is.452But again, None is returned as is.
453453
454 >>> print marshaller.marshall_from_request('null')454 >>> print(marshaller.marshall_from_request('null'))
455 None455 None
456456
457Since multipart/form-data can be used to upload data, file-like objects457Since multipart/form-data can be used to upload data, file-like objects
@@ -511,7 +511,7 @@
511511
512None is passed through.512None is passed through.
513513
514 >>> print marshaller.marshall_from_json_data(None)514 >>> print(marshaller.marshall_from_json_data(None))
515 None515 None
516516
517When coming from the request, everything is interpreted as a unicode517When coming from the request, everything is interpreted as a unicode
@@ -532,7 +532,7 @@
532532
533But again, 'null' is returned as None.533But again, 'null' is returned as None.
534534
535 >>> print marshaller.marshall_from_request('null')535 >>> print(marshaller.marshall_from_request('null'))
536 None536 None
537537
538Unlike a Bytes field, an ASCIILine field used in an entry is stored538Unlike a Bytes field, an ASCIILine field used in an entry is stored
@@ -581,7 +581,7 @@
581581
582None is always returned unchanged.582None is always returned unchanged.
583583
584 >>> print marshaller.marshall_from_json_data(None)584 >>> print(marshaller.marshall_from_json_data(None))
585 None585 None
586586
587Since this marshaller's Choice fields deal with small, fixed587Since this marshaller's Choice fields deal with small, fixed
@@ -589,7 +589,7 @@
589describe the vocabulary as a whole.589describe the vocabulary as a whole.
590590
591 >>> for token in marshaller.unmarshall_to_closeup(None, '10'):591 >>> for token in marshaller.unmarshall_to_closeup(None, '10'):
592 ... print sorted(token.items())592 ... print(sorted(token.items()))
593 [('title', None), ('token', '10')]593 [('title', None), ('token', '10')]
594 [('title', None), ('token', 'a value')]594 [('title', None), ('token', 'a value')]
595 [('title', None), ('token', 'True')]595 [('title', None), ('token', 'True')]
@@ -597,7 +597,7 @@
597And None is handled correctly.597And None is handled correctly.
598598
599 >>> for token in marshaller.unmarshall_to_closeup(None, None):599 >>> for token in marshaller.unmarshall_to_closeup(None, None):
600 ... print sorted(token.items())600 ... print(sorted(token.items()))
601 [('title', None), ('token', '10')]601 [('title', None), ('token', '10')]
602 [('title', None), ('token', 'a value')]602 [('title', None), ('token', 'a value')]
603 [('title', None), ('token', 'True')]603 [('title', None), ('token', 'True')]
@@ -613,7 +613,7 @@
613 ... try:613 ... try:
614 ... callable(*args)614 ... callable(*args)
615 ... except ValueError as e:615 ... except ValueError as e:
616 ... print 'ValueError:', unicode(e)616 ... print('ValueError:', unicode(e))
617617
618618
619Choice of EnumeratedTypes619Choice of EnumeratedTypes
@@ -641,7 +641,7 @@
641641
642None is returned unchanged:642None is returned unchanged:
643643
644 >>> print marshaller.marshall_from_json_data(None)644 >>> print(marshaller.marshall_from_json_data(None))
645 None645 None
646646
647This marshaller is for a Choice field describing a small, fixed647This marshaller is for a Choice field describing a small, fixed
@@ -651,7 +651,7 @@
651651
652 >>> for cuisine in sorted(652 >>> for cuisine in sorted(
653 ... marshaller.unmarshall_to_closeup(None, "Triaged")):653 ... marshaller.unmarshall_to_closeup(None, "Triaged")):
654 ... print sorted(cuisine.items())654 ... print(sorted(cuisine.items()))
655 [('title', 'American'), ('token', 'AMERICAN')]655 [('title', 'American'), ('token', 'AMERICAN')]
656 ...656 ...
657 [('title', 'Vegetarian'), ('token', 'VEGETARIAN')]657 [('title', 'Vegetarian'), ('token', 'VEGETARIAN')]
@@ -673,7 +673,7 @@
673 >>> from lazr.restful.example.base.root import COOKBOOKS673 >>> from lazr.restful.example.base.root import COOKBOOKS
674 >>> cookbook = COOKBOOKS[0]674 >>> cookbook = COOKBOOKS[0]
675 >>> cookbook_url = reference_marshaller.unmarshall(None, cookbook)675 >>> cookbook_url = reference_marshaller.unmarshall(None, cookbook)
676 >>> print cookbook_url676 >>> print(cookbook_url)
677 http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking677 http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking
678678
679A URL is unmarshalled to the underlying object.679A URL is unmarshalled to the underlying object.
@@ -692,7 +692,7 @@
692 ...692 ...
693 ValueError: got 'int', expected string: 4693 ValueError: got 'int', expected string: 4
694694
695 >>> print reference_marshaller.marshall_from_json_data(None)695 >>> print(reference_marshaller.marshall_from_json_data(None))
696 None696 None
697697
698Relative URLs698Relative URLs
@@ -702,7 +702,7 @@
702702
703 >>> cookbook = reference_marshaller.marshall_from_json_data(703 >>> cookbook = reference_marshaller.marshall_from_json_data(
704 ... '/cookbooks/Everyday%20Greens')704 ... '/cookbooks/Everyday%20Greens')
705 >>> print cookbook.name705 >>> print(cookbook.name)
706 Everyday Greens706 Everyday Greens
707707
708Collections708Collections
@@ -817,10 +817,10 @@
817817
818None is passed through though.818None is passed through though.
819819
820 >>> print list_marshaller.marshall_from_json_data(None)820 >>> print(list_marshaller.marshall_from_json_data(None))
821 None821 None
822822
823 >>> print dict_marshaller.marshall_from_json_data(None)823 >>> print(dict_marshaller.marshall_from_json_data(None))
824 None824 None
825825
826ValueError is also raised if one of the value in the list doesn't826ValueError is also raised if one of the value in the list doesn't
@@ -892,10 +892,10 @@
892892
893Except that 'null' still returns None.893Except that 'null' still returns None.
894894
895 >>> print list_marshaller.marshall_from_request('null')895 >>> print(list_marshaller.marshall_from_request('null'))
896 None896 None
897897
898 >>> print dict_marshaller.marshall_from_request('null')898 >>> print(dict_marshaller.marshall_from_request('null'))
899 None899 None
900900
901Also, as a convenience for web client, so that they don't have to JSON901Also, as a convenience for web client, so that they don't have to JSON
@@ -920,7 +920,7 @@
920920
921The unmarshall() method will return None when given None.921The unmarshall() method will return None when given None.
922922
923 >>> print dict_marshaller.unmarshall(None, None)923 >>> print(dict_marshaller.unmarshall(None, None))
924 None924 None
925925
926CollectionField926CollectionField
927927
=== modified file 'src/lazr/restful/docs/webservice-request.rst'
--- src/lazr/restful/docs/webservice-request.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/webservice-request.rst 2020-02-04 11:56:15 +0000
@@ -67,7 +67,7 @@
67 >>> cache.objects['object1'] = 'foo'67 >>> cache.objects['object1'] = 'foo'
68 >>> cache.objects['object2'] = 'bar'68 >>> cache.objects['object2'] = 'bar'
69 >>> for key in sorted(cache.objects):69 >>> for key in sorted(cache.objects):
70 ... print "%s: %s" % (key, cache.objects[key])70 ... print("%s: %s" % (key, cache.objects[key]))
71 object1: foo71 object1: foo
72 object2: bar72 object2: bar
7373
@@ -77,7 +77,7 @@
77 >>> cache.links['objectA'] = 'foo'77 >>> cache.links['objectA'] = 'foo'
78 >>> cache.links['objectB'] = 'bar'78 >>> cache.links['objectB'] = 'bar'
79 >>> for key in sorted(cache.links):79 >>> for key in sorted(cache.links):
80 ... print "%s: %s" % (key, cache.links[key])80 ... print("%s: %s" % (key, cache.links[key]))
81 objectA: foo81 objectA: foo
82 objectB: bar82 objectB: bar
8383
@@ -95,6 +95,6 @@
95 >>> cache = test_tales(95 >>> cache = test_tales(
96 ... "request/webservicerequest:cache", request=website_request)96 ... "request/webservicerequest:cache", request=website_request)
97 >>> for key in sorted(cache.links):97 >>> for key in sorted(cache.links):
98 ... print "%s: %s" % (key, cache.links[key])98 ... print("%s: %s" % (key, cache.links[key]))
99 objectA: foo99 objectA: foo
100 objectB: bar100 objectB: bar
101101
=== modified file 'src/lazr/restful/docs/webservice.rst'
--- src/lazr/restful/docs/webservice.rst 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/docs/webservice.rst 2020-02-04 11:56:15 +0000
@@ -862,7 +862,7 @@
862 >>> from lazr.restful._resource import get_entry_fields_in_write_order862 >>> from lazr.restful._resource import get_entry_fields_in_write_order
863 >>> def print_fields_in_write_order(entry):863 >>> def print_fields_in_write_order(entry):
864 ... for name, field in get_entry_fields_in_write_order(entry):864 ... for name, field in get_entry_fields_in_write_order(entry):
865 ... print name865 ... print(name)
866866
867 >>> print_fields_in_write_order(author_entry)867 >>> print_fields_in_write_order(author_entry)
868 name868 name
@@ -1123,7 +1123,7 @@
1123 >>> wadl_request = create_web_service_request(1123 >>> wadl_request = create_web_service_request(
1124 ... '/beta/', environ=wadl_headers)1124 ... '/beta/', environ=wadl_headers)
1125 >>> wadl_resource = wadl_request.traverse(app)1125 >>> wadl_resource = wadl_request.traverse(app)
1126 >>> print wadl_resource(wadl_request)1126 >>> print(wadl_resource(wadl_request))
1127 <?xml version="1.0"?>1127 <?xml version="1.0"?>
1128 <!DOCTYPE...1128 <!DOCTYPE...
1129 <wadl:application ...>1129 <wadl:application ...>
@@ -1139,7 +1139,7 @@
1139so there's an ``AssertionError``.1139so there's an ``AssertionError``.
11401140
1141 >>> sm.registerAdapter(DishCollection, [IAuthorSet], ICollection)1141 >>> sm.registerAdapter(DishCollection, [IAuthorSet], ICollection)
1142 >>> print wadl_resource(wadl_request)1142 >>> print(wadl_resource(wadl_request))
1143 Traceback (most recent call last):1143 Traceback (most recent call last):
1144 ...1144 ...
1145 AssertionError: There must be one (and only one) adapter1145 AssertionError: There must be one (and only one) adapter
@@ -1249,7 +1249,7 @@
1249 ... environ={'QUERY_STRING' :1249 ... environ={'QUERY_STRING' :
1250 ... 'ws.op=find_recipes&name=NoSuchRecipe'})1250 ... 'ws.op=find_recipes&name=NoSuchRecipe'})
1251 >>> operation_resource = request.traverse(app)1251 >>> operation_resource = request.traverse(app)
1252 >>> print operation_resource()1252 >>> print(operation_resource())
1253 No matches for NoSuchRecipe1253 No matches for NoSuchRecipe
12541254
1255Collections may also support named POST operations. These requests1255Collections may also support named POST operations. These requests
@@ -1260,7 +1260,7 @@
12601260
1261 >>> def modified_cookbook(object, event):1261 >>> def modified_cookbook(object, event):
1262 ... """Print a message when triggered."""1262 ... """Print a message when triggered."""
1263 ... print "You just modified a cookbook."1263 ... print("You just modified a cookbook.")
12641264
1265 >>> from lazr.lifecycle.interfaces import IObjectModifiedEvent1265 >>> from lazr.lifecycle.interfaces import IObjectModifiedEvent
1266 >>> from lazr.restful.testing.event import TestEventListener1266 >>> from lazr.restful.testing.event import TestEventListener
@@ -1269,7 +1269,7 @@
12691269
1270 >>> def modified_cookbook_set(object, event):1270 >>> def modified_cookbook_set(object, event):
1271 ... """Print a message when triggered."""1271 ... """Print a message when triggered."""
1272 ... print "You just modified the cookbook set."1272 ... print("You just modified the cookbook set.")
12731273
1274Here we create a new cookbook for an existing author. Because the1274Here we create a new cookbook for an existing author. Because the
1275operation's definition doesn't set send_modified_event to True, no1275operation's definition doesn't set send_modified_event to True, no
@@ -1506,11 +1506,11 @@
1506expect.1506expect.
15071507
1508 >>> request, operation = make_dummy_operation_request("A string.")1508 >>> request, operation = make_dummy_operation_request("A string.")
1509 >>> print operation()1509 >>> print(operation())
1510 "A string."1510 "A string."
1511 >>> request.response.getStatus()1511 >>> request.response.getStatus()
1512 2001512 200
1513 >>> print request.response.getHeader('Content-Type')1513 >>> print(request.response.getHeader('Content-Type'))
1514 application/json1514 application/json
15151515
1516 >>> request, operation = make_dummy_operation_request(True)1516 >>> request, operation = make_dummy_operation_request(True)
@@ -1579,7 +1579,7 @@
1579 >>> request, operation = make_dummy_operation_request(recipes)1579 >>> request, operation = make_dummy_operation_request(recipes)
1580 >>> response = operation()1580 >>> response = operation()
1581 >>> for key, value in sorted(simplejson.loads(response).items()):1581 >>> for key, value in sorted(simplejson.loads(response).items()):
1582 ... print '%s: %s' % (key, value)1582 ... print('%s: %s' % (key, value))
1583 entries: [{...}, {...}]1583 entries: [{...}, {...}]
1584 start: ...1584 start: ...
1585 total_size: 21585 total_size: 2
@@ -1591,7 +1591,7 @@
1591 >>> request, operation = make_dummy_operation_request(DishSet())1591 >>> request, operation = make_dummy_operation_request(DishSet())
1592 >>> response = operation()1592 >>> response = operation()
1593 >>> for key, value in sorted(simplejson.loads(response).items()):1593 >>> for key, value in sorted(simplejson.loads(response).items()):
1594 ... print '%s: %s' % (key, value)1594 ... print('%s: %s' % (key, value))
1595 entries: ...1595 entries: ...
1596 start: ...1596 start: ...
1597 total_size: ...1597 total_size: ...
@@ -1661,13 +1661,13 @@
1661 ... return dict(entity_body=entity_body,1661 ... return dict(entity_body=entity_body,
1662 ... response=get_request.response)1662 ... response=get_request.response)
16631663
1664 >>> print get_julia(etag_original)['response'].getStatus()1664 >>> print(get_julia(etag_original)['response'].getStatus())
1665 3041665 304
16661666
1667If the ETags don't match, the server assumes the client has an old1667If the ETags don't match, the server assumes the client has an old
1668representation, and sends the new representation.1668representation, and sends the new representation.
16691669
1670 >>> print get_julia('bad etag')['entity_body']1670 >>> print(get_julia('bad etag')['entity_body'])
1671 {...}1671 {...}
16721672
1673Change the state of the resource, and the ETag changes.1673Change the state of the resource, and the ETag changes.
@@ -1706,27 +1706,27 @@
1706Under normal circumstances, lazr.restful won't recognize an ETag1706Under normal circumstances, lazr.restful won't recognize an ETag
1707modified by mod_compress.1707modified by mod_compress.
17081708
1709 >>> print get_julia(modified_etag_1)['entity_body']1709 >>> print(get_julia(modified_etag_1)['entity_body'])
1710 {...}1710 {...}
17111711
1712When 'compensate_for_mod_compress_etag_modification' is set,1712When 'compensate_for_mod_compress_etag_modification' is set,
1713lazr.restful will recognize an ETag modified by mod_compress.1713lazr.restful will recognize an ETag modified by mod_compress.
17141714
1715 >>> c = webservice_configuration1715 >>> c = webservice_configuration
1716 >>> print c.compensate_for_mod_compress_etag_modification1716 >>> print(c.compensate_for_mod_compress_etag_modification)
1717 False1717 False
1718 >>> c.compensate_for_mod_compress_etag_modification = True1718 >>> c.compensate_for_mod_compress_etag_modification = True
17191719
1720 >>> print get_julia(modified_etag_1)['response'].getStatus()1720 >>> print(get_julia(modified_etag_1)['response'].getStatus())
1721 3041721 304
17221722
1723 >>> print get_julia(modified_etag_2)['response'].getStatus()1723 >>> print(get_julia(modified_etag_2)['response'].getStatus())
1724 3041724 304
17251725
1726Of course, that doesn't mean lazr.restful will recognize any random1726Of course, that doesn't mean lazr.restful will recognize any random
1727ETag.1727ETag.
17281728
1729 >>> print get_julia(etag + "-not-gzip")['entity_body']1729 >>> print(get_julia(etag + "-not-gzip")['entity_body'])
1730 {...}1730 {...}
17311731
1732Cleanup.1732Cleanup.
@@ -1831,10 +1831,10 @@
1831 >>> body = simplejson.dumps(author)1831 >>> body = simplejson.dumps(author)
1832 >>> put_request = create_web_service_request(1832 >>> put_request = create_web_service_request(
1833 ... beard_url, body=body, environ=headers, method='PUT')1833 ... beard_url, body=body, environ=headers, method='PUT')
1834 >>> print put_request.traverse(app)()1834 >>> print(put_request.traverse(app)())
1835 (<Recipe object...>, 'dish', ...)1835 (<Recipe object...>, 'dish', ...)
18361836
1837 >>> print put_request.response.getStatus()1837 >>> print(put_request.response.getStatus())
1838 4011838 401
18391839
1840Stored file resources1840Stored file resources
@@ -1863,7 +1863,7 @@
1863 ...1863 ...
1864 NotFound: ... name: 'cover'1864 NotFound: ... name: 'cover'
18651865
1866 >>> print C2.cover1866 >>> print(C2.cover)
1867 None1867 None
18681868
1869A cookbook can be given a cover with PUT.1869A cookbook can be given a cover with PUT.
@@ -1885,7 +1885,7 @@
1885 >>> file_resource()1885 >>> file_resource()
1886 >>> get_request.response.getStatus()1886 >>> get_request.response.getStatus()
1887 3031887 303
1888 >>> print get_request.response.getHeader('Location')1888 >>> print(get_request.response.getHeader('Location'))
1889 http://cookbooks.dev/.../filemanager/01889 http://cookbooks.dev/.../filemanager/0
18901890
1891The cover can be deleted with DELETE.1891The cover can be deleted with DELETE.
@@ -1902,7 +1902,7 @@
1902 ...1902 ...
1903 NotFound: ... name: 'cover'1903 NotFound: ... name: 'cover'
19041904
1905 >>> print C2.cover1905 >>> print(C2.cover)
1906 None1906 None
19071907
1908Field resources1908Field resources
@@ -1912,7 +1912,7 @@
19121912
1913 >>> field_resource = create_web_service_request(1913 >>> field_resource = create_web_service_request(
1914 ... '/beta/cookbooks/The%20Joy%20of%20Cooking/name').traverse(app)1914 ... '/beta/cookbooks/The%20Joy%20of%20Cooking/name').traverse(app)
1915 >>> print field_resource()1915 >>> print(field_resource())
1916 "The Joy of Cooking"1916 "The Joy of Cooking"
19171917
1918Requesting non available resources1918Requesting non available resources
@@ -2050,39 +2050,39 @@
2050matches the hostname you're requesting. If they don't match, your2050matches the hostname you're requesting. If they don't match, your
2051request will fail.2051request will fail.
20522052
2053 >>> print change_joy_author(u'http://not.the.same.host' + path)2053 >>> print(change_joy_author(u'http://not.the.same.host' + path))
2054 author_link: No such object...2054 author_link: No such object...
20552055
2056One possible source of hostname mismatches is the HTTP port. If the2056One possible source of hostname mismatches is the HTTP port. If the
2057web service is served from a strange port, you'll need to specify that2057web service is served from a strange port, you'll need to specify that
2058port in the URLs you send.2058port in the URLs you send.
20592059
2060 >>> print change_joy_author(u'http://api.cookbooks.dev' + path,2060 >>> print(change_joy_author(u'http://api.cookbooks.dev' + path,
2061 ... host='api.cookbooks.dev:9000')2061 ... host='api.cookbooks.dev:9000'))
2062 author_link: No such object...2062 author_link: No such object...
20632063
2064 >>> print change_joy_author(u'http://api.cookbooks.dev:9000' + path,2064 >>> print(change_joy_author(u'http://api.cookbooks.dev:9000' + path,
2065 ... host='api.cookbooks.dev:9000')2065 ... host='api.cookbooks.dev:9000'))
2066 {...}2066 {...}
20672067
2068You don't have to specify the default port in the URLs you send, even2068You don't have to specify the default port in the URLs you send, even
2069if you specified it when you made the request.2069if you specified it when you made the request.
20702070
2071 >>> print change_joy_author(u'http://api.cookbooks.dev' + path,2071 >>> print(change_joy_author(u'http://api.cookbooks.dev' + path,
2072 ... host='api.cookbooks.dev:80')2072 ... host='api.cookbooks.dev:80'))
2073 {...}2073 {...}
20742074
2075 >>> print change_joy_author(u'http://api.cookbooks.dev:80' + path,2075 >>> print(change_joy_author(u'http://api.cookbooks.dev:80' + path,
2076 ... host='api.cookbooks.dev')2076 ... host='api.cookbooks.dev'))
2077 {...}2077 {...}
20782078
2079 >>> print change_joy_author(u'https://api.cookbooks.dev' + path,2079 >>> print(change_joy_author(u'https://api.cookbooks.dev' + path,
2080 ... host='api.cookbooks.dev:443')2080 ... host='api.cookbooks.dev:443'))
2081 author_link: No such object...2081 author_link: No such object...
20822082
2083 >>> webservice_configuration.use_https = True2083 >>> webservice_configuration.use_https = True
2084 >>> print change_joy_author(u'https://api.cookbooks.dev' + path,2084 >>> print(change_joy_author(u'https://api.cookbooks.dev' + path,
2085 ... host='api.cookbooks.dev:443')2085 ... host='api.cookbooks.dev:443'))
2086 {...}2086 {...}
2087 >>> webservice_configuration.use_https = False2087 >>> webservice_configuration.use_https = False
20882088
@@ -2098,7 +2098,7 @@
20982098
2099 >>> resource = create_web_service_request(2099 >>> resource = create_web_service_request(
2100 ... recipe_url, method='GET').traverse(app)2100 ... recipe_url, method='GET').traverse(app)
2101 >>> print resource()2101 >>> print(resource())
2102 {...}2102 {...}
21032103
2104 >>> resource = create_web_service_request(2104 >>> resource = create_web_service_request(
21052105
=== modified file 'src/lazr/restful/error.py'
--- src/lazr/restful/error.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/error.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Error handling on the webservice."""3"""Error handling on the webservice."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'ClientErrorView',9 'ClientErrorView',
810
=== modified file 'src/lazr/restful/example/base/filemanager.py'
--- src/lazr/restful/example/base/filemanager.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/example/base/filemanager.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""The file manager for the LAZR example web service."""3"""The file manager for the LAZR example web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = ['FileManager',8__all__ = ['FileManager',
7 'ManagedFileResource']9 'ManagedFileResource']
810
=== modified file 'src/lazr/restful/example/base/interfaces.py'
--- src/lazr/restful/example/base/interfaces.py 2018-09-28 15:42:18 +0000
+++ src/lazr/restful/example/base/interfaces.py 2020-02-04 11:56:15 +0000
@@ -3,6 +3,8 @@
33
4"""Interface objects for the LAZR example web service."""4"""Interface objects for the LAZR example web service."""
55
6from __future__ import absolute_import, print_function
7
6__metaclass__ = type8__metaclass__ = type
7__all__ = ['AlreadyNew',9__all__ = ['AlreadyNew',
8 'Cuisine',10 'Cuisine',
911
=== modified file 'src/lazr/restful/example/base/root.py'
--- src/lazr/restful/example/base/root.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/example/base/root.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Data model objects for the LAZR example web service."""3"""Data model objects for the LAZR example web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = ['Cookbook',8__all__ = ['Cookbook',
7 'CookbookServiceRootResource',9 'CookbookServiceRootResource',
810
=== modified file 'src/lazr/restful/example/base/security.py'
--- src/lazr/restful/example/base/security.py 2015-04-08 20:11:29 +0000
+++ src/lazr/restful/example/base/security.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""A simple security policy for the LAZR example web service."""3"""A simple security policy for the LAZR example web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'CookbookWebServiceSecurityPolicy',9 'CookbookWebServiceSecurityPolicy',
810
=== modified file 'src/lazr/restful/example/base/subscribers.py'
--- src/lazr/restful/example/base/subscribers.py 2015-04-08 20:11:29 +0000
+++ src/lazr/restful/example/base/subscribers.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Event listeners for the example web service."""3"""Event listeners for the example web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = ['update_cookbook_revision_number']8__all__ = ['update_cookbook_revision_number']
79
810
=== modified file 'src/lazr/restful/example/base/tests/collection.txt'
--- src/lazr/restful/example/base/tests/collection.txt 2019-10-30 11:03:09 +0000
+++ src/lazr/restful/example/base/tests/collection.txt 2020-02-04 11:56:15 +0000
@@ -40,7 +40,7 @@
4040
41There are no XHTML representations available for collections.41There are no XHTML representations available for collections.
4242
43 >>> print webservice.get('/cookbooks', 'application/xhtml+xml')43 >>> print(webservice.get('/cookbooks', 'application/xhtml+xml'))
44 HTTP/1.1 200 Ok44 HTTP/1.1 200 Ok
45 ...45 ...
46 Content-Type: application/json46 Content-Type: application/json
@@ -76,7 +76,7 @@
76But requesting a batch size higher than the maximum configured value76But requesting a batch size higher than the maximum configured value
77results in a 400 error.77results in a 400 error.
7878
79 >>> print webservice.get("/cookbooks?ws.start=0&ws.size=1000")79 >>> print(webservice.get("/cookbooks?ws.start=0&ws.size=1000"))
80 HTTP/1.1 400 Bad Request80 HTTP/1.1 400 Bad Request
81 ...81 ...
82 Content-Type: text/plain...82 Content-Type: text/plain...
@@ -144,15 +144,15 @@
144144
145 >>> url = quote("/dishes/Roast chicken/recipes")145 >>> url = quote("/dishes/Roast chicken/recipes")
146 >>> result = webservice.get(url).jsonBody()146 >>> result = webservice.get(url).jsonBody()
147 >>> print result['resource_type_link']147 >>> print(result['resource_type_link'])
148 http://...#recipe-page-resource148 http://...#recipe-page-resource
149 >>> cookbooks_with_recipe = sorted(149 >>> cookbooks_with_recipe = sorted(
150 ... [r['cookbook_link'] for r in result['entries']])150 ... [r['cookbook_link'] for r in result['entries']])
151 >>> len(cookbooks_with_recipe)151 >>> len(cookbooks_with_recipe)
152 3152 3
153 >>> print cookbooks_with_recipe[0]153 >>> print(cookbooks_with_recipe[0])
154 http://.../cookbooks/James%20Beard%27s%20American%20Cookery154 http://.../cookbooks/James%20Beard%27s%20American%20Cookery
155 >>> print cookbooks_with_recipe[-1]155 >>> print(cookbooks_with_recipe[-1])
156 http://.../cookbooks/The%20Joy%20of%20Cooking156 http://.../cookbooks/The%20Joy%20of%20Cooking
157157
158================158================
@@ -201,13 +201,13 @@
201When an operation yields a collection of objects, the representation201When an operation yields a collection of objects, the representation
202includes a link that yields the total size of the collection.202includes a link that yields the total size of the collection.
203203
204 >>> print s_recipes['total_size_link']204 >>> print(s_recipes['total_size_link'])
205 http://.../cookbooks?search=chicken&vegetarian=false&ws.op=find_recipes&ws.show=total_size205 http://.../cookbooks?search=chicken&vegetarian=false&ws.op=find_recipes&ws.show=total_size
206206
207Sending a GET request to that link yields a JSON representation of the207Sending a GET request to that link yields a JSON representation of the
208total size.208total size.
209209
210 >>> print webservice.get(s_recipes['total_size_link']).jsonBody()210 >>> print(webservice.get(s_recipes['total_size_link']).jsonBody())
211 3211 3
212212
213If the entire collection fits in a single 'page' of results, the213If the entire collection fits in a single 'page' of results, the
@@ -239,7 +239,7 @@
239Custom operations may have error handling. In this case, the error239Custom operations may have error handling. In this case, the error
240handling is in the validate() method of the 'search' field.240handling is in the validate() method of the 'search' field.
241241
242 >>> print webservice.get("/cookbooks?ws.op=find_recipes")242 >>> print(webservice.get("/cookbooks?ws.op=find_recipes"))
243 HTTP/1.1 400 Bad Request243 HTTP/1.1 400 Bad Request
244 ...244 ...
245 search: Required input is missing.245 search: Required input is missing.
@@ -249,7 +249,7 @@
249 >>> from lazr.restful.testing.helpers import encode_response249 >>> from lazr.restful.testing.helpers import encode_response
250 >>> url = u"/cookbooks?ws.op=find_for_cuisine&cuisine=%E2%98%83"250 >>> url = u"/cookbooks?ws.op=find_for_cuisine&cuisine=%E2%98%83"
251 >>> response = webservice.get(url.encode("utf-8"))251 >>> response = webservice.get(url.encode("utf-8"))
252 >>> print encode_response(response)252 >>> print(encode_response(response))
253 HTTP/1.1 400 Bad Request253 HTTP/1.1 400 Bad Request
254 ...254 ...
255 cuisine: Invalid value "\u2603". Acceptable values are:...255 cuisine: Invalid value "\u2603". Acceptable values are:...
@@ -261,7 +261,7 @@
261261
262 >>> general_cookbooks = webservice.get(262 >>> general_cookbooks = webservice.get(
263 ... "/cookbooks?ws.op=find_for_cuisine&cuisine=General")263 ... "/cookbooks?ws.op=find_for_cuisine&cuisine=General")
264 >>> print general_cookbooks.jsonBody()['total_size']264 >>> print(general_cookbooks.jsonBody()['total_size'])
265 3265 3
266266
267POST operations267POST operations
@@ -279,18 +279,18 @@
279 ... name=name, cuisine=cuisine,279 ... name=name, cuisine=cuisine,
280 ... copyright_date=date, last_printing=date, price=price)280 ... copyright_date=date, last_printing=date, price=price)
281281
282 >>> print webservice.get(quote('/cookbooks/The Cake Bible'))282 >>> print(webservice.get(quote('/cookbooks/The Cake Bible')))
283 HTTP/1.1 404 Not Found283 HTTP/1.1 404 Not Found
284 ...284 ...
285285
286 >>> from datetime import date286 >>> from datetime import date
287 >>> print create_cookbook("The Cake Bible", "Dessert", date(1988, 1, 1))287 >>> print(create_cookbook("The Cake Bible", "Dessert", date(1988, 1, 1)))
288 HTTP/1.1 201 Created288 HTTP/1.1 201 Created
289 ...289 ...
290 Location: http://.../cookbooks/The%20Cake%20Bible290 Location: http://.../cookbooks/The%20Cake%20Bible
291 ...291 ...
292292
293 >>> print webservice.get("/cookbooks/The%20Cake%20Bible")293 >>> print(webservice.get("/cookbooks/The%20Cake%20Bible"))
294 HTTP/1.1 200 Ok294 HTTP/1.1 200 Ok
295 ...295 ...
296296
@@ -298,21 +298,21 @@
298create a cookbook with a name that's already in use. This exception is298create a cookbook with a name that's already in use. This exception is
299raised by the create() method itself.299raised by the create() method itself.
300300
301 >>> print create_cookbook("The Cake Bible", "Dessert", date(1988, 1, 1))301 >>> print(create_cookbook("The Cake Bible", "Dessert", date(1988, 1, 1)))
302 HTTP/1.1 409 Conflict302 HTTP/1.1 409 Conflict
303 ...303 ...
304 A cookbook called "The Cake Bible" already exists.304 A cookbook called "The Cake Bible" already exists.
305305
306A POST request has no meaning unless it specifies a custom operation.306A POST request has no meaning unless it specifies a custom operation.
307307
308 >>> print webservice.post("/cookbooks", 'text/plain', '')308 >>> print(webservice.post("/cookbooks", 'text/plain', ''))
309 HTTP/1.1 400 Bad Request309 HTTP/1.1 400 Bad Request
310 ...310 ...
311 No operation name given.311 No operation name given.
312312
313You can't invoke a nonexistent operation:313You can't invoke a nonexistent operation:
314314
315 >>> print webservice.named_post("/cookbooks", "nosuchop", {})315 >>> print(webservice.named_post("/cookbooks", "nosuchop", {}))
316 HTTP/1.1 400 Bad Request316 HTTP/1.1 400 Bad Request
317 ...317 ...
318 No such operation: nosuchop318 No such operation: nosuchop
319319
=== modified file 'src/lazr/restful/example/base/tests/entry.txt'
--- src/lazr/restful/example/base/tests/entry.txt 2018-09-28 15:46:34 +0000
+++ src/lazr/restful/example/base/tests/entry.txt 2020-02-04 11:56:15 +0000
@@ -108,7 +108,7 @@
108Every entry has an XHTML representation. The default representation is108Every entry has an XHTML representation. The default representation is
109a simple definition list.109a simple definition list.
110110
111 >>> print webservice.get(greens_url, 'application/xhtml+xml')111 >>> print(webservice.get(greens_url, 'application/xhtml+xml'))
112 HTTP/1.1 200 Ok112 HTTP/1.1 200 Ok
113 ...113 ...
114 <dl ...>114 <dl ...>
@@ -118,7 +118,7 @@
118Getting the XHTML representation works correctly even when some of the fields118Getting the XHTML representation works correctly even when some of the fields
119have non-ascii values.119have non-ascii values.
120120
121 >>> print webservice.get(construsions_url, 'application/xhtml+xml')121 >>> print(webservice.get(construsions_url, 'application/xhtml+xml'))
122 HTTP/1.1 200 Ok122 HTTP/1.1 200 Ok
123 ...123 ...
124 <dl ...>124 <dl ...>
@@ -158,7 +158,7 @@
158...and the XHTML representation of an ICookbook will be the result of158...and the XHTML representation of an ICookbook will be the result of
159calling a DummyView object.159calling a DummyView object.
160160
161 >>> print webservice.get(greens_url, 'application/xhtml+xml')161 >>> print(webservice.get(greens_url, 'application/xhtml+xml'))
162 HTTP/1.1 200 Ok162 HTTP/1.1 200 Ok
163 ...163 ...
164 <html>foo</html>164 <html>foo</html>
@@ -172,7 +172,7 @@
172 ... required=[ICookbook, IWebServiceClientRequest],172 ... required=[ICookbook, IWebServiceClientRequest],
173 ... provided=IInterface, name=view_name)173 ... provided=IInterface, name=view_name)
174174
175 >>> print webservice.get(greens_url, 'application/xhtml+xml')175 >>> print(webservice.get(greens_url, 'application/xhtml+xml'))
176 HTTP/1.1 200 Ok176 HTTP/1.1 200 Ok
177 ...177 ...
178 <dl ...>178 <dl ...>
@@ -185,11 +185,11 @@
185There are two recipes in "James Beard's American Cookery", but one of185There are two recipes in "James Beard's American Cookery", but one of
186them has been marked private. The private one cannot be retrieved.186them has been marked private. The private one cannot be retrieved.
187187
188 >>> print webservice.get('/recipes/3')188 >>> print(webservice.get('/recipes/3'))
189 HTTP/1.1 200 Ok189 HTTP/1.1 200 Ok
190 ...190 ...
191191
192 >>> print webservice.get('/recipes/5')192 >>> print(webservice.get('/recipes/5'))
193 HTTP/1.1 401 Unauthorized193 HTTP/1.1 401 Unauthorized
194 ...194 ...
195195
@@ -199,9 +199,9 @@
199value for the 'confirmed' field is not visible.199value for the 'confirmed' field is not visible.
200200
201 >>> cookbook = webservice.get(greens_url).jsonBody()201 >>> cookbook = webservice.get(greens_url).jsonBody()
202 >>> print cookbook['name']202 >>> print(cookbook['name'])
203 Everyday Greens203 Everyday Greens
204 >>> print cookbook['confirmed']204 >>> print(cookbook['confirmed'])
205 tag:launchpad.net:2008:redacted205 tag:launchpad.net:2008:redacted
206206
207Named operations207Named operations
@@ -235,7 +235,7 @@
235may be relative to the versioned service root. This is for developer235may be relative to the versioned service root. This is for developer
236convenience only, as lazr.restful never serves relative URLs.236convenience only, as lazr.restful never serves relative URLs.
237237
238 >>> print dish_url238 >>> print(dish_url)
239 http://cookbooks.dev/devel/dishes/Roast%20chicken239 http://cookbooks.dev/devel/dishes/Roast%20chicken
240 >>> relative_url = quote("/dishes/Roast chicken")240 >>> relative_url = quote("/dishes/Roast chicken")
241 >>> find_recipe_in_joy(relative_url)['instructions']241 >>> find_recipe_in_joy(relative_url)['instructions']
@@ -255,22 +255,22 @@
255interesting. As a result of the operation, the cookbook's location will255interesting. As a result of the operation, the cookbook's location will
256change so we get a 301 response with the new URL.256change so we get a 301 response with the new URL.
257257
258 >>> print webservice.get(joy_url).jsonBody()['cuisine']258 >>> print(webservice.get(joy_url).jsonBody()['cuisine'])
259 General259 General
260260
261 >>> print webservice.named_post(joy_url, 'make_more_interesting', {})261 >>> print(webservice.named_post(joy_url, 'make_more_interesting', {}))
262 HTTP/1.1 301 Moved Permanently262 HTTP/1.1 301 Moved Permanently
263 ...263 ...
264 Location: http://cookbooks.dev/devel/cookbooks/The%20New%20The%20Joy%20of%20Cooking264 Location: http://cookbooks.dev/devel/cookbooks/The%20New%20The%20Joy%20of%20Cooking
265 ...265 ...
266266
267 >>> new_joy_url = quote("/cookbooks/The New The Joy of Cooking")267 >>> new_joy_url = quote("/cookbooks/The New The Joy of Cooking")
268 >>> print webservice.get(new_joy_url).jsonBody()['name']268 >>> print(webservice.get(new_joy_url).jsonBody()['name'])
269 The New The Joy of Cooking269 The New The Joy of Cooking
270270
271Custom operations may have error handling.271Custom operations may have error handling.
272272
273 >>> print webservice.named_post(new_joy_url, 'make_more_interesting', {})273 >>> print(webservice.named_post(new_joy_url, 'make_more_interesting', {}))
274 HTTP/1.1 400 Bad Request274 HTTP/1.1 400 Bad Request
275 ...275 ...
276 The 'New' trick can't be used on this cookbook because its276 The 'New' trick can't be used on this cookbook because its
@@ -283,7 +283,7 @@
283283
284Trying to invoke a nonexistent custom operation yields an error.284Trying to invoke a nonexistent custom operation yields an error.
285285
286 >>> print webservice.get("%s?ws.op=no_such_operation" % joy_url)286 >>> print(webservice.get("%s?ws.op=no_such_operation" % joy_url))
287 HTTP/1.1 400 Bad Request287 HTTP/1.1 400 Bad Request
288 ...288 ...
289 No such operation: no_such_operation289 No such operation: no_such_operation
@@ -316,12 +316,12 @@
316Greens" cookbook. The data returned is the new JSON representation of316Greens" cookbook. The data returned is the new JSON representation of
317the object.317the object.
318318
319 >>> print webservice.get(greens_url).jsonBody()['revision_number']319 >>> print(webservice.get(greens_url).jsonBody()['revision_number'])
320 0320 0
321321
322 >>> result = modify_cookbook('Everyday Greens', {'cuisine' : 'American'},322 >>> result = modify_cookbook('Everyday Greens', {'cuisine' : 'American'},
323 ... 'PATCH')323 ... 'PATCH')
324 >>> print result324 >>> print(result)
325 HTTP/1.1 209 Content Returned325 HTTP/1.1 209 Content Returned
326 ...326 ...
327 Content-Type: application/json327 Content-Type: application/json
@@ -329,13 +329,13 @@
329 {...}329 {...}
330330
331 >>> greens = result.jsonBody()331 >>> greens = result.jsonBody()
332 >>> print greens['cuisine']332 >>> print(greens['cuisine'])
333 American333 American
334334
335Whenever a client modifies a cookbook, the revision_number is335Whenever a client modifies a cookbook, the revision_number is
336incremented behind the scenes.336incremented behind the scenes.
337337
338 >>> print greens['revision_number']338 >>> print(greens['revision_number'])
339 1339 1
340340
341A modification may cause one of en entry's links to point to another341A modification may cause one of en entry's links to point to another
@@ -343,16 +343,16 @@
343recipe, turning it into a recipe for baked beans.343recipe, turning it into a recipe for baked beans.
344344
345 >>> old_dish = webservice.get("/recipes/1").jsonBody()['dish_link']345 >>> old_dish = webservice.get("/recipes/1").jsonBody()['dish_link']
346 >>> print old_dish346 >>> print(old_dish)
347 http://.../dishes/Roast%20chicken347 http://.../dishes/Roast%20chicken
348348
349 >>> new_dish = webservice.get("/recipes/4").jsonBody()['dish_link']349 >>> new_dish = webservice.get("/recipes/4").jsonBody()['dish_link']
350 >>> print new_dish350 >>> print(new_dish)
351 http://.../dishes/Baked%20beans351 http://.../dishes/Baked%20beans
352352
353 >>> new_entry = modify_entry(353 >>> new_entry = modify_entry(
354 ... "/recipes/2", {'dish_link' : new_dish}, 'PATCH').jsonBody()354 ... "/recipes/2", {'dish_link' : new_dish}, 'PATCH').jsonBody()
355 >>> print new_entry['dish_link']355 >>> print(new_entry['dish_link'])
356 http://.../dishes/Baked%20beans356 http://.../dishes/Baked%20beans
357357
358When changing one of an entry's links, you can use an absolute URL (as358When changing one of an entry's links, you can use an absolute URL (as
@@ -364,14 +364,14 @@
364 >>> new_entry = modify_entry(364 >>> new_entry = modify_entry(
365 ... "/recipes/2", {'dish_link' : relative_old_dish},365 ... "/recipes/2", {'dish_link' : relative_old_dish},
366 ... 'PATCH').jsonBody()366 ... 'PATCH').jsonBody()
367 >>> print new_entry['dish_link']367 >>> print(new_entry['dish_link'])
368 http://.../dishes/Roast%20chicken368 http://.../dishes/Roast%20chicken
369369
370A modification might cause an entry's address to change. Here we use370A modification might cause an entry's address to change. Here we use
371the web service to change the cookbook's name to 'Everyday Greens 2'.371the web service to change the cookbook's name to 'Everyday Greens 2'.
372372
373 >>> print modify_cookbook('Everyday Greens',373 >>> print(modify_cookbook('Everyday Greens',
374 ... {'name' : 'Everyday Greens 2'}, 'PATCH')374 ... {'name' : 'Everyday Greens 2'}, 'PATCH'))
375 HTTP/1.1 301 Moved Permanently375 HTTP/1.1 301 Moved Permanently
376 ...376 ...
377 Location: http://.../Everyday%20Greens%202377 Location: http://.../Everyday%20Greens%202
@@ -384,8 +384,8 @@
384the cookbook name back, we need to send a PATCH request to the new384the cookbook name back, we need to send a PATCH request to the new
385address.385address.
386386
387 >>> print modify_cookbook('Everyday Greens 2',387 >>> print(modify_cookbook('Everyday Greens 2',
388 ... {'name' : 'Everyday Greens'}, 'PATCH')388 ... {'name' : 'Everyday Greens'}, 'PATCH'))
389 HTTP/1.1 301 Moved Permanently389 HTTP/1.1 301 Moved Permanently
390 ...390 ...
391 Location: http://.../cookbooks/Everyday%20Greens391 Location: http://.../cookbooks/Everyday%20Greens
@@ -398,19 +398,19 @@
398also set the X-Content-Type-Override header, which will override the398also set the X-Content-Type-Override header, which will override the
399value of Content-Type.399value of Content-Type.
400400
401 >>> print modify_cookbook('Everyday Greens',401 >>> print(modify_cookbook('Everyday Greens',
402 ... {'cuisine' : 'General'}, 'POST',402 ... {'cuisine' : 'General'}, 'POST',
403 ... {'X-HTTP-Method-Override' : 'PATCH',403 ... {'X-HTTP-Method-Override' : 'PATCH',
404 ... 'Content-Type': 'not-a-valid-content/type',404 ... 'Content-Type': 'not-a-valid-content/type',
405 ... 'X-Content-Type-Override': 'application/json'})405 ... 'X-Content-Type-Override': 'application/json'}))
406 HTTP/1.1 209 Content Returned406 HTTP/1.1 209 Content Returned
407 ...407 ...
408408
409Here, the use of a nonexistent HTTP method causes an error.409Here, the use of a nonexistent HTTP method causes an error.
410410
411 >>> print modify_cookbook('Everyday Greens',411 >>> print(modify_cookbook('Everyday Greens',
412 ... {'cuisine' : 'General'}, 'POST',412 ... {'cuisine' : 'General'}, 'POST',
413 ... {'X-HTTP-Method-Override' : 'NOSUCHMETHOD'})413 ... {'X-HTTP-Method-Override' : 'NOSUCHMETHOD'}))
414 HTTP/1.1 405 Method Not Allowed414 HTTP/1.1 405 Method Not Allowed
415 ...415 ...
416416
@@ -419,8 +419,8 @@
419method, your value is ignored. Here, a nonexistent HTTP method is419method, your value is ignored. Here, a nonexistent HTTP method is
420ignored in favor of HTTP GET.420ignored in favor of HTTP GET.
421421
422 >>> print webservice('/cookbooks/Everyday%20Greens', 'GET',422 >>> print(webservice('/cookbooks/Everyday%20Greens', 'GET',
423 ... headers={'X-HTTP-Method-Override' : 'NOSUCHMETHOD'})423 ... headers={'X-HTTP-Method-Override' : 'NOSUCHMETHOD'}))
424 HTTP/1.1 200 Ok424 HTTP/1.1 200 Ok
425 ...425 ...
426 Content-Type: application/json426 Content-Type: application/json
@@ -437,24 +437,24 @@
437the object that was modified.437the object that was modified.
438438
439 >>> greens = webservice.get(greens_url).jsonBody()439 >>> greens = webservice.get(greens_url).jsonBody()
440 >>> print greens['cuisine']440 >>> print(greens['cuisine'])
441 General441 General
442442
443 >>> greens['cuisine'] = 'Vegetarian'443 >>> greens['cuisine'] = 'Vegetarian'
444 >>> print modify_cookbook('Everyday Greens', greens, 'PUT')444 >>> print(modify_cookbook('Everyday Greens', greens, 'PUT'))
445 HTTP/1.1 209 Content Returned445 HTTP/1.1 209 Content Returned
446 ...446 ...
447 {...}447 {...}
448448
449 >>> greens = webservice.get(greens_url).jsonBody()449 >>> greens = webservice.get(greens_url).jsonBody()
450 >>> print greens['cuisine']450 >>> print(greens['cuisine'])
451 Vegetarian451 Vegetarian
452452
453Because our patch format is the same as our representation format (a453Because our patch format is the same as our representation format (a
454JSON hash), any document that works with a PUT request will also work454JSON hash), any document that works with a PUT request will also work
455with a PATCH request.455with a PATCH request.
456456
457 >>> print modify_cookbook('Everyday Greens', greens, 'PATCH')457 >>> print(modify_cookbook('Everyday Greens', greens, 'PATCH'))
458 HTTP/1.1 209 Content Returned458 HTTP/1.1 209 Content Returned
459 ...459 ...
460460
@@ -464,8 +464,8 @@
464When making a PATCH, you don't have to get a JSON representation464When making a PATCH, you don't have to get a JSON representation
465back. You can also get an HTML representation.465back. You can also get an HTML representation.
466466
467 >>> print modify_cookbook('Everyday Greens', {}, 'PATCH',467 >>> print(modify_cookbook('Everyday Greens', {}, 'PATCH',
468 ... headers={'Accept': 'application/xhtml+xml'})468 ... headers={'Accept': 'application/xhtml+xml'}))
469 HTTP/1.1 209 Content Returned469 HTTP/1.1 209 Content Returned
470 ...470 ...
471 Content-Type: application/xhtml+xml471 Content-Type: application/xhtml+xml
@@ -476,8 +476,8 @@
476You can even get a WADL representation, though that's pretty useless.476You can even get a WADL representation, though that's pretty useless.
477477
478 >>> headers = {'Accept':'application/vd.sun.wadl+xml'}478 >>> headers = {'Accept':'application/vd.sun.wadl+xml'}
479 >>> print modify_cookbook('Everyday Greens', {}, 'PATCH',479 >>> print(modify_cookbook('Everyday Greens', {}, 'PATCH',
480 ... headers=headers)480 ... headers=headers))
481 HTTP/1.1 209 Content Returned481 HTTP/1.1 209 Content Returned
482 ...482 ...
483 Content-Type: application/vd.sun.wadl+xml483 Content-Type: application/vd.sun.wadl+xml
@@ -556,8 +556,8 @@
556If-None-Match header. This lets you save time when the resource hasn't556If-None-Match header. This lets you save time when the resource hasn't
557changed.557changed.
558558
559 >>> print webservice.get(greens_url,559 >>> print(webservice.get(greens_url,
560 ... headers={'If-None-Match': greens_etag})560 ... headers={'If-None-Match': greens_etag}))
561 HTTP/1.1 304 Not Modified561 HTTP/1.1 304 Not Modified
562 ...562 ...
563563
@@ -570,7 +570,7 @@
570 >>> etag = 'dummy-etag'570 >>> etag = 'dummy-etag'
571 >>> response = ajax.get(greens_url, headers={'If-None-Match' : etag})571 >>> response = ajax.get(greens_url, headers={'If-None-Match' : etag})
572 >>> etag = response.getheader("Etag")572 >>> etag = response.getheader("Etag")
573 >>> print ajax.get(greens_url, headers={'If-None-Match' : etag})573 >>> print(ajax.get(greens_url, headers={'If-None-Match' : etag}))
574 HTTP/1.1 304 Not Modified574 HTTP/1.1 304 Not Modified
575 ...575 ...
576576
@@ -581,8 +581,8 @@
581If the ETag you provide in If-Match matches the entry's current ETag,581If the ETag you provide in If-Match matches the entry's current ETag,
582your request goes through.582your request goes through.
583583
584 >>> print modify_cookbook('Everyday Greens', greens, 'PATCH',584 >>> print(modify_cookbook('Everyday Greens', greens, 'PATCH',
585 ... {'If-Match' : greens_etag})585 ... {'If-Match' : greens_etag}))
586 HTTP/1.1 209 Content Returned586 HTTP/1.1 209 Content Returned
587 ...587 ...
588588
@@ -590,8 +590,8 @@
590after you got your copy of it. Your request will fail with status code590after you got your copy of it. Your request will fail with status code
591412.591412.
592592
593 >>> print modify_cookbook('Everyday Greens', greens, 'PATCH',593 >>> print(modify_cookbook('Everyday Greens', greens, 'PATCH',
594 ... {'If-Match' : '"an-old-etag"'})594 ... {'If-Match' : '"an-old-etag"'}))
595 HTTP/1.1 412 Precondition Failed595 HTTP/1.1 412 Precondition Failed
596 ...596 ...
597597
@@ -600,15 +600,15 @@
600600
601 >>> greens = webservice.get(greens_url).jsonBody()601 >>> greens = webservice.get(greens_url).jsonBody()
602 >>> match = '"an-old-etag", %s' % greens['http_etag']602 >>> match = '"an-old-etag", %s' % greens['http_etag']
603 >>> print modify_cookbook('Everyday Greens', greens, 'PATCH',603 >>> print(modify_cookbook('Everyday Greens', greens, 'PATCH',
604 ... {'If-Match' : match})604 ... {'If-Match' : match}))
605 HTTP/1.1 209 Content Returned605 HTTP/1.1 209 Content Returned
606 ...606 ...
607607
608Both PUT and PATCH requests work this way.608Both PUT and PATCH requests work this way.
609609
610 >>> print modify_cookbook('Everyday Greens', greens, 'PUT',610 >>> print(modify_cookbook('Everyday Greens', greens, 'PUT',
611 ... {'If-Match' : 'an-old-etag'})611 ... {'If-Match' : 'an-old-etag'}))
612 HTTP/1.1 412 Precondition Failed612 HTTP/1.1 412 Precondition Failed
613 ...613 ...
614614
@@ -623,7 +623,7 @@
623read-only field?623read-only field?
624624
625 >>> greens = webservice.get(greens_url).jsonBody()625 >>> greens = webservice.get(greens_url).jsonBody()
626 >>> print greens['copyright_date']626 >>> print(greens['copyright_date'])
627 2003-01-01627 2003-01-01
628 >>> etag_before_server_modification = greens['http_etag']628 >>> etag_before_server_modification = greens['http_etag']
629629
@@ -639,7 +639,7 @@
639 >>> greens_object.copyright_date = datetime.date(2005, 12, 12)639 >>> greens_object.copyright_date = datetime.date(2005, 12, 12)
640640
641 >>> new_greens = webservice.get(greens_url).jsonBody()641 >>> new_greens = webservice.get(greens_url).jsonBody()
642 >>> print new_greens['copyright_date']642 >>> print(new_greens['copyright_date'])
643 2005-12-12643 2005-12-12
644 >>> etag_after_server_modification = new_greens['http_etag']644 >>> etag_after_server_modification = new_greens['http_etag']
645645
@@ -652,8 +652,8 @@
652fail, right?652fail, right?
653653
654 >>> body = {'description' : 'New description.'}654 >>> body = {'description' : 'New description.'}
655 >>> print modify_cookbook('Everyday Greens', body, 'PATCH',655 >>> print(modify_cookbook('Everyday Greens', body, 'PATCH',
656 ... {'If-Match' : etag_before_server_modification})656 ... {'If-Match' : etag_before_server_modification}))
657 HTTP/1.1 209 Content Returned657 HTTP/1.1 209 Content Returned
658 ...658 ...
659659
@@ -690,8 +690,8 @@
690ETag will never match anything, you'll always get a 412 error.)690ETag will never match anything, you'll always get a 412 error.)
691691
692 >>> body = {'description' : 'New description.'}692 >>> body = {'description' : 'New description.'}
693 >>> print modify_cookbook('Everyday Greens', body, 'PATCH',693 >>> print(modify_cookbook('Everyday Greens', body, 'PATCH',
694 ... {'If-Match' : "Weird etag"})694 ... {'If-Match' : "Weird etag"}))
695 HTTP/1.1 412 Precondition Failed695 HTTP/1.1 412 Precondition Failed
696 ...696 ...
697697
@@ -706,16 +706,16 @@
706A totally bogus ETag fails with a 412 error.706A totally bogus ETag fails with a 412 error.
707707
708 >>> greens['description'] = 'Another new description'708 >>> greens['description'] = 'Another new description'
709 >>> print modify_cookbook('Everyday Greens', greens, 'PUT',709 >>> print(modify_cookbook('Everyday Greens', greens, 'PUT',
710 ... {'If-Match' : "Not the old ETag"})710 ... {'If-Match' : "Not the old ETag"}))
711 HTTP/1.1 412 Precondition Failed711 HTTP/1.1 412 Precondition Failed
712 ...712 ...
713713
714When we use the original ETag, we don't cause a 412 error, but the PUT714When we use the original ETag, we don't cause a 412 error, but the PUT
715request fails anyway.715request fails anyway.
716716
717 >>> print modify_cookbook('Everyday Greens', greens, 'PUT',717 >>> print(modify_cookbook('Everyday Greens', greens, 'PUT',
718 ... {'If-Match' : old_etag})718 ... {'If-Match' : old_etag}))
719 HTTP/1.1 400 Bad Request719 HTTP/1.1 400 Bad Request
720 ...720 ...
721 http_etag: You tried to modify a read-only attribute.721 http_etag: You tried to modify a read-only attribute.
@@ -735,15 +735,15 @@
735735
736 >>> new_etag = webservice.get(greens_url).jsonBody()['http_etag']736 >>> new_etag = webservice.get(greens_url).jsonBody()['http_etag']
737737
738 >>> print webservice.get(738 >>> print(webservice.get(
739 ... greens_url,739 ... greens_url,
740 ... headers={'If-None-Match': new_etag})740 ... headers={'If-None-Match': new_etag}))
741 HTTP/1.1 304 Not Modified741 HTTP/1.1 304 Not Modified
742 ...742 ...
743743
744 >>> print webservice.get(744 >>> print(webservice.get(
745 ... greens_url,745 ... greens_url,
746 ... headers={'If-None-Match': "changed" + new_etag})746 ... headers={'If-None-Match': "changed" + new_etag}))
747 HTTP/1.1 200 Ok747 HTTP/1.1 200 Ok
748 ...748 ...
749749
@@ -767,7 +767,7 @@
767767
768 >>> recipe_url = '/recipes/3'768 >>> recipe_url = '/recipes/3'
769 >>> recipe = webservice.get(recipe_url).jsonBody()769 >>> recipe = webservice.get(recipe_url).jsonBody()
770 >>> print recipe['dish_link']770 >>> print(recipe['dish_link'])
771 http://.../dishes/Roast%20chicken771 http://.../dishes/Roast%20chicken
772772
773 >>> def modify_dish(url, recipe, new_dish_url):773 >>> def modify_dish(url, recipe, new_dish_url):
@@ -778,18 +778,18 @@
778 >>> new_dish = webservice.get(quote('/dishes/Baked beans')).jsonBody()778 >>> new_dish = webservice.get(quote('/dishes/Baked beans')).jsonBody()
779 >>> new_dish_url = new_dish['self_link']779 >>> new_dish_url = new_dish['self_link']
780 >>> recipe['dish_link'] = new_dish_url780 >>> recipe['dish_link'] = new_dish_url
781 >>> print modify_dish(recipe_url, recipe, new_dish_url)781 >>> print(modify_dish(recipe_url, recipe, new_dish_url))
782 HTTP/1.1 209 Content Returned782 HTTP/1.1 209 Content Returned
783 ...783 ...
784784
785 >>> recipe = webservice.get(recipe_url).jsonBody()785 >>> recipe = webservice.get(recipe_url).jsonBody()
786 >>> print recipe['dish_link']786 >>> print(recipe['dish_link'])
787 http://.../dishes/Baked%20beans787 http://.../dishes/Baked%20beans
788788
789Identification of the dish is done by specifying a URL; a random789Identification of the dish is done by specifying a URL; a random
790string won't work.790string won't work.
791791
792 >>> print modify_dish(recipe_url, recipe, 'A random string')792 >>> print(modify_dish(recipe_url, recipe, 'A random string'))
793 HTTP/1.1 400 Bad Request793 HTTP/1.1 400 Bad Request
794 ...794 ...
795 dish_link: "A random string" is not a valid URI.795 dish_link: "A random string" is not a valid URI.
@@ -797,14 +797,14 @@
797But not just any URL will do. It has to identify an object in the web797But not just any URL will do. It has to identify an object in the web
798service.798service.
799799
800 >>> print modify_dish(recipe_url, recipe, 'http://www.canonical.com')800 >>> print(modify_dish(recipe_url, recipe, 'http://www.canonical.com'))
801 HTTP/1.1 400 Bad Request801 HTTP/1.1 400 Bad Request
802 ...802 ...
803 dish_link: No such object "http://www.canonical.com".803 dish_link: No such object "http://www.canonical.com".
804804
805 >>> print modify_dish(805 >>> print(modify_dish(
806 ... recipe_url, recipe,806 ... recipe_url, recipe,
807 ... 'http://www.canonical.com/dishes/Baked%20beans')807 ... 'http://www.canonical.com/dishes/Baked%20beans'))
808 HTTP/1.1 400 Bad Request808 HTTP/1.1 400 Bad Request
809 ...809 ...
810 dish_link: No such object "http://www.canonical.com/dishes/Baked%20beans".810 dish_link: No such object "http://www.canonical.com/dishes/Baked%20beans".
@@ -813,7 +813,7 @@
813of HTTP).813of HTTP).
814814
815 >>> https_link = recipe['dish_link'].replace('http:', 'https:')815 >>> https_link = recipe['dish_link'].replace('http:', 'https:')
816 >>> print modify_dish(recipe_url, recipe, https_link)816 >>> print(modify_dish(recipe_url, recipe, https_link))
817 HTTP/1.1 400 Bad Request817 HTTP/1.1 400 Bad Request
818 ...818 ...
819 dish_link: No such object "https://.../Baked%20beans".819 dish_link: No such object "https://.../Baked%20beans".
@@ -822,7 +822,7 @@
822the object isn't the right kind of object. A recipe must be for a822the object isn't the right kind of object. A recipe must be for a
823dish, not a cookbook:823dish, not a cookbook:
824824
825 >>> print modify_dish(recipe_url, recipe, recipe['cookbook_link'])825 >>> print(modify_dish(recipe_url, recipe, recipe['cookbook_link']))
826 HTTP/1.1 400 Bad Request826 HTTP/1.1 400 Bad Request
827 ...827 ...
828 dish_link: Your value points to the wrong kind of object828 dish_link: Your value points to the wrong kind of object
@@ -858,53 +858,53 @@
858The two 400 error codes below are caused by a failure to understand858The two 400 error codes below are caused by a failure to understand
859the assertion. The string used in the assertion might not be a date.859the assertion. The string used in the assertion might not be a date.
860860
861 >>> print patch_greens_copyright_date('dummy')861 >>> print(patch_greens_copyright_date('dummy'))
862 HTTP/1.1 400 Bad Request862 HTTP/1.1 400 Bad Request
863 ...863 ...
864 copyright_date: Value doesn't look like a date.864 copyright_date: Value doesn't look like a date.
865865
866Or it might be a date that's not in UTC.866Or it might be a date that's not in UTC.
867867
868 >>> print patch_greens_copyright_date(u'2005-06-06T00:00:00.000000+05:00')868 >>> print(patch_greens_copyright_date(u'2005-06-06T00:00:00.000000+05:00'))
869 HTTP/1.1 400 Bad Request869 HTTP/1.1 400 Bad Request
870 ...870 ...
871 copyright_date: Time not in UTC.871 copyright_date: Time not in UTC.
872872
873There are five ways to specify UTC:873There are five ways to specify UTC:
874874
875 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000Z')875 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000Z'))
876 HTTP/1.1 209 Content Returned876 HTTP/1.1 209 Content Returned
877 ...877 ...
878878
879 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000+00:00')879 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000+00:00'))
880 HTTP/1.1 209 Content Returned880 HTTP/1.1 209 Content Returned
881 ...881 ...
882882
883 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000+0000')883 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000+0000'))
884 HTTP/1.1 209 Content Returned884 HTTP/1.1 209 Content Returned
885 ...885 ...
886886
887 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000-00:00')887 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000-00:00'))
888 HTTP/1.1 209 Content Returned888 HTTP/1.1 209 Content Returned
889 ...889 ...
890890
891 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000-0000')891 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000-0000'))
892 HTTP/1.1 209 Content Returned892 HTTP/1.1 209 Content Returned
893 ...893 ...
894894
895A value with a missing timezone is treated as UTC.895A value with a missing timezone is treated as UTC.
896896
897 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00.000000')897 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00.000000'))
898 HTTP/1.1 209 Content Returned898 HTTP/1.1 209 Content Returned
899 ...899 ...
900900
901Less precise time measurements may also be acceptable.901Less precise time measurements may also be acceptable.
902902
903 >>> print patch_greens_copyright_date(u'2003-01-01T00:00:00Z')903 >>> print(patch_greens_copyright_date(u'2003-01-01T00:00:00Z'))
904 HTTP/1.1 209 Content Returned904 HTTP/1.1 209 Content Returned
905 ...905 ...
906906
907 >>> print patch_greens_copyright_date(u'2003-01-01')907 >>> print(patch_greens_copyright_date(u'2003-01-01'))
908 HTTP/1.1 209 Content Returned908 HTTP/1.1 209 Content Returned
909 ...909 ...
910910
@@ -914,30 +914,30 @@
914A document that would be acceptable as the payload of a PATCH request914A document that would be acceptable as the payload of a PATCH request
915might not be acceptable as the payload of a PUT request.915might not be acceptable as the payload of a PUT request.
916916
917 >>> print modify_cookbook('Everyday Greens', {'name' : 'Greens'}, 'PUT')917 >>> print(modify_cookbook('Everyday Greens', {'name' : 'Greens'}, 'PUT'))
918 HTTP/1.1 400 Bad Request918 HTTP/1.1 400 Bad Request
919 ...919 ...
920 You didn't specify a value for the attribute 'description'.920 You didn't specify a value for the attribute 'description'.
921921
922A document that's not a valid JSON document is also unacceptable.922A document that's not a valid JSON document is also unacceptable.
923923
924 >>> print webservice.patch(greens_url, "application/json", "{")924 >>> print(webservice.patch(greens_url, "application/json", "{"))
925 HTTP/1.1 400 Bad Request925 HTTP/1.1 400 Bad Request
926 ...926 ...
927 Entity-body was not a well-formed JSON document.927 Entity-body was not a well-formed JSON document.
928928
929A document that's valid JSON but is not a JSON hash is unacceptable.929A document that's valid JSON but is not a JSON hash is unacceptable.
930930
931 >>> print modify_cookbook('Everyday Greens', 'name=Greens', 'PATCH')931 >>> print(modify_cookbook('Everyday Greens', 'name=Greens', 'PATCH'))
932 HTTP/1.1 400 Bad Request932 HTTP/1.1 400 Bad Request
933 ...933 ...
934 Expected a JSON hash.934 Expected a JSON hash.
935935
936An entry's read-only attributes can't be modified.936An entry's read-only attributes can't be modified.
937937
938 >>> print modify_cookbook(938 >>> print(modify_cookbook(
939 ... 'Everyday Greens',939 ... 'Everyday Greens',
940 ... {'copyright_date' : u'2001-01-01T01:01:01+00:00Z'}, 'PATCH')940 ... {'copyright_date' : u'2001-01-01T01:01:01+00:00Z'}, 'PATCH'))
941 HTTP/1.1 400 Bad Request941 HTTP/1.1 400 Bad Request
942 ...942 ...
943 copyright_date: You tried to modify a read-only attribute.943 copyright_date: You tried to modify a read-only attribute.
@@ -945,17 +945,17 @@
945You can send a document that includes a value for a read-only945You can send a document that includes a value for a read-only
946attribute, but it has to be the same as the current value.946attribute, but it has to be the same as the current value.
947947
948 >>> print modify_cookbook(948 >>> print(modify_cookbook(
949 ... 'Everyday Greens',949 ... 'Everyday Greens',
950 ... {'copyright_date' : greens['copyright_date']}, 'PATCH')950 ... {'copyright_date' : greens['copyright_date']}, 'PATCH'))
951 HTTP/1.1 209 Content Returned951 HTTP/1.1 209 Content Returned
952 ...952 ...
953953
954You can't change the link to an entry's associated collection.954You can't change the link to an entry's associated collection.
955955
956 >>> print modify_cookbook('Everyday Greens',956 >>> print(modify_cookbook('Everyday Greens',
957 ... {'recipes_collection_link' : 'dummy'},957 ... {'recipes_collection_link' : 'dummy'},
958 ... 'PATCH')958 ... 'PATCH'))
959 HTTP/1.1 400 Bad Request959 HTTP/1.1 400 Bad Request
960 ...960 ...
961 recipes_collection_link: You tried to modify a collection...961 recipes_collection_link: You tried to modify a collection...
@@ -963,33 +963,33 @@
963Again, you can send a document that includes a value for an associated963Again, you can send a document that includes a value for an associated
964collection link; you just can't _change_ the value.964collection link; you just can't _change_ the value.
965965
966 >>> print modify_cookbook(966 >>> print(modify_cookbook(
967 ... 'Everyday Greens',967 ... 'Everyday Greens',
968 ... {'recipes_collection_link' : greens['recipes_collection_link']},968 ... {'recipes_collection_link' : greens['recipes_collection_link']},
969 ... 'PATCH')969 ... 'PATCH'))
970 HTTP/1.1 209 Content Returned970 HTTP/1.1 209 Content Returned
971 ...971 ...
972972
973You can't directly change an entry's URL address.973You can't directly change an entry's URL address.
974974
975 >>> print modify_cookbook('Everyday Greens',975 >>> print(modify_cookbook('Everyday Greens',
976 ... {'self_link' : 'dummy'}, 'PATCH')976 ... {'self_link' : 'dummy'}, 'PATCH'))
977 HTTP/1.1 400 Bad Request977 HTTP/1.1 400 Bad Request
978 ...978 ...
979 self_link: You tried to modify a read-only attribute.979 self_link: You tried to modify a read-only attribute.
980980
981You can't directly change an entry's ETag.981You can't directly change an entry's ETag.
982982
983 >>> print modify_cookbook('Everyday Greens',983 >>> print(modify_cookbook('Everyday Greens',
984 ... {'http_etag' : 'dummy'}, 'PATCH')984 ... {'http_etag' : 'dummy'}, 'PATCH'))
985 HTTP/1.1 400 Bad Request985 HTTP/1.1 400 Bad Request
986 ...986 ...
987 http_etag: You tried to modify a read-only attribute.987 http_etag: You tried to modify a read-only attribute.
988988
989You can't change an entry's resource type.989You can't change an entry's resource type.
990990
991 >>> print modify_cookbook('Everyday Greens',991 >>> print(modify_cookbook('Everyday Greens',
992 ... {'resource_type_link' : 'dummy'}, 'PATCH')992 ... {'resource_type_link' : 'dummy'}, 'PATCH'))
993 HTTP/1.1 400 Bad Request993 HTTP/1.1 400 Bad Request
994 ...994 ...
995 resource_type_link: You tried to modify a read-only attribute.995 resource_type_link: You tried to modify a read-only attribute.
@@ -998,8 +998,8 @@
998though it were the actual object. A cookbook has a998though it were the actual object. A cookbook has a
999'recipes_collection_link', but it doesn't have 'recipes' directly.999'recipes_collection_link', but it doesn't have 'recipes' directly.
10001000
1001 >>> print modify_cookbook(1001 >>> print(modify_cookbook(
1002 ... 'Everyday Greens', {'recipes' : 'dummy'}, 'PATCH')1002 ... 'Everyday Greens', {'recipes' : 'dummy'}, 'PATCH'))
1003 HTTP/1.1 400 Bad Request1003 HTTP/1.1 400 Bad Request
1004 ...1004 ...
1005 recipes: You tried to modify a nonexistent attribute.1005 recipes: You tried to modify a nonexistent attribute.
@@ -1007,8 +1007,8 @@
1007A recipe has a 'dish_link', but it doesn't have a 'dish' directly.1007A recipe has a 'dish_link', but it doesn't have a 'dish' directly.
10081008
1009 >>> url = quote('/cookbooks/The Joy of Cooking/Roast chicken')1009 >>> url = quote('/cookbooks/The Joy of Cooking/Roast chicken')
1010 >>> print webservice.patch(url, 'application/json',1010 >>> print(webservice.patch(url, 'application/json',
1011 ... simplejson.dumps({'dish' : 'dummy'}))1011 ... simplejson.dumps({'dish' : 'dummy'})))
1012 HTTP/1.1 400 Bad Request1012 HTTP/1.1 400 Bad Request
1013 ...1013 ...
1014 dish: You tried to modify a nonexistent attribute.1014 dish: You tried to modify a nonexistent attribute.
@@ -1016,16 +1016,16 @@
1016You can't set values that violate data integrity rules. For instance,1016You can't set values that violate data integrity rules. For instance,
1017you can't set a required value to None.1017you can't set a required value to None.
10181018
1019 >>> print modify_cookbook('Everyday Greens',1019 >>> print(modify_cookbook('Everyday Greens',
1020 ... {'name' : None}, 'PATCH')1020 ... {'name' : None}, 'PATCH'))
1021 HTTP/1.1 400 Bad Request1021 HTTP/1.1 400 Bad Request
1022 ...1022 ...
1023 name: Missing required value.1023 name: Missing required value.
10241024
1025And of course you can't modify attributes that don't exist.1025And of course you can't modify attributes that don't exist.
10261026
1027 >>> print modify_cookbook(1027 >>> print(modify_cookbook(
1028 ... 'Everyday Greens', {'nonesuch' : 'dummy'}, 'PATCH')1028 ... 'Everyday Greens', {'nonesuch' : 'dummy'}, 'PATCH'))
1029 HTTP/1.1 400 Bad Request1029 HTTP/1.1 400 Bad Request
1030 ...1030 ...
1031 nonesuch: You tried to modify a nonexistent attribute.1031 nonesuch: You tried to modify a nonexistent attribute.
@@ -1037,14 +1037,14 @@
1037example web service, recipes can be deleted.1037example web service, recipes can be deleted.
10381038
1039 >>> recipe_url = "/recipes/6"1039 >>> recipe_url = "/recipes/6"
1040 >>> print webservice.get(recipe_url)1040 >>> print(webservice.get(recipe_url))
1041 HTTP/1.1 200 Ok1041 HTTP/1.1 200 Ok
1042 ...1042 ...
10431043
1044 >>> print webservice.delete(recipe_url)1044 >>> print(webservice.delete(recipe_url))
1045 HTTP/1.1 200 Ok1045 HTTP/1.1 200 Ok
1046 ...1046 ...
10471047
1048 >>> print webservice.get(recipe_url)1048 >>> print(webservice.get(recipe_url))
1049 HTTP/1.1 404 Not Found1049 HTTP/1.1 404 Not Found
1050 ...1050 ...
10511051
=== modified file 'src/lazr/restful/example/base/tests/field.txt'
--- src/lazr/restful/example/base/tests/field.txt 2011-07-27 21:28:35 +0000
+++ src/lazr/restful/example/base/tests/field.txt 2020-02-04 11:56:15 +0000
@@ -18,10 +18,10 @@
18 ... return webservice(field_url, 'PATCH',18 ... return webservice(field_url, 'PATCH',
19 ... simplejson.dumps(description)).jsonBody()19 ... simplejson.dumps(description)).jsonBody()
2020
21 >>> print set_description("New description")21 >>> print(set_description("New description"))
22 New description22 New description
2323
24 >>> print webservice.get(field_url)24 >>> print(webservice.get(field_url))
25 HTTP/1.1 200 Ok25 HTTP/1.1 200 Ok
26 ...26 ...
27 Content-Type: application/json27 Content-Type: application/json
@@ -31,20 +31,20 @@
31PATCH on a field resource works identically to PUT.31PATCH on a field resource works identically to PUT.
3232
33 >>> representation = simplejson.dumps('<b>Bold description</b>')33 >>> representation = simplejson.dumps('<b>Bold description</b>')
34 >>> print webservice.put(field_url, 'application/json',34 >>> print(webservice.put(field_url, 'application/json',
35 ... representation).jsonBody()35 ... representation).jsonBody())
36 <b>Bold description</b>36 <b>Bold description</b>
3737
38If you get a field that contains a link to another object, you'll see38If you get a field that contains a link to another object, you'll see
39the link, rather than the actual object.39the link, rather than the actual object.
4040
41 >>> link_field_url = "/recipes/3/cookbook_link"41 >>> link_field_url = "/recipes/3/cookbook_link"
42 >>> print webservice.get(link_field_url).jsonBody()42 >>> print(webservice.get(link_field_url).jsonBody())
43 http://.../cookbooks/James%20Beard%27s%20American%20Cookery43 http://.../cookbooks/James%20Beard%27s%20American%20Cookery
4444
45 >>> collection_url = quote(45 >>> collection_url = quote(
46 ... "/cookbooks/The Joy of Cooking/recipes_collection_link")46 ... "/cookbooks/The Joy of Cooking/recipes_collection_link")
47 >>> print webservice.get(collection_url).jsonBody()47 >>> print(webservice.get(collection_url).jsonBody())
48 http://.../cookbooks/The%20Joy%20of%20Cooking/recipes48 http://.../cookbooks/The%20Joy%20of%20Cooking/recipes
4949
50Changing a field resource that contains a link works the same way as50Changing a field resource that contains a link works the same way as
@@ -52,10 +52,10 @@
5252
53 >>> new_value = simplejson.dumps(53 >>> new_value = simplejson.dumps(
54 ... webservice.get(cookbook_url).jsonBody()['self_link'])54 ... webservice.get(cookbook_url).jsonBody()['self_link'])
55 >>> print new_value55 >>> print(new_value)
56 "http://.../cookbooks/The%20Joy%20of%20Cooking"56 "http://.../cookbooks/The%20Joy%20of%20Cooking"
5757
58 >>> print webservice(link_field_url, 'PATCH', new_value)58 >>> print(webservice(link_field_url, 'PATCH', new_value))
59 HTTP/1.1 209 Content Returned59 HTTP/1.1 209 Content Returned
60 ...60 ...
61 Content-Type: application/json61 Content-Type: application/json
@@ -67,13 +67,13 @@
67the entry as a whole or just modifying a single field.67the entry as a whole or just modifying a single field.
6868
69 >>> date_field_url = cookbook_url + "/copyright_date"69 >>> date_field_url = cookbook_url + "/copyright_date"
70 >>> print webservice.put(date_field_url, 'application/json',70 >>> print(webservice.put(date_field_url, 'application/json',
71 ... simplejson.dumps("string"))71 ... simplejson.dumps("string")))
72 HTTP/1.1 400 Bad Request72 HTTP/1.1 400 Bad Request
73 ...73 ...
74 copyright_date: Value doesn't look like a date.74 copyright_date: Value doesn't look like a date.
7575
76 >>> print webservice(collection_url, 'PATCH', new_value)76 >>> print(webservice(collection_url, 'PATCH', new_value))
77 HTTP/1.1 400 Bad Request77 HTTP/1.1 400 Bad Request
78 ...78 ...
79 recipes_collection_link: You tried to modify a collection attribute.79 recipes_collection_link: You tried to modify a collection attribute.
@@ -81,10 +81,10 @@
81Field resources also support GET, for when you only need part of an81Field resources also support GET, for when you only need part of an
82entry. You can get either a JSON or XHTML-fragment representation.82entry. You can get either a JSON or XHTML-fragment representation.
8383
84 >>> print webservice.get(field_url).jsonBody()84 >>> print(webservice.get(field_url).jsonBody())
85 <b>Bold description</b>85 <b>Bold description</b>
8686
87 >>> print webservice.get(field_url, 'application/xhtml+xml')87 >>> print(webservice.get(field_url, 'application/xhtml+xml'))
88 HTTP/1.1 200 Ok88 HTTP/1.1 200 Ok
89 ...89 ...
90 Content-Type: application/xhtml+xml90 Content-Type: application/xhtml+xml
@@ -104,8 +104,8 @@
104104
105 >>> name_url = cookbook_url + "/name"105 >>> name_url = cookbook_url + "/name"
106 >>> representation = simplejson.dumps("The Joy of Cooking Extreme")106 >>> representation = simplejson.dumps("The Joy of Cooking Extreme")
107 >>> print webservice.put(name_url, 'application/json',107 >>> print(webservice.put(name_url, 'application/json',
108 ... representation)108 ... representation))
109 HTTP/1.1 301 Moved Permanently109 HTTP/1.1 301 Moved Permanently
110 ...110 ...
111 Location: http://.../cookbooks/The%20Joy%20of%20Cooking%20Extreme/name111 Location: http://.../cookbooks/The%20Joy%20of%20Cooking%20Extreme/name
@@ -113,12 +113,12 @@
113113
114Note that the entry's URL has also changed.114Note that the entry's URL has also changed.
115115
116 >>> print webservice.get(cookbook_url)116 >>> print(webservice.get(cookbook_url))
117 HTTP/1.1 404 Not Found117 HTTP/1.1 404 Not Found
118 ...118 ...
119119
120 >>> new_cookbook_url = quote("/cookbooks/The Joy of Cooking Extreme")120 >>> new_cookbook_url = quote("/cookbooks/The Joy of Cooking Extreme")
121 >>> print webservice.get(new_cookbook_url)121 >>> print(webservice.get(new_cookbook_url))
122 HTTP/1.1 200 Ok122 HTTP/1.1 200 Ok
123 ...123 ...
124124
@@ -126,8 +126,8 @@
126126
127 >>> representation = simplejson.dumps("The Joy of Cooking")127 >>> representation = simplejson.dumps("The Joy of Cooking")
128 >>> new_name_url = new_cookbook_url + "/name"128 >>> new_name_url = new_cookbook_url + "/name"
129 >>> print webservice.put(new_name_url, 'application/json',129 >>> print(webservice.put(new_name_url, 'application/json',
130 ... representation)130 ... representation))
131 HTTP/1.1 301 Moved Permanently131 HTTP/1.1 301 Moved Permanently
132 ...132 ...
133 Location: http://.../cookbooks/The%20Joy%20of%20Cooking/name133 Location: http://.../cookbooks/The%20Joy%20of%20Cooking/name
@@ -144,7 +144,7 @@
144within the cookbook entry itself.144within the cookbook entry itself.
145145
146 >>> cookbook = webservice.get(cookbook_url).jsonBody()146 >>> cookbook = webservice.get(cookbook_url).jsonBody()
147 >>> print cookbook['cuisine']147 >>> print(cookbook['cuisine'])
148 General148 General
149149
150Here's the representation of the resource for the same 'cuisine' field.150Here's the representation of the resource for the same 'cuisine' field.
@@ -167,8 +167,8 @@
167HTML-escaped string, similar to what's seen in the JSON representation167HTML-escaped string, similar to what's seen in the JSON representation
168of the entry.168of the entry.
169169
170 >>> print webservice.get(cookbook_url + '/cuisine',170 >>> print(webservice.get(cookbook_url + '/cuisine',
171 ... 'application/xhtml+xml')171 ... 'application/xhtml+xml'))
172 HTTP/1.1 200 Ok172 HTTP/1.1 200 Ok
173 ...173 ...
174 General174 General
@@ -180,7 +180,7 @@
180Field resources support GET, PUT, and PATCH.180Field resources support GET, PUT, and PATCH.
181181
182 >>> for method in ['HEAD', 'POST', 'DELETE', 'OPTIONS']:182 >>> for method in ['HEAD', 'POST', 'DELETE', 'OPTIONS']:
183 ... print webservice(field_url, method)183 ... print(webservice(field_url, method))
184 HTTP/1.1 405 Method Not Allowed...184 HTTP/1.1 405 Method Not Allowed...
185 Allow: GET PUT PATCH185 Allow: GET PUT PATCH
186 ...186 ...
@@ -210,13 +210,13 @@
210 >>> cookbook_etag == etag210 >>> cookbook_etag == etag
211 False211 False
212212
213 >>> print webservice.get(field_url, headers={'If-None-Match': etag})213 >>> print(webservice.get(field_url, headers={'If-None-Match': etag}))
214 HTTP/1.1 304 Not Modified214 HTTP/1.1 304 Not Modified
215 ...215 ...
216216
217 >>> ignored = set_description("new description")217 >>> ignored = set_description("new description")
218 >>> print webservice.get(field_url,218 >>> print(webservice.get(field_url,
219 ... headers={'If-None-Match': etag})219 ... headers={'If-None-Match': etag}))
220 HTTP/1.1 200 Ok220 HTTP/1.1 200 Ok
221 ...221 ...
222222
@@ -234,18 +234,18 @@
234provided in If-Match is the one we just got from a GET request.234provided in If-Match is the one we just got from a GET request.
235235
236 >>> representation = simplejson.dumps("New description")236 >>> representation = simplejson.dumps("New description")
237 >>> print webservice.put(field_url, 'application/json',237 >>> print(webservice.put(field_url, 'application/json',
238 ... representation,238 ... representation,
239 ... headers={'If-Match': cookbook_etag})239 ... headers={'If-Match': cookbook_etag}))
240 HTTP/1.1 209 Content Returned240 HTTP/1.1 209 Content Returned
241 ...241 ...
242242
243But when the field is modified, the ETag changes. Any subsequent243But when the field is modified, the ETag changes. Any subsequent
244requests that use that ETag in If-Match will fail.244requests that use that ETag in If-Match will fail.
245245
246 >>> print webservice.put(field_url, 'application/json',246 >>> print(webservice.put(field_url, 'application/json',
247 ... representation,247 ... representation,
248 ... headers={'If-Match': cookbook_etag})248 ... headers={'If-Match': cookbook_etag}))
249 HTTP/1.1 412 Precondition Failed249 HTTP/1.1 412 Precondition Failed
250 ...250 ...
251251
@@ -258,7 +258,7 @@
258Every entry has an XHTML representation. The default representation is258Every entry has an XHTML representation. The default representation is
259a simple text node.259a simple text node.
260260
261 >>> print webservice.get(field_url, 'application/xhtml+xml')261 >>> print(webservice.get(field_url, 'application/xhtml+xml'))
262 HTTP/1.1 200 Ok262 HTTP/1.1 200 Ok
263 ...263 ...
264 Description264 Description
@@ -281,7 +281,7 @@
281 ... """Bold the original string and add a snowman."""281 ... """Bold the original string and add a snowman."""
282 ... return simple_renderer282 ... return simple_renderer
283283
284 >>> print webservice.get(cookbook_url +'/name', 'application/xhtml+xml')284 >>> print(webservice.get(cookbook_url +'/name', 'application/xhtml+xml'))
285 HTTP/1.1 200 Ok285 HTTP/1.1 200 Ok
286 ...286 ...
287 The Joy of Cooking287 The Joy of Cooking
@@ -298,7 +298,7 @@
298298
299 >>> from lazr.restful.testing.helpers import encode_response299 >>> from lazr.restful.testing.helpers import encode_response
300 >>> response = webservice.get(field_url, 'application/xhtml+xml')300 >>> response = webservice.get(field_url, 'application/xhtml+xml')
301 >>> print encode_response(response)301 >>> print(encode_response(response))
302 HTTP/1.1 200 Ok302 HTTP/1.1 200 Ok
303 ...303 ...
304 \u2603 <b>Description</b>304 \u2603 <b>Description</b>
@@ -306,8 +306,8 @@
306In fact, that adapter will be used for every ITextLine field of an306In fact, that adapter will be used for every ITextLine field of an
307ICookbook.307ICookbook.
308308
309 >>> print encode_response(309 >>> print(encode_response(
310 ... webservice.get(cookbook_url +'/name', 'application/xhtml+xml'))310 ... webservice.get(cookbook_url +'/name', 'application/xhtml+xml')))
311 HTTP/1.1 200 Ok311 HTTP/1.1 200 Ok
312 ...312 ...
313 <b>The Joy of Cooking</b>313 <b>The Joy of Cooking</b>
@@ -315,15 +315,15 @@
315The adapter will not be used for ITextLine fields of other interfaces:315The adapter will not be used for ITextLine fields of other interfaces:
316316
317 >>> dish_field_url = quote('/dishes/Roast chicken/name')317 >>> dish_field_url = quote('/dishes/Roast chicken/name')
318 >>> print webservice.get(dish_field_url, 'application/xhtml+xml')318 >>> print(webservice.get(dish_field_url, 'application/xhtml+xml'))
319 HTTP/1.1 200 Ok319 HTTP/1.1 200 Ok
320 ...320 ...
321 Roast chicken321 Roast chicken
322322
323It will not be used for non-text fields of ICookbook.323It will not be used for non-text fields of ICookbook.
324324
325 >>> print webservice.get(cookbook_url + '/copyright_date',325 >>> print(webservice.get(cookbook_url + '/copyright_date',
326 ... 'application/xhtml+xml')326 ... 'application/xhtml+xml'))
327 HTTP/1.1 200 Ok327 HTTP/1.1 200 Ok
328 ...328 ...
329 1995-01-01329 1995-01-01
@@ -336,13 +336,13 @@
336336
337 >>> response = webservice.get(337 >>> response = webservice.get(
338 ... cookbook_url, 'application/json;include=lp_html')338 ... cookbook_url, 'application/json;include=lp_html')
339 >>> print response.getheader("Content-Type")339 >>> print(response.getheader("Content-Type"))
340 application/json;include=lp_html340 application/json;include=lp_html
341341
342The cookbook's description is a normal JSON representation...342The cookbook's description is a normal JSON representation...
343343
344 >>> json = response.jsonBody()344 >>> json = response.jsonBody()
345 >>> print json['description']345 >>> print(json['description'])
346 Description346 Description
347347
348...but the JSON dictionary will include a 'lp_html' sub-dictionary...348...but the JSON dictionary will include a 'lp_html' sub-dictionary...
@@ -356,10 +356,10 @@
356 [u'description', u'name']356 [u'description', u'name']
357357
358 >>> from lazr.restful.testing.helpers import encode_unicode358 >>> from lazr.restful.testing.helpers import encode_unicode
359 >>> print encode_unicode(html['description'])359 >>> print(encode_unicode(html['description']))
360 \u2603 <b>Description</b>360 \u2603 <b>Description</b>
361361
362 >>> print encode_unicode(html['name'])362 >>> print(encode_unicode(html['name']))
363 \u2603 <b>The Joy of Cooking</b>363 \u2603 <b>The Joy of Cooking</b>
364364
365Cleanup365Cleanup
@@ -375,7 +375,7 @@
375375
376 >>> ignored = set_description("<b>Bold description</b>")376 >>> ignored = set_description("<b>Bold description</b>")
377377
378 >>> print webservice.get(field_url, 'application/xhtml+xml')378 >>> print(webservice.get(field_url, 'application/xhtml+xml'))
379 HTTP/1.1 200 Ok379 HTTP/1.1 200 Ok
380 ...380 ...
381 &lt;b&gt;Bold description&lt;/b&gt;381 &lt;b&gt;Bold description&lt;/b&gt;
@@ -409,7 +409,7 @@
409returns UTF-8 instead of Unicode.409returns UTF-8 instead of Unicode.
410410
411 >>> response = webservice.get(field_url, 'application/xhtml+xml')411 >>> response = webservice.get(field_url, 'application/xhtml+xml')
412 >>> print encode_response(response)412 >>> print(encode_response(response))
413 HTTP/1.1 200 Ok413 HTTP/1.1 200 Ok
414 ...414 ...
415 \u2603 <b>Description</b>415 \u2603 <b>Description</b>
@@ -418,7 +418,7 @@
418ICookbook/ITextLine, other ITextLine fields of ICookbook are not418ICookbook/ITextLine, other ITextLine fields of ICookbook are not
419affected.419affected.
420420
421 >>> print webservice.get(cookbook_url + '/name', 'application/xhtml+xml')421 >>> print(webservice.get(cookbook_url + '/name', 'application/xhtml+xml'))
422 HTTP/1.1 200 Ok422 HTTP/1.1 200 Ok
423 ...423 ...
424 The Joy of Cooking424 The Joy of Cooking
@@ -427,7 +427,7 @@
427representations of that entry's fields.427representations of that entry's fields.
428428
429 >>> response = webservice.get(cookbook_url, 'application/xhtml+xml')429 >>> response = webservice.get(cookbook_url, 'application/xhtml+xml')
430 >>> print encode_response(response)430 >>> print(encode_response(response))
431 HTTP/1.1 200 Ok431 HTTP/1.1 200 Ok
432 ...432 ...
433 <dt>description</dt>433 <dt>description</dt>
@@ -439,7 +439,7 @@
439 >>> ignored = getGlobalSiteManager().unregisterAdapter(439 >>> ignored = getGlobalSiteManager().unregisterAdapter(
440 ... dummy_renderer, name='description')440 ... dummy_renderer, name='description')
441441
442 >>> print webservice.get(field_url, 'application/xhtml+xml')442 >>> print(webservice.get(field_url, 'application/xhtml+xml'))
443 HTTP/1.1 200 Ok443 HTTP/1.1 200 Ok
444 ...444 ...
445 Description445 Description
446446
=== modified file 'src/lazr/restful/example/base/tests/hostedfile.txt'
--- src/lazr/restful/example/base/tests/hostedfile.txt 2009-11-12 16:35:38 +0000
+++ src/lazr/restful/example/base/tests/hostedfile.txt 2020-02-04 11:56:15 +0000
@@ -22,14 +22,14 @@
22 u'http://.../cookbooks/Everyday%20Greens/cover'22 u'http://.../cookbooks/Everyday%20Greens/cover'
2323
24 >>> greens_cover = greens['cover_link']24 >>> greens_cover = greens['cover_link']
25 >>> print webservice.get(greens_cover)25 >>> print(webservice.get(greens_cover))
26 HTTP/1.1 404 Not Found26 HTTP/1.1 404 Not Found
27 ...27 ...
2828
29We can upload a cover with PUT.29We can upload a cover with PUT.
3030
31 >>> print webservice.put(greens_cover, 'image/png',31 >>> print(webservice.put(greens_cover, 'image/png',
32 ... "Pretend this is an image file.")32 ... "Pretend this is an image file."))
33 HTTP/1.1 200 Ok33 HTTP/1.1 200 Ok
34 ...34 ...
3535
@@ -38,7 +38,7 @@
38Internet.38Internet.
3939
40 >>> result = webservice.get(greens_cover)40 >>> result = webservice.get(greens_cover)
41 >>> print result41 >>> print(result)
42 HTTP/1.1 303 See Other42 HTTP/1.1 303 See Other
43 ...43 ...
44 Location: http://cookbooks.dev/.../filemanager/044 Location: http://cookbooks.dev/.../filemanager/0
@@ -53,7 +53,7 @@
5353
54 >>> filemanager_url = result.getheader('location')54 >>> filemanager_url = result.getheader('location')
55 >>> response = webservice.get(filemanager_url)55 >>> response = webservice.get(filemanager_url)
56 >>> print response56 >>> print(response)
57 HTTP/1.1 200 Ok57 HTTP/1.1 200 Ok
58 ...58 ...
59 Content-Type: image/png59 Content-Type: image/png
@@ -64,7 +64,7 @@
64The simple file manager has some nice features like setting the64The simple file manager has some nice features like setting the
65Content-Disposition, Last-Modified, and ETag headers.65Content-Disposition, Last-Modified, and ETag headers.
6666
67 >>> print response.getheader('Content-Disposition')67 >>> print(response.getheader('Content-Disposition'))
68 attachment; filename="cover"68 attachment; filename="cover"
6969
70Note that the name of the file is "cover", the same as the field70Note that the name of the file is "cover", the same as the field
@@ -79,25 +79,25 @@
79Make a second request using the ETag, and you'll get the response code 30479Make a second request using the ETag, and you'll get the response code 304
80("Not Modified").80("Not Modified").
8181
82 >>> print webservice.get(filemanager_url,82 >>> print(webservice.get(filemanager_url,
83 ... headers={'If-None-Match': etag})83 ... headers={'If-None-Match': etag}))
84 HTTP/1.1 304 Not Modified84 HTTP/1.1 304 Not Modified
85 ...85 ...
8686
87PUT is also used to modify a hosted file. Here's one that provides a87PUT is also used to modify a hosted file. Here's one that provides a
88filename as part of Content-Disposition.88filename as part of Content-Disposition.
8989
90 >>> print webservice.put(greens_cover, 'image/png',90 >>> print(webservice.put(greens_cover, 'image/png',
91 ... "Pretend this is another image file.",91 ... "Pretend this is another image file.",
92 ... {'Content-Disposition':92 ... {'Content-Disposition':
93 ... 'attachment; filename="greens-cover.png"'})93 ... 'attachment; filename="greens-cover.png"'}))
94 HTTP/1.1 200 Ok94 HTTP/1.1 200 Ok
95 ...95 ...
9696
97The new cover is available at a different URL.97The new cover is available at a different URL.
9898
99 >>> result = webservice.get(greens_cover)99 >>> result = webservice.get(greens_cover)
100 >>> print result100 >>> print(result)
101 HTTP/1.1 303 See Other101 HTTP/1.1 303 See Other
102 ...102 ...
103 Location: http://cookbooks.dev/.../filemanager/1103 Location: http://cookbooks.dev/.../filemanager/1
@@ -107,7 +107,7 @@
107back to us in the Content-Disposition header.107back to us in the Content-Disposition header.
108108
109 >>> filemanager_url = result.getheader('location')109 >>> filemanager_url = result.getheader('location')
110 >>> print webservice.get(filemanager_url)110 >>> print(webservice.get(filemanager_url))
111 HTTP/1.1 200 Ok111 HTTP/1.1 200 Ok
112 ...112 ...
113 Content-Disposition: attachment; filename="greens-cover.png"113 Content-Disposition: attachment; filename="greens-cover.png"
@@ -118,11 +118,11 @@
118real web service to define a more complex named operation that118real web service to define a more complex named operation that
119manipulates uploaded files.119manipulates uploaded files.
120120
121 >>> print webservice.named_post(greens_url, 'replace_cover',121 >>> print(webservice.named_post(greens_url, 'replace_cover',
122 ... cover='\x01\x02')122 ... cover='\x01\x02'))
123 HTTP/1.1 200 Ok123 HTTP/1.1 200 Ok
124 ...124 ...
125 >>> print webservice.get(greens_cover)125 >>> print(webservice.get(greens_cover))
126 HTTP/1.1 303 See Other126 HTTP/1.1 303 See Other
127 ...127 ...
128 Location: http://cookbooks.dev/devel/filemanager/2128 Location: http://cookbooks.dev/devel/filemanager/2
@@ -130,11 +130,11 @@
130130
131Deleting a cover (with DELETE) disables the redirect.131Deleting a cover (with DELETE) disables the redirect.
132132
133 >>> print webservice.delete(greens_cover)133 >>> print(webservice.delete(greens_cover))
134 HTTP/1.1 200 Ok134 HTTP/1.1 200 Ok
135 ...135 ...
136136
137 >>> print webservice.get(greens_cover)137 >>> print(webservice.get(greens_cover))
138 HTTP/1.1 404 Not Found138 HTTP/1.1 404 Not Found
139 ...139 ...
140140
@@ -148,8 +148,8 @@
148 >>> greens['cover_link'] = 'http://google.com/logo.png'148 >>> greens['cover_link'] = 'http://google.com/logo.png'
149149
150 >>> import simplejson150 >>> import simplejson
151 >>> print webservice.put(greens_url, 'application/json',151 >>> print(webservice.put(greens_url, 'application/json',
152 ... simplejson.dumps(greens))152 ... simplejson.dumps(greens)))
153 HTTP/1.1 400 Bad Request153 HTTP/1.1 400 Bad Request
154 ...154 ...
155 cover_link: To modify this field you need to send a PUT request to its155 cover_link: To modify this field you need to send a PUT request to its
@@ -159,12 +159,12 @@
159delete it.159delete it.
160160
161 >>> url = '/recipes/1/prepared_image'161 >>> url = '/recipes/1/prepared_image'
162 >>> print webservice.put(url, 'application/x-tar-gz', 'fakefiledata')162 >>> print(webservice.put(url, 'application/x-tar-gz', 'fakefiledata'))
163 HTTP/1.1 405 Method Not Allowed...163 HTTP/1.1 405 Method Not Allowed...
164 Allow: GET164 Allow: GET
165 ...165 ...
166166
167 >>> print webservice.delete(url)167 >>> print(webservice.delete(url))
168 HTTP/1.1 405 Method Not Allowed...168 HTTP/1.1 405 Method Not Allowed...
169 Allow: GET169 Allow: GET
170 ...170 ...
171171
=== modified file 'src/lazr/restful/example/base/tests/redirect.txt'
--- src/lazr/restful/example/base/tests/redirect.txt 2011-01-17 23:48:11 +0000
+++ src/lazr/restful/example/base/tests/redirect.txt 2020-02-04 11:56:15 +0000
@@ -11,7 +11,7 @@
11 >>> from lazr.restful.testing.webservice import WebServiceCaller11 >>> from lazr.restful.testing.webservice import WebServiceCaller
12 >>> webservice = WebServiceCaller(domain='cookbooks.dev')12 >>> webservice = WebServiceCaller(domain='cookbooks.dev')
1313
14 >>> print webservice.get("/cookbooks/featured")14 >>> print(webservice.get("/cookbooks/featured"))
15 HTTP/1.1 301 Moved Permanently15 HTTP/1.1 301 Moved Permanently
16 ...16 ...
17 Location: http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking17 Location: http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking
@@ -28,15 +28,15 @@
28 >>> from lazr.restful.testing.webservice import (28 >>> from lazr.restful.testing.webservice import (
29 ... WebServiceAjaxCaller)29 ... WebServiceAjaxCaller)
30 >>> ajax = WebServiceAjaxCaller(domain='cookbooks.dev')30 >>> ajax = WebServiceAjaxCaller(domain='cookbooks.dev')
31 >>> print ajax.get("/cookbooks/featured")31 >>> print(ajax.get("/cookbooks/featured"))
32 HTTP/1.1 301 Moved Permanently32 HTTP/1.1 301 Moved Permanently
33 ...33 ...
34 Location: http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking?ws.accept=application/json34 Location: http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking?ws.accept=application/json
35 ...35 ...
3636
37 >>> print ajax.get(37 >>> print(ajax.get(
38 ... "/cookbooks/featured",38 ... "/cookbooks/featured",
39 ... headers=dict(Accept="application/xhtml+xml"))39 ... headers=dict(Accept="application/xhtml+xml")))
40 HTTP/1.1 301 Moved Permanently40 HTTP/1.1 301 Moved Permanently
41 ...41 ...
42 Location: http://...?ws.accept=application/xhtml%2Bxml42 Location: http://...?ws.accept=application/xhtml%2Bxml
@@ -46,7 +46,7 @@
46valid in URIs. In this case, the redirect is to a URL that contains46valid in URIs. In this case, the redirect is to a URL that contains
47curly braces (see traversal.py for details).47curly braces (see traversal.py for details).
4848
49 >>> print ajax.get("/cookbooks/featured-invalid")49 >>> print(ajax.get("/cookbooks/featured-invalid"))
50 HTTP/1.1 301 Moved Permanently50 HTTP/1.1 301 Moved Permanently
51 ...51 ...
52 Location: http://.../Mastering%20the%20Art%20of%20French%20Cooking{invalid}?ws.accept=application/json52 Location: http://.../Mastering%20the%20Art%20of%20French%20Cooking{invalid}?ws.accept=application/json
5353
=== modified file 'src/lazr/restful/example/base/tests/representation-cache.txt'
--- src/lazr/restful/example/base/tests/representation-cache.txt 2010-06-14 15:24:20 +0000
+++ src/lazr/restful/example/base/tests/representation-cache.txt 2020-02-04 11:56:15 +0000
@@ -41,13 +41,13 @@
4141
42 >>> from lazr.restful.example.base.root import C4 as greens_object42 >>> from lazr.restful.example.base.root import C4 as greens_object
43 >>> json = "application/json"43 >>> json = "application/json"
44 >>> print cache.get(greens_object, json, "devel")44 >>> print(cache.get(greens_object, json, "devel"))
45 None45 None
46 >>> print cache.get(greens_object, json, "devel", "missing")46 >>> print(cache.get(greens_object, json, "devel", "missing"))
47 missing47 missing
4848
49 >>> cache.set(greens_object, json, "devel", "This is the 'devel' value.")49 >>> cache.set(greens_object, json, "devel", "This is the 'devel' value.")
50 >>> print cache.get(greens_object, json, "devel")50 >>> print(cache.get(greens_object, json, "devel"))
51 This is the 'devel' value.51 This is the 'devel' value.
52 >>> sorted(dictionary.keys())52 >>> sorted(dictionary.keys())
53 ['http://cookbooks.dev/devel/cookbooks/Everyday%20Greens,application/json']53 ['http://cookbooks.dev/devel/cookbooks/Everyday%20Greens,application/json']
@@ -56,7 +56,7 @@
56for different versions.56for different versions.
5757
58 >>> cache.set(greens_object, json, "1.0", "This is the '1.0' value.")58 >>> cache.set(greens_object, json, "1.0", "This is the '1.0' value.")
59 >>> print cache.get(greens_object, json, "1.0")59 >>> print(cache.get(greens_object, json, "1.0"))
60 This is the '1.0' value.60 This is the '1.0' value.
61 >>> sorted(dictionary.keys())61 >>> sorted(dictionary.keys())
62 ['http://cookbooks.dev/1.0/cookbooks/Everyday%20Greens,application/json',62 ['http://cookbooks.dev/1.0/cookbooks/Everyday%20Greens,application/json',
@@ -67,9 +67,9 @@
67 >>> cache.delete(greens_object)67 >>> cache.delete(greens_object)
68 >>> sorted(dictionary.keys())68 >>> sorted(dictionary.keys())
69 []69 []
70 >>> print cache.get(greens_object, json, "devel")70 >>> print(cache.get(greens_object, json, "devel"))
71 None71 None
72 >>> print cache.get(greens_object, json, "1.0")72 >>> print(cache.get(greens_object, json, "1.0"))
73 None73 None
7474
75DO_NOT_CACHE75DO_NOT_CACHE
@@ -103,7 +103,7 @@
103 >>> test_cache.set("Don't cache this.", json, "cache everything!",103 >>> test_cache.set("Don't cache this.", json, "cache everything!",
104 ... "representation")104 ... "representation")
105 >>> for key in sorted(test_dict.keys()):105 >>> for key in sorted(test_dict.keys()):
106 ... print key106 ... print(key)
107 Cache this.,application/json,1.0107 Cache this.,application/json,1.0
108 Don't cache this.,application/json,cache everything!108 Don't cache this.,application/json,cache everything!
109109
@@ -112,7 +112,7 @@
112112
113 >>> bad_key = "Don't cache this.,application/json,1.0"113 >>> bad_key = "Don't cache this.,application/json,1.0"
114 >>> test_dict[bad_key] = "This representation should not be cached."114 >>> test_dict[bad_key] = "This representation should not be cached."
115 >>> print test_cache.get("Don't cache this.", json, "1.0")115 >>> print(test_cache.get("Don't cache this.", json, "1.0"))
116 None116 None
117117
118A representation cache118A representation cache
@@ -135,7 +135,7 @@
135 >>> recipe_url = "/recipes/1"135 >>> recipe_url = "/recipes/1"
136 >>> ignored = webservice.get(recipe_url)136 >>> ignored = webservice.get(recipe_url)
137 >>> [the_only_key] = dictionary.keys()137 >>> [the_only_key] = dictionary.keys()
138 >>> print the_only_key138 >>> print(the_only_key)
139 http://cookbooks.dev/devel/recipes/1,application/json139 http://cookbooks.dev/devel/recipes/1,application/json
140140
141Note that the cache key incorporates the web service version name141Note that the cache key incorporates the web service version name
@@ -145,7 +145,7 @@
145Associated with the key is a string: the JSON representation of the object.145Associated with the key is a string: the JSON representation of the object.
146146
147 >>> import simplejson147 >>> import simplejson
148 >>> print simplejson.loads(dictionary[the_only_key])['self_link']148 >>> print(simplejson.loads(dictionary[the_only_key])['self_link'])
149 http://cookbooks.dev/devel/recipes/1149 http://cookbooks.dev/devel/recipes/1
150150
151If we get a representation of the same resource from a different web151If we get a representation of the same resource from a different web
@@ -153,7 +153,7 @@
153153
154 >>> ignored = webservice.get(recipe_url, api_version="1.0")154 >>> ignored = webservice.get(recipe_url, api_version="1.0")
155 >>> for key in sorted(dictionary.keys()):155 >>> for key in sorted(dictionary.keys()):
156 ... print key156 ... print(key)
157 http://cookbooks.dev/1.0/recipes/1,application/json157 http://cookbooks.dev/1.0/recipes/1,application/json
158 http://cookbooks.dev/devel/recipes/1,application/json158 http://cookbooks.dev/devel/recipes/1,application/json
159159
@@ -174,14 +174,14 @@
174174
175 >>> old_instructions = webservice.get(175 >>> old_instructions = webservice.get(
176 ... recipe_url, api_version='devel').jsonBody()['instructions']176 ... recipe_url, api_version='devel').jsonBody()['instructions']
177 >>> print old_instructions177 >>> print(old_instructions)
178 You can always judge...178 You can always judge...
179 >>> response = webservice.patch(recipe_url, 'application/json',179 >>> response = webservice.patch(recipe_url, 'application/json',
180 ... simplejson.dumps(dict(instructions="New instructions")),180 ... simplejson.dumps(dict(instructions="New instructions")),
181 ... api_version='devel')181 ... api_version='devel')
182 >>> print response.status182 >>> print(response.status)
183 209183 209
184 >>> print response.jsonBody()['instructions']184 >>> print(response.jsonBody()['instructions'])
185 New instructions185 New instructions
186186
187The modified representation is immediately available in the cache.187The modified representation is immediately available in the cache.
@@ -189,7 +189,7 @@
189 >>> from lazr.restful.example.base.root import RECIPES189 >>> from lazr.restful.example.base.root import RECIPES
190 >>> recipe = [recipe for recipe in RECIPES if recipe.id == 1][0]190 >>> recipe = [recipe for recipe in RECIPES if recipe.id == 1][0]
191 >>> cached_representation = cache.get(recipe, json, 'devel')191 >>> cached_representation = cache.get(recipe, json, 'devel')
192 >>> print simplejson.loads(cached_representation)['instructions']192 >>> print(simplejson.loads(cached_representation)['instructions'])
193 New instructions193 New instructions
194194
195Cleanup.195Cleanup.
@@ -206,14 +206,14 @@
206 >>> cookbook = [cookbook for cookbook in COOKBOOKS206 >>> cookbook = [cookbook for cookbook in COOKBOOKS
207 ... if cookbook.name == "Everyday Greens"][0]207 ... if cookbook.name == "Everyday Greens"][0]
208 >>> cache.set(cookbook, json, 'devel', "Dummy value.")208 >>> cache.set(cookbook, json, 'devel', "Dummy value.")
209 >>> print dictionary.keys()[0]209 >>> print(dictionary.keys()[0])
210 http://.../devel/cookbooks/Everyday%20Greens,application/json210 http://.../devel/cookbooks/Everyday%20Greens,application/json
211211
212 >>> from urllib import quote212 >>> from urllib import quote
213 >>> greens_url = quote("/cookbooks/Everyday Greens")213 >>> greens_url = quote("/cookbooks/Everyday Greens")
214 >>> ignore = webservice.named_post(214 >>> ignore = webservice.named_post(
215 ... greens_url, "replace_cover", cover="foo")215 ... greens_url, "replace_cover", cover="foo")
216 >>> print len(dictionary.keys())216 >>> print(len(dictionary.keys()))
217 0217 0
218218
219But unless the web service is the only way to manipulate a data set,219But unless the web service is the only way to manipulate a data set,
@@ -231,13 +231,13 @@
231the cache's delete() method.231the cache's delete() method.
232232
233 >>> ignore = webservice.get(recipe_url, api_version='devel')233 >>> ignore = webservice.get(recipe_url, api_version='devel')
234 >>> print cache.get(recipe, json, 'devel')234 >>> print(cache.get(recipe, json, 'devel'))
235 {...}235 {...}
236 >>> cache.delete(recipe)236 >>> cache.delete(recipe)
237237
238This deletes all the relevant representations.238This deletes all the relevant representations.
239239
240 >>> print cache.get(recipe, json, 'devel')240 >>> print(cache.get(recipe, json, 'devel'))
241 None241 None
242 >>> dictionary.keys()242 >>> dictionary.keys()
243 []243 []
@@ -251,7 +251,7 @@
251representation is not added to the cache.251representation is not added to the cache.
252252
253 >>> greens = webservice.get(greens_url).jsonBody()253 >>> greens = webservice.get(greens_url).jsonBody()
254 >>> print greens['confirmed']254 >>> print(greens['confirmed'])
255 tag:launchpad.net:2008:redacted255 tag:launchpad.net:2008:redacted
256256
257 >>> dictionary.keys()257 >>> dictionary.keys()
@@ -274,12 +274,12 @@
274definitely comes from the cache, not the original data source.274definitely comes from the cache, not the original data source.
275275
276 >>> cached_greens = webservice.get(greens_url).jsonBody()276 >>> cached_greens = webservice.get(greens_url).jsonBody()
277 >>> print cached_greens['name']277 >>> print(cached_greens['name'])
278 This comes from the cache; it is not generated.278 This comes from the cache; it is not generated.
279279
280But the redacted value is still redacted.280But the redacted value is still redacted.
281281
282 >>> print cached_greens['confirmed']282 >>> print(cached_greens['confirmed'])
283 tag:launchpad.net:2008:redacted283 tag:launchpad.net:2008:redacted
284284
285Cleanup: clear the cache.285Cleanup: clear the cache.
@@ -294,7 +294,7 @@
294294
295 >>> recipe_url ="/recipes/5"295 >>> recipe_url ="/recipes/5"
296 >>> response = webservice.get(recipe_url)296 >>> response = webservice.get(recipe_url)
297 >>> print response.status297 >>> print(response.status)
298 401298 401
299 >>> len(dictionary.keys())299 >>> len(dictionary.keys())
300 0300 0
@@ -306,7 +306,7 @@
306 >>> from lazr.restful.example.base.root import C3_D2 as recipe306 >>> from lazr.restful.example.base.root import C3_D2 as recipe
307 >>> recipe.private = False307 >>> recipe.private = False
308308
309 >>> print webservice.get("/recipes/5").jsonBody()['instructions']309 >>> print(webservice.get("/recipes/5").jsonBody()['instructions'])
310 Without doubt the most famous...310 Without doubt the most famous...
311311
312Now there's a representation in the cache.312Now there's a representation in the cache.
@@ -321,7 +321,7 @@
321321
322 >>> recipe.private = True322 >>> recipe.private = True
323 >>> response = webservice.get(recipe_url)323 >>> response = webservice.get(recipe_url)
324 >>> print response.status324 >>> print(response.status)
325 401325 401
326326
327Cleanup: clear the cache.327Cleanup: clear the cache.
@@ -351,7 +351,7 @@
351351
352 >>> for instructions in (352 >>> for instructions in (
353 ... sorted(recipe['instructions'] for recipe in recipes)):353 ... sorted(recipe['instructions'] for recipe in recipes)):
354 ... print instructions354 ... print(instructions)
355 A perfectly roasted chicken is...355 A perfectly roasted chicken is...
356 Draw, singe, stuff, and truss...356 Draw, singe, stuff, and truss...
357 ...357 ...
@@ -362,7 +362,7 @@
362populated the cache.362populated the cache.
363363
364 >>> for key in sorted(dictionary.keys()):364 >>> for key in sorted(dictionary.keys()):
365 ... print key365 ... print(key)
366 http://cookbooks.dev/devel/recipes/1,application/json366 http://cookbooks.dev/devel/recipes/1,application/json
367 http://cookbooks.dev/devel/recipes/2,application/json367 http://cookbooks.dev/devel/recipes/2,application/json
368 http://cookbooks.dev/devel/recipes/3,application/json368 http://cookbooks.dev/devel/recipes/3,application/json
@@ -379,7 +379,7 @@
379 >>> recipes = webservice.get("/recipes").jsonBody()['entries']379 >>> recipes = webservice.get("/recipes").jsonBody()['entries']
380 >>> for instructions in (380 >>> for instructions in (
381 ... sorted(recipe['instructions'] for recipe in recipes)):381 ... sorted(recipe['instructions'] for recipe in recipes)):
382 ... print instructions382 ... print(instructions)
383 This representation is from the cache.383 This representation is from the cache.
384 This representation is from the cache.384 This representation is from the cache.
385 This representation is from the cache.385 This representation is from the cache.
@@ -394,7 +394,7 @@
394394
395 >>> from lazr.restful.interfaces import IWebServiceConfiguration395 >>> from lazr.restful.interfaces import IWebServiceConfiguration
396 >>> config = getUtility(IWebServiceConfiguration)396 >>> config = getUtility(IWebServiceConfiguration)
397 >>> print config.enable_server_side_representation_cache397 >>> print(config.enable_server_side_representation_cache)
398 True398 True
399399
400Set this configuration value to False, and representations will not be400Set this configuration value to False, and representations will not be
@@ -404,7 +404,7 @@
404 >>> recipes = webservice.get("/recipes").jsonBody()['entries']404 >>> recipes = webservice.get("/recipes").jsonBody()['entries']
405 >>> for instructions in (405 >>> for instructions in (
406 ... sorted(recipe['instructions'] for recipe in recipes)):406 ... sorted(recipe['instructions'] for recipe in recipes)):
407 ... print instructions407 ... print(instructions)
408 A perfectly roasted chicken is...408 A perfectly roasted chicken is...
409 Draw, singe, stuff, and truss...409 Draw, singe, stuff, and truss...
410 Preheat oven to...410 Preheat oven to...
@@ -419,7 +419,7 @@
419419
420The cache is still registered as a utility.420The cache is still registered as a utility.
421421
422 >>> print getUtility(IRepresentationCache)422 >>> print(getUtility(IRepresentationCache))
423 <lazr.restful.simple.DictionaryBasedRepresentationCache object ...>423 <lazr.restful.simple.DictionaryBasedRepresentationCache object ...>
424424
425And it's still populated, as we can see by re-enabling it.425And it's still populated, as we can see by re-enabling it.
@@ -428,7 +428,7 @@
428 >>> recipes = webservice.get("/recipes").jsonBody()['entries']428 >>> recipes = webservice.get("/recipes").jsonBody()['entries']
429 >>> for instructions in (429 >>> for instructions in (
430 ... sorted(recipe['instructions'] for recipe in recipes)):430 ... sorted(recipe['instructions'] for recipe in recipes)):
431 ... print instructions431 ... print(instructions)
432 This representation is from the cache.432 This representation is from the cache.
433 This representation is from the cache.433 This representation is from the cache.
434 This representation is from the cache.434 This representation is from the cache.
@@ -454,7 +454,7 @@
454 >>> recipes = webservice.get("/recipes").jsonBody()['entries']454 >>> recipes = webservice.get("/recipes").jsonBody()['entries']
455 >>> for instructions in (455 >>> for instructions in (
456 ... sorted(recipe['instructions'] for recipe in recipes)):456 ... sorted(recipe['instructions'] for recipe in recipes)):
457 ... print instructions457 ... print(instructions)
458 A perfectly roasted chicken is...458 A perfectly roasted chicken is...
459 Draw, singe, stuff, and truss...459 Draw, singe, stuff, and truss...
460 Preheat oven to...460 Preheat oven to...
461461
=== modified file 'src/lazr/restful/example/base/tests/root.txt'
--- src/lazr/restful/example/base/tests/root.txt 2010-05-10 11:48:42 +0000
+++ src/lazr/restful/example/base/tests/root.txt 2020-02-04 11:56:15 +0000
@@ -17,7 +17,7 @@
17 >>> top_level_links['cookbooks_collection_link']17 >>> top_level_links['cookbooks_collection_link']
18 u'http://cookbooks.dev/devel/cookbooks'18 u'http://cookbooks.dev/devel/cookbooks'
1919
20 >>> print top_level_links['resource_type_link']20 >>> print(top_level_links['resource_type_link'])
21 http://cookbooks.dev/devel/#service-root21 http://cookbooks.dev/devel/#service-root
2222
23The client can explore the entire web service by following these links23The client can explore the entire web service by following these links
@@ -29,7 +29,7 @@
2929
30There is no XHTML representation available for the service root.30There is no XHTML representation available for the service root.
3131
32 >>> print webservice.get('/', 'application/xhtml+xml')32 >>> print(webservice.get('/', 'application/xhtml+xml'))
33 HTTP/1.1 200 Ok33 HTTP/1.1 200 Ok
34 ...34 ...
35 Content-Type: application/json35 Content-Type: application/json
@@ -40,7 +40,7 @@
40HEAD and OPTIONS.40HEAD and OPTIONS.
4141
42 >>> for method in ['HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS']:42 >>> for method in ['HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS']:
43 ... print webservice("/", method)43 ... print(webservice("/", method))
44 HTTP/1.1 405 Method Not Allowed...44 HTTP/1.1 405 Method Not Allowed...
45 Allow: GET45 Allow: GET
46 ...46 ...
@@ -110,7 +110,7 @@
110root. The cookbook web service has a 'featured cookbook' which may110root. The cookbook web service has a 'featured cookbook' which may
111change over time.111change over time.
112112
113 >>> print top_level_links['featured_cookbook_link']113 >>> print(top_level_links['featured_cookbook_link'])
114 http://.../cookbooks/featured114 http://.../cookbooks/featured
115115
116Caching policy116Caching policy
@@ -121,13 +121,13 @@
121service root can be cached for a long time:121service root can be cached for a long time:
122122
123 >>> response = webservice.get('/', api_version='1.0')123 >>> response = webservice.get('/', api_version='1.0')
124 >>> print response.getheader('Cache-Control')124 >>> print(response.getheader('Cache-Control'))
125 max-age=10000125 max-age=10000
126126
127The latest version of the service root should be cached for less time.127The latest version of the service root should be cached for less time.
128128
129 >>> response = webservice.get('/', api_version='devel')129 >>> response = webservice.get('/', api_version='devel')
130 >>> print response.getheader('Cache-Control')130 >>> print(response.getheader('Cache-Control'))
131 max-age=2131 max-age=2
132132
133Both the WADL and JSON representations of the service root are133Both the WADL and JSON representations of the service root are
@@ -135,7 +135,7 @@
135135
136 >>> wadl_type = 'application/vnd.sun.wadl+xml'136 >>> wadl_type = 'application/vnd.sun.wadl+xml'
137 >>> response = webservice.get('/', wadl_type)137 >>> response = webservice.get('/', wadl_type)
138 >>> print response.getheader('Cache-Control')138 >>> print(response.getheader('Cache-Control'))
139 max-age=2139 max-age=2
140140
141The Date header is set along with Cache-Control so that the client can141The Date header is set along with Cache-Control so that the client can
@@ -152,7 +152,7 @@
152 ... '/', wadl_type, headers={'If-None-Match' : etag})152 ... '/', wadl_type, headers={'If-None-Match' : etag})
153 >>> conditional_response.status153 >>> conditional_response.status
154 304154 304
155 >>> print conditional_response.getheader('Cache-Control')155 >>> print(conditional_response.getheader('Cache-Control'))
156 max-age=2156 max-age=2
157 >>> conditional_response.getheader('Date') is None157 >>> conditional_response.getheader('Date') is None
158 False158 False
@@ -166,9 +166,9 @@
166 >>> agent = 'Python-httplib2/$Rev: 259$'166 >>> agent = 'Python-httplib2/$Rev: 259$'
167 >>> response = webservice.get(167 >>> response = webservice.get(
168 ... '/', wadl_type, headers={'User-Agent' : agent})168 ... '/', wadl_type, headers={'User-Agent' : agent})
169 >>> print response.getheader('Cache-Control')169 >>> print(response.getheader('Cache-Control'))
170 None170 None
171 >>> print response.getheader('Date')171 >>> print(response.getheader('Date'))
172 None172 None
173173
174If the client identifies as an agent _based on_ httplib2, we take a174If the client identifies as an agent _based on_ httplib2, we take a
@@ -177,7 +177,7 @@
177 >>> agent = "Custom client (%s)" % agent177 >>> agent = "Custom client (%s)" % agent
178 >>> response = webservice.get(178 >>> response = webservice.get(
179 ... '/', wadl_type, headers={'User-Agent' : agent})179 ... '/', wadl_type, headers={'User-Agent' : agent})
180 >>> print response.getheader('Cache-Control')180 >>> print(response.getheader('Cache-Control'))
181 max-age=2181 max-age=2
182 >>> response.getheader('Date') is None182 >>> response.getheader('Date') is None
183 False183 False
@@ -192,7 +192,7 @@
192 >>> policy[-1] = 0192 >>> policy[-1] = 0
193193
194 >>> response = webservice.get('/')194 >>> response = webservice.get('/')
195 >>> print response.getheader('Cache-Control')195 >>> print(response.getheader('Cache-Control'))
196 None196 None
197 >>> response.getheader('Date') is None197 >>> response.getheader('Date') is None
198 True198 True
199199
=== modified file 'src/lazr/restful/example/base/tests/service.txt'
--- src/lazr/restful/example/base/tests/service.txt 2009-11-16 14:25:45 +0000
+++ src/lazr/restful/example/base/tests/service.txt 2020-02-04 11:56:15 +0000
@@ -17,7 +17,7 @@
1717
18 >>> top_level_response = webservice.get(18 >>> top_level_response = webservice.get(
19 ... "/", api_version="devel").jsonBody()19 ... "/", api_version="devel").jsonBody()
20 >>> print top_level_response['resource_type_link']20 >>> print(top_level_response['resource_type_link'])
21 http://cookbooks.dev/devel/#service-root21 http://cookbooks.dev/devel/#service-root
2222
23The web service in examples/multiversion is devoted solely to testing23The web service in examples/multiversion is devoted solely to testing
@@ -28,18 +28,18 @@
2828
29An attempt to access a nonexistent resource yields a 404 error.29An attempt to access a nonexistent resource yields a 404 error.
3030
31 >>> print webservice.get("/no-such-resource")31 >>> print(webservice.get("/no-such-resource"))
32 HTTP/1.1 404 Not Found32 HTTP/1.1 404 Not Found
33 ...33 ...
3434
35An attempt to access an existing resource without versioning the URL35An attempt to access an existing resource without versioning the URL
36yields a 404 error.36yields a 404 error.
3737
38 >>> print webservice.get("/cookbooks", api_version="/")38 >>> print(webservice.get("/cookbooks", api_version="/"))
39 HTTP/1.1 404 Not Found39 HTTP/1.1 404 Not Found
40 ...40 ...
4141
42 >>> print webservice.get("/", api_version="/")42 >>> print(webservice.get("/", api_version="/"))
43 HTTP/1.1 404 Not Found43 HTTP/1.1 404 Not Found
44 ...44 ...
4545
@@ -49,23 +49,23 @@
49An attempt to use an unsupported or nonexistent HTTP method on a49An attempt to use an unsupported or nonexistent HTTP method on a
50resource yields a 405 error.50resource yields a 405 error.
5151
52 >>> print webservice("/", method="COPY")52 >>> print(webservice("/", method="COPY"))
53 HTTP/1.1 405 Method Not Allowed...53 HTTP/1.1 405 Method Not Allowed...
54 Allow: GET54 Allow: GET
55 ...55 ...
5656
57 >>> print webservice.delete("/cookbooks")57 >>> print(webservice.delete("/cookbooks"))
58 HTTP/1.1 405 Method Not Allowed...58 HTTP/1.1 405 Method Not Allowed...
59 Allow: GET POST59 Allow: GET POST
60 ...60 ...
6161
62 >>> from urllib import quote62 >>> from urllib import quote
63 >>> print webservice.delete(quote("/dishes/Roast chicken"))63 >>> print(webservice.delete(quote("/dishes/Roast chicken")))
64 HTTP/1.1 405 Method Not Allowed...64 HTTP/1.1 405 Method Not Allowed...
65 Allow: GET PUT PATCH65 Allow: GET PUT PATCH
66 ...66 ...
6767
68 >>> print webservice.delete(quote("/cookbooks/The Joy of Cooking"))68 >>> print(webservice.delete(quote("/cookbooks/The Joy of Cooking")))
69 HTTP/1.1 405 Method Not Allowed...69 HTTP/1.1 405 Method Not Allowed...
70 Allow: GET PUT PATCH POST70 Allow: GET PUT PATCH POST
71 ...71 ...
@@ -76,18 +76,18 @@
76An attempt to PATCH a document with unsupported media type to a76An attempt to PATCH a document with unsupported media type to a
77resource yields a 415 error.77resource yields a 415 error.
7878
79 >>> print webservice.patch("/recipes/1", 'text/plain', "Foo")79 >>> print(webservice.patch("/recipes/1", 'text/plain', "Foo"))
80 HTTP/1.1 415 Unsupported Media Type80 HTTP/1.1 415 Unsupported Media Type
81 ...81 ...
8282
83The only supported media types are ones that begin with 'application/json'.83The only supported media types are ones that begin with 'application/json'.
8484
85 >>> print webservice.patch("/recipes/1", 'application/json', "{}")85 >>> print(webservice.patch("/recipes/1", 'application/json', "{}"))
86 HTTP/1.1 209 Content Returned86 HTTP/1.1 209 Content Returned
87 ...87 ...
8888
89 >>> print webservice.patch(89 >>> print(webservice.patch(
90 ... "/recipes/1", 'application/json; charset=UTF-8', "{}")90 ... "/recipes/1", 'application/json; charset=UTF-8', "{}"))
91 HTTP/1.1 209 Content Returned91 HTTP/1.1 209 Content Returned
92 ...92 ...
9393
9494
=== modified file 'src/lazr/restful/example/base/tests/test_integration.py'
--- src/lazr/restful/example/base/tests/test_integration.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/example/base/tests/test_integration.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test harness for LAZR doctests."""3"""Test harness for LAZR doctests."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = []8__all__ = []
79
@@ -45,7 +47,12 @@
45 [name47 [name
46 for name in os.listdir(os.path.dirname(__file__))48 for name in os.listdir(os.path.dirname(__file__))
47 if name.endswith('.txt')])49 if name.endswith('.txt')])
48 suite = doctest.DocFileSuite(optionflags=DOCTEST_FLAGS, *doctest_files)50 globs = {
51 'absolute_import': absolute_import,
52 'print_function': print_function,
53 }
54 suite = doctest.DocFileSuite(
55 *doctest_files, optionflags=DOCTEST_FLAGS, globs=globs)
49 suite.layer = WSGILayer56 suite.layer = WSGILayer
50 tests.addTest(suite)57 tests.addTest(suite)
51 return tests58 return tests
5259
=== modified file 'src/lazr/restful/example/base/tests/wadl.txt'
--- src/lazr/restful/example/base/tests/wadl.txt 2018-09-28 15:42:49 +0000
+++ src/lazr/restful/example/base/tests/wadl.txt 2020-02-04 11:56:15 +0000
@@ -21,7 +21,7 @@
2121
22It's an XML document.22It's an XML document.
2323
24 >>> print wadl24 >>> print(wadl)
25 <?xml version="1.0"?>25 <?xml version="1.0"?>
26 <wadl:application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"26 <wadl:application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
27 xmlns="http://research.sun.com/wadl/2006/10"27 xmlns="http://research.sun.com/wadl/2006/10"
@@ -180,7 +180,7 @@
180A cookbook starts out with no hosted cover, and an ordinary180A cookbook starts out with no hosted cover, and an ordinary
181GET to the place where the cover would be results in a 404 error.181GET to the place where the cover would be results in a 404 error.
182182
183 >>> print webservice.get(entry_url + '/cover')183 >>> print(webservice.get(entry_url + '/cover'))
184 HTTP/1.1 404 Not Found184 HTTP/1.1 404 Not Found
185 ...185 ...
186186
@@ -264,17 +264,17 @@
264 ... if not isinstance(child, _Comment)]264 ... if not isinstance(child, _Comment)]
265265
266 >>> service_doc, version_doc = children[:2]266 >>> service_doc, version_doc = children[:2]
267 >>> print service_doc.attrib['title']267 >>> print(service_doc.attrib['title'])
268 About this service268 About this service
269 >>> for p_tag in service_doc:269 >>> for p_tag in service_doc:
270 ... print p_tag.text270 ... print(p_tag.text)
271 This is a web service.271 This is a web service.
272 It's got resources!272 It's got resources!
273273
274 >>> print version_doc.attrib['title']274 >>> print(version_doc.attrib['title'])
275 About version devel275 About version devel
276 >>> for p_tag in version_doc:276 >>> for p_tag in version_doc:
277 ... print p_tag.text277 ... print(p_tag.text)
278 The unstable development version.278 The unstable development version.
279 Don't use this unless you like changing things.279 Don't use this unless you like changing things.
280280
@@ -397,7 +397,7 @@
397397
398 >>> resources.tag398 >>> resources.tag
399 '...resources'399 '...resources'
400 >>> print resources.attrib['base']400 >>> print(resources.attrib['base'])
401 http://cookbooks.dev/devel/401 http://cookbooks.dev/devel/
402402
403As with the <resources> tags shown earlier, this one contains a single403As with the <resources> tags shown earlier, this one contains a single
@@ -746,7 +746,7 @@
746 >>> doc = copyright_date[0]746 >>> doc = copyright_date[0]
747 >>> doc.tag747 >>> doc.tag
748 '...doc'748 '...doc'
749 >>> print "\n".join([node.text for node in doc])749 >>> print("\n".join([node.text for node in doc]))
750 Copyright Date750 Copyright Date
751 The copyright date for this work.751 The copyright date for this work.
752752
753753
=== modified file 'src/lazr/restful/example/base/traversal.py'
--- src/lazr/restful/example/base/traversal.py 2015-04-08 20:11:29 +0000
+++ src/lazr/restful/example/base/traversal.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Traversal rules for the LAZR example web service."""3"""Traversal rules for the LAZR example web service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'BelowRootAbsoluteURL',9 'BelowRootAbsoluteURL',
810
=== modified file 'src/lazr/restful/example/base_extended/README.txt'
--- src/lazr/restful/example/base_extended/README.txt 2010-08-05 13:12:00 +0000
+++ src/lazr/restful/example/base_extended/README.txt 2020-02-04 11:56:15 +0000
@@ -14,7 +14,7 @@
1414
15And as we can see below, a recipe's representation now include its comments.15And as we can see below, a recipe's representation now include its comments.
1616
17 >>> print "\n".join(webservice.get('/recipes/1').jsonBody()['comments'])17 >>> print("\n".join(webservice.get('/recipes/1').jsonBody()['comments']))
18 Comment 118 Comment 1
19 Comment 219 Comment 2
2020
2121
=== modified file 'src/lazr/restful/example/base_extended/comments.py'
--- src/lazr/restful/example/base_extended/comments.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/example/base_extended/comments.py 2020-02-04 11:56:15 +0000
@@ -1,3 +1,5 @@
1from __future__ import absolute_import, print_function
2
1from zope.component import adapter3from zope.component import adapter
2from zope.interface import implementer, Interface4from zope.interface import implementer, Interface
3from zope.schema import List, Text5from zope.schema import List, Text
46
=== modified file 'src/lazr/restful/example/base_extended/tests/test_integration.py'
--- src/lazr/restful/example/base_extended/tests/test_integration.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/example/base_extended/tests/test_integration.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test harness for LAZR doctests."""3"""Test harness for LAZR doctests."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = []8__all__ = []
79
@@ -33,7 +35,12 @@
33def load_tests(loader, tests, pattern):35def load_tests(loader, tests, pattern):
34 """See `zope.testing.testrunner`."""36 """See `zope.testing.testrunner`."""
35 doctest_files = ['../README.txt']37 doctest_files = ['../README.txt']
36 suite = doctest.DocFileSuite(optionflags=DOCTEST_FLAGS, *doctest_files)38 globs = {
39 'absolute_import': absolute_import,
40 'print_function': print_function,
41 }
42 suite = doctest.DocFileSuite(
43 *doctest_files, optionflags=DOCTEST_FLAGS, globs=globs)
37 suite.layer = WSGILayer44 suite.layer = WSGILayer
38 tests.addTest(suite)45 tests.addTest(suite)
39 return tests46 return tests
4047
=== modified file 'src/lazr/restful/example/multiversion/resources.py'
--- src/lazr/restful/example/multiversion/resources.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/example/multiversion/resources.py 2020-02-04 11:56:15 +0000
@@ -1,3 +1,5 @@
1from __future__ import absolute_import, print_function
2
1__metaclass__ = type3__metaclass__ = type
24
3__all__ = ['IKeyValuePair',5__all__ = ['IKeyValuePair',
46
=== modified file 'src/lazr/restful/example/multiversion/root.py'
--- src/lazr/restful/example/multiversion/root.py 2010-08-18 15:33:46 +0000
+++ src/lazr/restful/example/multiversion/root.py 2020-02-04 11:56:15 +0000
@@ -1,5 +1,7 @@
1"""The RESTful service root."""1"""The RESTful service root."""
22
3from __future__ import absolute_import, print_function
4
3__metaclass__ = type5__metaclass__ = type
4__all__ = [6__all__ = [
5 'BelowRootAbsoluteURL',7 'BelowRootAbsoluteURL',
68
=== modified file 'src/lazr/restful/example/multiversion/tests/introduction.txt'
--- src/lazr/restful/example/multiversion/tests/introduction.txt 2011-02-01 00:57:27 +0000
+++ src/lazr/restful/example/multiversion/tests/introduction.txt 2020-02-04 11:56:15 +0000
@@ -19,22 +19,22 @@
1919
20 >>> top_level_response = webservice.get(20 >>> top_level_response = webservice.get(
21 ... "/", api_version="beta").jsonBody()21 ... "/", api_version="beta").jsonBody()
22 >>> print top_level_response['key_value_pairs_collection_link']22 >>> print(top_level_response['key_value_pairs_collection_link'])
23 http://multiversion.dev/beta/pairs23 http://multiversion.dev/beta/pairs
2424
25 >>> top_level_response = webservice.get(25 >>> top_level_response = webservice.get(
26 ... "/", api_version="1.0").jsonBody()26 ... "/", api_version="1.0").jsonBody()
27 >>> print top_level_response['key_value_pairs_collection_link']27 >>> print(top_level_response['key_value_pairs_collection_link'])
28 http://multiversion.dev/1.0/pairs28 http://multiversion.dev/1.0/pairs
2929
30 >>> top_level_response = webservice.get(30 >>> top_level_response = webservice.get(
31 ... "/", api_version="2.0").jsonBody()31 ... "/", api_version="2.0").jsonBody()
32 >>> print top_level_response['key_value_pairs_collection_link']32 >>> print(top_level_response['key_value_pairs_collection_link'])
33 http://multiversion.dev/2.0/pairs33 http://multiversion.dev/2.0/pairs
3434
35 >>> top_level_response = webservice.get(35 >>> top_level_response = webservice.get(
36 ... "/", api_version="3.0").jsonBody()36 ... "/", api_version="3.0").jsonBody()
37 >>> print top_level_response['key_value_pairs_collection_link']37 >>> print(top_level_response['key_value_pairs_collection_link'])
38 http://multiversion.dev/3.0/pairs38 http://multiversion.dev/3.0/pairs
3939
40Like all web services, the multiversion service also serves a40Like all web services, the multiversion service also serves a
@@ -46,7 +46,7 @@
4646
47 >>> top_level_response = webservice.get(47 >>> top_level_response = webservice.get(
48 ... "/", api_version="trunk").jsonBody()48 ... "/", api_version="trunk").jsonBody()
49 >>> print top_level_response['key_value_pairs_collection_link']49 >>> print(top_level_response['key_value_pairs_collection_link'])
50 http://multiversion.dev/trunk/pairs50 http://multiversion.dev/trunk/pairs
5151
52All versions of the web service can be accessed through Ajax.52All versions of the web service can be accessed through Ajax.
@@ -55,16 +55,16 @@
55 >>> ajax = WebServiceAjaxCaller(domain='multiversion.dev')55 >>> ajax = WebServiceAjaxCaller(domain='multiversion.dev')
5656
57 >>> body = ajax.get('/', api_version="1.0").jsonBody()57 >>> body = ajax.get('/', api_version="1.0").jsonBody()
58 >>> print body['resource_type_link']58 >>> print(body['resource_type_link'])
59 http://multiversion.dev/1.0/#service-root59 http://multiversion.dev/1.0/#service-root
6060
61 >>> body = ajax.get('/', api_version="trunk").jsonBody()61 >>> body = ajax.get('/', api_version="trunk").jsonBody()
62 >>> print body['resource_type_link']62 >>> print(body['resource_type_link'])
63 http://multiversion.dev/trunk/#service-root63 http://multiversion.dev/trunk/#service-root
6464
65An attempt to access a nonexistent version yields a 404 error.65An attempt to access a nonexistent version yields a 404 error.
6666
67 >>> print webservice.get('/', api_version="no_such_version")67 >>> print(webservice.get('/', api_version="no_such_version"))
68 HTTP/1.1 404 Not Found68 HTTP/1.1 404 Not Found
69 ...69 ...
7070
@@ -79,7 +79,7 @@
79 >>> def show_pairs(version):79 >>> def show_pairs(version):
80 ... body = webservice.get('/pairs', api_version=version).jsonBody()80 ... body = webservice.get('/pairs', api_version=version).jsonBody()
81 ... for entry in sorted(body['entries'], key=itemgetter('key')):81 ... for entry in sorted(body['entries'], key=itemgetter('key')):
82 ... print "%s: %s" % (entry['key'], entry['value'])82 ... print("%s: %s" % (entry['key'], entry['value']))
8383
84 >>> show_pairs('beta')84 >>> show_pairs('beta')
85 1: 285 1: 2
@@ -140,13 +140,13 @@
140140
141 >>> get_comment('1.0')141 >>> get_comment('1.0')
142 u''142 u''
143 >>> print change_comment('I changed 1.0', '1.0')143 >>> print(change_comment('I changed 1.0', '1.0'))
144 I changed 1.0 (modified by mutator #1)144 I changed 1.0 (modified by mutator #1)
145145
146 >>> print change_comment('I changed 2.0', '2.0')146 >>> print(change_comment('I changed 2.0', '2.0'))
147 I changed 2.0 (modified by mutator #1)147 I changed 2.0 (modified by mutator #1)
148148
149 >>> print change_comment('I changed 3.0', '3.0')149 >>> print(change_comment('I changed 3.0', '3.0'))
150 I changed 3.0 (modified by mutator #2)150 I changed 3.0 (modified by mutator #2)
151151
152You can try to modify the 'comment' field from a version that doesn't152You can try to modify the 'comment' field from a version that doesn't
@@ -154,7 +154,7 @@
154154
155 >>> change_comment('I changed beta', 'beta', False)155 >>> change_comment('I changed beta', 'beta', False)
156156
157 >>> print get_comment('1.0')157 >>> print(get_comment('1.0'))
158 I changed 3.0 (modified by mutator #2)158 I changed 3.0 (modified by mutator #2)
159159
160A field called 'deleted' is published starting in version '3.0'. A160A field called 'deleted' is published starting in version '3.0'. A
@@ -165,7 +165,7 @@
165 ... entry_body = webservice.get(165 ... entry_body = webservice.get(
166 ... '/pairs/foo', api_version=version).jsonBody()166 ... '/pairs/foo', api_version=version).jsonBody()
167 ... for key in sorted(entry_body.keys()):167 ... for key in sorted(entry_body.keys()):
168 ... print key168 ... print(key)
169169
170 >>> show_fields('beta')170 >>> show_fields('beta')
171 a_comment171 a_comment
@@ -244,11 +244,11 @@
244If an entry field is not published in a certain version, the244If an entry field is not published in a certain version, the
245corresponding field resource does not exist for that version.245corresponding field resource does not exist for that version.
246246
247 >>> print webservice.get('/pairs/foo/deleted', api_version='beta').body247 >>> print(webservice.get('/pairs/foo/deleted', api_version='beta').body)
248 Object: <...>, name: u'deleted'248 Object: <...>, name: u'deleted'
249249
250 >>> print webservice.get(250 >>> print(webservice.get(
251 ... '/pairs/foo/deleted', api_version='3.0').jsonBody()251 ... '/pairs/foo/deleted', api_version='3.0').jsonBody())
252 False252 False
253253
254Named operations254Named operations
@@ -267,12 +267,12 @@
267The named operation is not published at all in the 'beta' version of267The named operation is not published at all in the 'beta' version of
268the web service.268the web service.
269269
270 >>> print show_value("beta", 'byValue')270 >>> print(show_value("beta", 'byValue'))
271 Traceback (most recent call last):271 Traceback (most recent call last):
272 ...272 ...
273 ValueError: No such operation: byValue273 ValueError: No such operation: byValue
274274
275 >>> print show_value("beta", 'by_value')275 >>> print(show_value("beta", 'by_value'))
276 Traceback (most recent call last):276 Traceback (most recent call last):
277 ...277 ...
278 ValueError: No such operation: by_value278 ValueError: No such operation: by_value
@@ -280,12 +280,12 @@
280In the '1.0' and '2.0' versions, the named operation is published as280In the '1.0' and '2.0' versions, the named operation is published as
281'byValue'. 'by_value' does not work.281'byValue'. 'by_value' does not work.
282282
283 >>> print show_value("1.0", 'byValue')283 >>> print(show_value("1.0", 'byValue'))
284 foo284 foo
285285
286 >>> print show_value("2.0", 'byValue')286 >>> print(show_value("2.0", 'byValue'))
287 foo287 foo
288 >>> print show_value("2.0", 'by_value')288 >>> print(show_value("2.0", 'by_value'))
289 Traceback (most recent call last):289 Traceback (most recent call last):
290 ...290 ...
291 ValueError: No such operation: by_value291 ValueError: No such operation: by_value
@@ -293,10 +293,10 @@
293In the '3.0' version, the named operation is published as293In the '3.0' version, the named operation is published as
294'by_value'. 'byValue' does not work.294'by_value'. 'byValue' does not work.
295295
296 >>> print show_value("3.0", "by_value")296 >>> print(show_value("3.0", "by_value"))
297 foo297 foo
298298
299 >>> print show_value("3.0", 'byValue')299 >>> print(show_value("3.0", 'byValue'))
300 Traceback (most recent call last):300 Traceback (most recent call last):
301 ...301 ...
302 ValueError: No such operation: byValue302 ValueError: No such operation: byValue
@@ -304,12 +304,12 @@
304In the 'trunk' version, the named operation has been removed. Neither304In the 'trunk' version, the named operation has been removed. Neither
305'byValue' nor 'by_value' work.305'byValue' nor 'by_value' work.
306306
307 >>> print show_value("trunk", 'byValue')307 >>> print(show_value("trunk", 'byValue'))
308 Traceback (most recent call last):308 Traceback (most recent call last):
309 ...309 ...
310 ValueError: No such operation: byValue310 ValueError: No such operation: byValue
311311
312 >>> print show_value("trunk", 'by_value')312 >>> print(show_value("trunk", 'by_value'))
313 Traceback (most recent call last):313 Traceback (most recent call last):
314 ...314 ...
315 ValueError: No such operation: by_value315 ValueError: No such operation: by_value
316316
=== modified file 'src/lazr/restful/example/multiversion/tests/operation.txt'
--- src/lazr/restful/example/multiversion/tests/operation.txt 2018-09-28 15:39:53 +0000
+++ src/lazr/restful/example/multiversion/tests/operation.txt 2020-02-04 11:56:15 +0000
@@ -21,7 +21,7 @@
21 >>> from zope.component import getUtility21 >>> from zope.component import getUtility
22 >>> from lazr.restful.interfaces import IWebServiceConfiguration22 >>> from lazr.restful.interfaces import IWebServiceConfiguration
23 >>> config = getUtility(IWebServiceConfiguration)23 >>> config = getUtility(IWebServiceConfiguration)
24 >>> print config.first_version_with_total_size_link24 >>> print(config.first_version_with_total_size_link)
25 2.025 2.0
2626
27When the 'byValue' operation is invoked in version 1.0, it always returns a27When the 'byValue' operation is invoked in version 1.0, it always returns a
@@ -31,30 +31,30 @@
31 ... url = '/pairs?ws.op=%s&value=%s&ws.size=%s' % (op, value, size)31 ... url = '/pairs?ws.op=%s&value=%s&ws.size=%s' % (op, value, size)
32 ... return webservice.get(url, api_version=version).jsonBody()32 ... return webservice.get(url, api_version=version).jsonBody()
3333
34 >>> print sorted(get_collection('1.0').keys())34 >>> print(sorted(get_collection('1.0').keys()))
35 [u'entries', u'next_collection_link', u'start', u'total_size']35 [u'entries', u'next_collection_link', u'start', u'total_size']
3636
37The operation itself doesn't change between 1.0 and 2.0, but in37The operation itself doesn't change between 1.0 and 2.0, but in
38version 2.0, the operation starts returning total_size_link.38version 2.0, the operation starts returning total_size_link.
3939
40 >>> print sorted(get_collection('2.0').keys())40 >>> print(sorted(get_collection('2.0').keys()))
41 [u'entries', u'next_collection_link', u'start', u'total_size_link']41 [u'entries', u'next_collection_link', u'start', u'total_size_link']
4242
43The same happens in 3.0.43The same happens in 3.0.
4444
45 >>> print sorted(get_collection('3.0', 'by_value').keys())45 >>> print(sorted(get_collection('3.0', 'by_value').keys()))
46 [u'entries', u'next_collection_link', u'start', u'total_size_link']46 [u'entries', u'next_collection_link', u'start', u'total_size_link']
4747
48However, if the total size is easy to calculate (for instance, because48However, if the total size is easy to calculate (for instance, because
49all the results fit on one page), a total_size is returned instead of49all the results fit on one page), a total_size is returned instead of
50total_size_link.50total_size_link.
5151
52 >>> print sorted(get_collection('3.0', 'by_value', size=100).keys())52 >>> print(sorted(get_collection('3.0', 'by_value', size=100).keys()))
53 [u'entries', u'start', u'total_size']53 [u'entries', u'start', u'total_size']
5454
55 >>> for key, value in sorted(55 >>> for key, value in sorted(
56 ... get_collection('3.0', 'by_value', 'no-such-value').items()):56 ... get_collection('3.0', 'by_value', 'no-such-value').items()):
57 ... print '%s: %s' % (key, value)57 ... print('%s: %s' % (key, value))
58 entries: []58 entries: []
59 start: 059 start: 0
60 total_size: 060 total_size: 0
@@ -69,7 +69,7 @@
69In the example web service, mutator methods are published as named69In the example web service, mutator methods are published as named
70operations in the 'beta' and '1.0' versions.70operations in the 'beta' and '1.0' versions.
7171
72 >>> print config.last_version_with_mutator_named_operations72 >>> print(config.last_version_with_mutator_named_operations)
73 1.073 1.0
7474
75If you look at the definition of IKeyValuePair in75If you look at the definition of IKeyValuePair in
@@ -93,11 +93,11 @@
9393
94In version 'beta', the 'a_comment' field has no mutator at all.94In version 'beta', the 'a_comment' field has no mutator at all.
9595
96 >>> print call_mutator("beta")96 >>> print(call_mutator("beta"))
97 HTTP/1.1 405 Method Not Allowed97 HTTP/1.1 405 Method Not Allowed
98 ...98 ...
9999
100 >>> print call_mutator("beta", op="comment_mutator_2")100 >>> print(call_mutator("beta", op="comment_mutator_2"))
101 HTTP/1.1 405 Method Not Allowed101 HTTP/1.1 405 Method Not Allowed
102 ...102 ...
103103
@@ -105,14 +105,14 @@
105'a_comment'. Because mutators are published as named operation in105'a_comment'. Because mutators are published as named operation in
106version '1.0', we can also invoke the mutator method directly.106version '1.0', we can also invoke the mutator method directly.
107107
108 >>> print call_mutator("1.0", "comment_mutator_1")108 >>> print(call_mutator("1.0", "comment_mutator_1"))
109 HTTP/1.1 200 Ok109 HTTP/1.1 200 Ok
110 ...110 ...
111111
112'comment_mutator_2' still doesn't work, because it's not the mutator112'comment_mutator_2' still doesn't work, because it's not the mutator
113for 'a_comment' in version '1.0'.113for 'a_comment' in version '1.0'.
114114
115 >>> print call_mutator("1.0", "comment_mutator_2")115 >>> print(call_mutator("1.0", "comment_mutator_2"))
116 HTTP/1.1 400 Bad Request116 HTTP/1.1 400 Bad Request
117 ...117 ...
118118
@@ -125,7 +125,7 @@
125for 'a_comment', but mutators are no longer published as named125for 'a_comment', but mutators are no longer published as named
126operations, so we can no longer invoke the mutator method directly.126operations, so we can no longer invoke the mutator method directly.
127127
128 >>> print call_mutator("2.0")128 >>> print(call_mutator("2.0"))
129 HTTP/1.1 405 Method Not Allowed129 HTTP/1.1 405 Method Not Allowed
130 ...130 ...
131131
@@ -137,6 +137,6 @@
137for 'a_comment'. But since version '3.0' is after version '1.0', that137for 'a_comment'. But since version '3.0' is after version '1.0', that
138method is not published as a named operation.138method is not published as a named operation.
139139
140 >>> print call_mutator("3.0", "comment_mutator_2")140 >>> print(call_mutator("3.0", "comment_mutator_2"))
141 HTTP/1.1 405 Method Not Allowed141 HTTP/1.1 405 Method Not Allowed
142 ...142 ...
143143
=== modified file 'src/lazr/restful/example/multiversion/tests/test_integration.py'
--- src/lazr/restful/example/multiversion/tests/test_integration.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/example/multiversion/tests/test_integration.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test harness for doctests for lazr.restful multiversion example service."""3"""Test harness for doctests for lazr.restful multiversion example service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = []8__all__ = []
79
@@ -48,7 +50,12 @@
48 [name50 [name
49 for name in os.listdir(os.path.dirname(__file__))51 for name in os.listdir(os.path.dirname(__file__))
50 if name.endswith('.txt')])52 if name.endswith('.txt')])
51 suite = doctest.DocFileSuite(optionflags=DOCTEST_FLAGS, *doctest_files)53 globs = {
54 'absolute_import': absolute_import,
55 'print_function': print_function,
56 }
57 suite = doctest.DocFileSuite(
58 *doctest_files, optionflags=DOCTEST_FLAGS, globs=globs)
52 suite.layer = WSGILayer59 suite.layer = WSGILayer
53 tests.addTest(suite)60 tests.addTest(suite)
54 return tests61 return tests
5562
=== modified file 'src/lazr/restful/example/multiversion/tests/wadl.txt'
--- src/lazr/restful/example/multiversion/tests/wadl.txt 2010-08-05 14:08:20 +0000
+++ src/lazr/restful/example/multiversion/tests/wadl.txt 2020-02-04 11:56:15 +0000
@@ -40,10 +40,10 @@
40is not present at all.40is not present at all.
4141
42 >>> contents = wadl_contents_for_version('beta')42 >>> contents = wadl_contents_for_version('beta')
43 >>> print contents['version_doc'].attrib['title']43 >>> print(contents['version_doc'].attrib['title'])
44 About version beta44 About version beta
4545
46 >>> print contents['base'].attrib['base']46 >>> print(contents['base'].attrib['base'])
47 http://multiversion.dev/beta/47 http://multiversion.dev/beta/
4848
49 >>> pair_collection = contents['pair_collection']49 >>> pair_collection = contents['pair_collection']
@@ -62,9 +62,9 @@
62In '2.0', the by_value method is called 'byValue'.62In '2.0', the by_value method is called 'byValue'.
6363
64 >>> contents = wadl_contents_for_version('2.0')64 >>> contents = wadl_contents_for_version('2.0')
65 >>> print contents['version_doc'].attrib['title']65 >>> print(contents['version_doc'].attrib['title'])
66 About version 2.066 About version 2.0
67 >>> print contents['base'].attrib['base']67 >>> print(contents['base'].attrib['base'])
68 http://multiversion.dev/2.0/68 http://multiversion.dev/2.0/
6969
70 >>> pair_collection = contents['pair_collection']70 >>> pair_collection = contents['pair_collection']
@@ -74,9 +74,9 @@
74In '3.0', the method changes its name to 'by_value'.74In '3.0', the method changes its name to 'by_value'.
7575
76 >>> contents = wadl_contents_for_version('3.0')76 >>> contents = wadl_contents_for_version('3.0')
77 >>> print contents['version_doc'].attrib['title']77 >>> print(contents['version_doc'].attrib['title'])
78 About version 3.078 About version 3.0
79 >>> print contents['base'].attrib['base']79 >>> print(contents['base'].attrib['base'])
80 http://multiversion.dev/3.0/80 http://multiversion.dev/3.0/
8181
82 >>> pair_collection = contents['pair_collection']82 >>> pair_collection = contents['pair_collection']
@@ -86,7 +86,7 @@
86In 'trunk', the method disappears.86In 'trunk', the method disappears.
8787
88 >>> contents = wadl_contents_for_version('trunk')88 >>> contents = wadl_contents_for_version('trunk')
89 >>> print contents['base'].attrib['base']89 >>> print(contents['base'].attrib['base'])
90 http://multiversion.dev/trunk/90 http://multiversion.dev/trunk/
9191
92 >>> pair_collection = contents['pair_collection']92 >>> pair_collection = contents['pair_collection']
9393
=== modified file 'src/lazr/restful/example/wsgi/resources.py'
--- src/lazr/restful/example/wsgi/resources.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/example/wsgi/resources.py 2020-02-04 11:56:15 +0000
@@ -1,3 +1,5 @@
1from __future__ import absolute_import, print_function
2
1__metaclass__ = type3__metaclass__ = type
24
3__all__ = ['IKeyValuePair',5__all__ = ['IKeyValuePair',
46
=== modified file 'src/lazr/restful/example/wsgi/root.py'
--- src/lazr/restful/example/wsgi/root.py 2010-02-25 17:07:16 +0000
+++ src/lazr/restful/example/wsgi/root.py 2020-02-04 11:56:15 +0000
@@ -1,5 +1,7 @@
1"""The RESTful service root."""1"""The RESTful service root."""
22
3from __future__ import absolute_import, print_function
4
3__metaclass__ = type5__metaclass__ = type
4__all__ = [6__all__ = [
5 'BelowRootAbsoluteURL',7 'BelowRootAbsoluteURL',
68
=== modified file 'src/lazr/restful/example/wsgi/run.py'
--- src/lazr/restful/example/wsgi/run.py 2009-09-03 19:42:04 +0000
+++ src/lazr/restful/example/wsgi/run.py 2020-02-04 11:56:15 +0000
@@ -1,4 +1,7 @@
1#!/usr/bin/python1#!/usr/bin/python
2
3from __future__ import absolute_import, print_function
4
2from lazr.restful.wsgi import WSGIApplication5from lazr.restful.wsgi import WSGIApplication
36
4if __name__ == '__main__':7if __name__ == '__main__':
58
=== modified file 'src/lazr/restful/example/wsgi/tests/introduction.txt'
--- src/lazr/restful/example/wsgi/tests/introduction.txt 2011-01-21 18:09:15 +0000
+++ src/lazr/restful/example/wsgi/tests/introduction.txt 2020-02-04 11:56:15 +0000
@@ -19,7 +19,7 @@
19 >>> links = sorted([entry['self_link']19 >>> links = sorted([entry['self_link']
20 ... for entry in collection_resource['entries']])20 ... for entry in collection_resource['entries']])
21 >>> for link in links:21 >>> for link in links:
22 ... print link22 ... print(link)
23 http://wsgidemo.dev/1.0/123 http://wsgidemo.dev/1.0/1
24 http://wsgidemo.dev/1.0/foo24 http://wsgidemo.dev/1.0/foo
2525
@@ -32,5 +32,5 @@
3232
33You can get a field resource.33You can get a field resource.
3434
35 >>> print webservice.get("/pairs/foo/value").jsonBody()35 >>> print(webservice.get("/pairs/foo/value").jsonBody())
36 bar36 bar
3737
=== modified file 'src/lazr/restful/example/wsgi/tests/test_integration.py'
--- src/lazr/restful/example/wsgi/tests/test_integration.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/example/wsgi/tests/test_integration.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test harness for doctests for lazr.restful example WSGI service."""3"""Test harness for doctests for lazr.restful example WSGI service."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = []8__all__ = []
79
@@ -45,7 +47,12 @@
45 [name47 [name
46 for name in os.listdir(os.path.dirname(__file__))48 for name in os.listdir(os.path.dirname(__file__))
47 if name.endswith('.txt')])49 if name.endswith('.txt')])
48 suite = doctest.DocFileSuite(optionflags=DOCTEST_FLAGS, *doctest_files)50 globs = {
51 'absolute_import': absolute_import,
52 'print_function': print_function,
53 }
54 suite = doctest.DocFileSuite(
55 *doctest_files, optionflags=DOCTEST_FLAGS, globs=globs)
49 suite.layer = WSGILayer56 suite.layer = WSGILayer
50 tests.addTest(suite)57 tests.addTest(suite)
51 return tests58 return tests
5259
=== modified file 'src/lazr/restful/fields.py'
--- src/lazr/restful/fields.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/fields.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""LAZR zope.schema.IField implementation."""3"""LAZR zope.schema.IField implementation."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'CollectionField',9 'CollectionField',
810
=== modified file 'src/lazr/restful/interface.py'
--- src/lazr/restful/interface.py 2009-03-26 17:25:22 +0000
+++ src/lazr/restful/interface.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Helpers for working with Zope interface."""3"""Helpers for working with Zope interface."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'copy_attribute',9 'copy_attribute',
810
=== modified file 'src/lazr/restful/interfaces/__init__.py'
--- src/lazr/restful/interfaces/__init__.py 2009-03-26 17:25:22 +0000
+++ src/lazr/restful/interfaces/__init__.py 2020-02-04 11:56:15 +0000
@@ -17,6 +17,8 @@
1717
18# pylint: disable-msg=W040118# pylint: disable-msg=W0401
1919
20from __future__ import absolute_import, print_function
21
20__version__ = 1.022__version__ = 1.0
2123
22# Re-export in such a way that __version__ can still be imported if24# Re-export in such a way that __version__ can still be imported if
2325
=== modified file 'src/lazr/restful/interfaces/_fields.py'
--- src/lazr/restful/interfaces/_fields.py 2011-01-19 22:29:32 +0000
+++ src/lazr/restful/interfaces/_fields.py 2020-02-04 11:56:15 +0000
@@ -16,6 +16,8 @@
1616
17"""Interfaces for LAZR zope.schema fields."""17"""Interfaces for LAZR zope.schema fields."""
1818
19from __future__ import absolute_import, print_function
20
19__metaclass__ = type21__metaclass__ = type
20__all__ = [22__all__ = [
21 'ICollectionField',23 'ICollectionField',
2224
=== modified file 'src/lazr/restful/interfaces/_rest.py'
--- src/lazr/restful/interfaces/_rest.py 2011-03-31 01:13:59 +0000
+++ src/lazr/restful/interfaces/_rest.py 2020-02-04 11:56:15 +0000
@@ -18,6 +18,8 @@
18# Pylint doesn't grok zope interfaces.18# Pylint doesn't grok zope interfaces.
19# pylint: disable-msg=E0211,E021319# pylint: disable-msg=E0211,E0213
2020
21from __future__ import absolute_import, print_function
22
21__metaclass__ = type23__metaclass__ = type
22__all__ = [24__all__ = [
23 'IByteStorage',25 'IByteStorage',
2426
=== modified file 'src/lazr/restful/jsoncache.py'
--- src/lazr/restful/jsoncache.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/jsoncache.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
2#2#
3"""A class for storing resources where they can be seen by a template."""3"""A class for storing resources where they can be seen by a template."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7__all__ = [9__all__ = [
810
=== modified file 'src/lazr/restful/marshallers.py'
--- src/lazr/restful/marshallers.py 2018-11-15 15:29:42 +0000
+++ src/lazr/restful/marshallers.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Marshallers for fields used in HTTP resources."""3"""Marshallers for fields used in HTTP resources."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'AbstractCollectionFieldMarshaller',9 'AbstractCollectionFieldMarshaller',
810
=== modified file 'src/lazr/restful/metazcml.py'
--- src/lazr/restful/metazcml.py 2019-11-04 13:51:46 +0000
+++ src/lazr/restful/metazcml.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""ZCML registration directives for the LAZR webservice framework."""3"""ZCML registration directives for the LAZR webservice framework."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = []8__all__ = []
79
810
=== modified file 'src/lazr/restful/publisher.py'
--- src/lazr/restful/publisher.py 2019-11-04 17:35:37 +0000
+++ src/lazr/restful/publisher.py 2020-02-04 11:56:15 +0000
@@ -6,6 +6,8 @@
6with the Zope publisher.6with the Zope publisher.
7"""7"""
88
9from __future__ import absolute_import, print_function
10
9__metaclass__ = type11__metaclass__ = type
10__all__ = [12__all__ = [
11 'browser_request_to_web_service_request',13 'browser_request_to_web_service_request',
1214
=== modified file 'src/lazr/restful/security.py'
--- src/lazr/restful/security.py 2009-03-26 17:25:22 +0000
+++ src/lazr/restful/security.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Utilities for dealing with zope security."""3"""Utilities for dealing with zope security."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'protect_schema',9 'protect_schema',
810
=== modified file 'src/lazr/restful/simple.py'
--- src/lazr/restful/simple.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/simple.py 2020-02-04 11:56:15 +0000
@@ -1,5 +1,7 @@
1"""Simple implementations of various Zope and lazr.restful interfaces."""1"""Simple implementations of various Zope and lazr.restful interfaces."""
22
3from __future__ import absolute_import, print_function
4
3__metaclass__ = type5__metaclass__ = type
4__all__ = [6__all__ = [
5 'BaseRepresentationCache',7 'BaseRepresentationCache',
68
=== modified file 'src/lazr/restful/tales.py'
--- src/lazr/restful/tales.py 2018-02-21 14:46:13 +0000
+++ src/lazr/restful/tales.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
2#2#
3"""Implementation of the ws: namespace in TALES."""3"""Implementation of the ws: namespace in TALES."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7all = ['entry_adapter_for_schema']9all = ['entry_adapter_for_schema']
810
=== modified file 'src/lazr/restful/testing/event.py'
--- src/lazr/restful/testing/event.py 2009-04-02 15:44:00 +0000
+++ src/lazr/restful/testing/event.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Helper class for checking the event notifications."""3"""Helper class for checking the event notifications."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7import zope.component9import zope.component
810
=== modified file 'src/lazr/restful/testing/helpers.py'
--- src/lazr/restful/testing/helpers.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/testing/helpers.py 2020-02-04 11:56:15 +0000
@@ -1,3 +1,5 @@
1from __future__ import absolute_import, print_function
2
1import sys3import sys
2from types import ModuleType4from types import ModuleType
35
46
=== modified file 'src/lazr/restful/testing/tales.py'
--- src/lazr/restful/testing/tales.py 2019-11-12 14:26:41 +0000
+++ src/lazr/restful/testing/tales.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Helper functions for testing TALES expressions."""3"""Helper functions for testing TALES expressions."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7__all__ = [9__all__ = [
810
=== modified file 'src/lazr/restful/testing/webservice.py'
--- src/lazr/restful/testing/webservice.py 2016-02-17 01:07:21 +0000
+++ src/lazr/restful/testing/webservice.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Testing helpers for webservice unit tests."""3"""Testing helpers for webservice unit tests."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'create_web_service_request',9 'create_web_service_request',
@@ -176,7 +178,7 @@
176 """178 """
177 for key, value in sorted(json_body.items()):179 for key, value in sorted(json_body.items()):
178 if key != 'http_etag':180 if key != 'http_etag':
179 print '%s: %r' % (key, value)181 print('%s: %r' % (key, value))
180182
181183
182def pprint_collection(json_body):184def pprint_collection(json_body):
@@ -185,11 +187,11 @@
185 if key == 'total_size_link':187 if key == 'total_size_link':
186 continue188 continue
187 if key != 'entries':189 if key != 'entries':
188 print '%s: %r' % (key, value)190 print('%s: %r' % (key, value))
189 print '---'191 print('---')
190 for entry in json_body['entries']:192 for entry in json_body['entries']:
191 pprint_entry(entry)193 pprint_entry(entry)
192 print '---'194 print('---')
193195
194196
195class WebServiceTestPublication(Publication):197class WebServiceTestPublication(Publication):
196198
=== modified file 'src/lazr/restful/tests/test_declarations.py'
--- src/lazr/restful/tests/test_declarations.py 2019-11-04 17:35:37 +0000
+++ src/lazr/restful/tests/test_declarations.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Unit tests for the conversion of interfaces into a web service."""3"""Unit tests for the conversion of interfaces into a web service."""
44
5from __future__ import absolute_import, print_function
6
5import testtools7import testtools
6from zope.component import (8from zope.component import (
7 adapter,9 adapter,
810
=== modified file 'src/lazr/restful/tests/test_docs.py'
--- src/lazr/restful/tests/test_docs.py 2019-11-04 09:54:43 +0000
+++ src/lazr/restful/tests/test_docs.py 2020-02-04 11:56:15 +0000
@@ -17,6 +17,8 @@
1717
18# pylint: disable=E0611,W014218# pylint: disable=E0611,W0142
1919
20from __future__ import absolute_import, print_function
21
20__metaclass__ = type22__metaclass__ = type
21__all__ = []23__all__ = []
2224
@@ -50,7 +52,11 @@
50 os.path.abspath(52 os.path.abspath(
51 resource_filename('lazr.restful', 'docs/%s' % name)))53 resource_filename('lazr.restful', 'docs/%s' % name)))
52 atexit.register(cleanup_resources)54 atexit.register(cleanup_resources)
55 globs = {
56 'absolute_import': absolute_import,
57 'print_function': print_function,
58 }
53 tests.addTest(doctest.DocFileSuite(59 tests.addTest(doctest.DocFileSuite(
54 *doctest_files, module_relative=False, optionflags=DOCTEST_FLAGS,60 *doctest_files, module_relative=False, optionflags=DOCTEST_FLAGS,
55 tearDown=tearDown))61 tearDown=tearDown, globs=globs))
56 return tests62 return tests
5763
=== modified file 'src/lazr/restful/tests/test_error.py'
--- src/lazr/restful/tests/test_error.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/tests/test_error.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Tests of lazr.restful navigation."""3"""Tests of lazr.restful navigation."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7from pkg_resources import resource_filename9from pkg_resources import resource_filename
810
=== modified file 'src/lazr/restful/tests/test_etag.py'
--- src/lazr/restful/tests/test_etag.py 2016-02-17 00:49:58 +0000
+++ src/lazr/restful/tests/test_etag.py 2020-02-04 11:56:15 +0000
@@ -1,6 +1,8 @@
1# Copyright 2008 Canonical Ltd. All rights reserved.1# Copyright 2008 Canonical Ltd. All rights reserved.
2"""Tests for ETag generation."""2"""Tests for ETag generation."""
33
4from __future__ import absolute_import, print_function
5
4__metaclass__ = type6__metaclass__ = type
57
6import unittest8import unittest
79
=== modified file 'src/lazr/restful/tests/test_navigation.py'
--- src/lazr/restful/tests/test_navigation.py 2018-09-28 15:34:25 +0000
+++ src/lazr/restful/tests/test_navigation.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Tests of lazr.restful navigation."""3"""Tests of lazr.restful navigation."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7import unittest9import unittest
810
=== modified file 'src/lazr/restful/tests/test_utils.py'
--- src/lazr/restful/tests/test_utils.py 2011-03-17 13:45:53 +0000
+++ src/lazr/restful/tests/test_utils.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test for lazr.restful.utils."""3"""Test for lazr.restful.utils."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7import random9import random
810
=== modified file 'src/lazr/restful/tests/test_webservice.py'
--- src/lazr/restful/tests/test_webservice.py 2018-09-28 15:46:57 +0000
+++ src/lazr/restful/tests/test_webservice.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Test for the WADL generation."""3"""Test for the WADL generation."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
68
7from contextlib import contextmanager9from contextlib import contextmanager
810
=== modified file 'src/lazr/restful/utils.py'
--- src/lazr/restful/utils.py 2011-03-21 14:00:50 +0000
+++ src/lazr/restful/utils.py 2020-02-04 11:56:15 +0000
@@ -2,6 +2,8 @@
22
3"""Various utility functions."""3"""Various utility functions."""
44
5from __future__ import absolute_import, print_function
6
5__metaclass__ = type7__metaclass__ = type
6__all__ = [8__all__ = [
7 'camelcase_to_underscore_separated',9 'camelcase_to_underscore_separated',
810
=== modified file 'src/lazr/restful/wsgi.py'
--- src/lazr/restful/wsgi.py 2015-04-08 20:11:29 +0000
+++ src/lazr/restful/wsgi.py 2020-02-04 11:56:15 +0000
@@ -1,5 +1,7 @@
1"""A WSGI application for a lazr.restful web service."""1"""A WSGI application for a lazr.restful web service."""
22
3from __future__ import absolute_import, print_function
4
3__metaclass__ = type5__metaclass__ = type
4__all__ = [6__all__ = [
5 'BaseWSGIWebServiceConfiguration',7 'BaseWSGIWebServiceConfiguration',

Subscribers

People subscribed via source and target branches