Package: z3c.rest
This package provides a framework to build REST APIs on top of Zope 3.Detailed DocumentationA Framework for Building RESTive Services in Zope 3This package implements several components that relate to building RESTive Web
services using the Zope publisher. Each set of components is documented in a
corresponding text file.client.txt[must read]This package also provides a REST Web client, which can be used for testing
or for accessing a RESTive API within an application.null.txt[advanced user]In order to create new resources, the publisher must be able to traverse to
resources/objects that do not yet exist. This file explains how those null
resources work.traverser.txt[advanced user]Thetraversermodule contains several traversal helper components for
common traversal scenarios, suhc as containers and null resources.rest.txt[informative]This document introduces the hooks required to manage RESTive requests in
the publisher. It also discusses hwo those components are used by the
publisher.REST ClientThe REST client provides a simple Python API to interact easily with RESTive
Web services. It was designed to have a similar API to Zope’s test
browser.Let’s start by instantiating the the client. Of course we have a version of
the client that talks directly to the Zope publisher:>>> from z3c.rest import testing
>>> client = testing.RESTClient()For testing purposes, we have defined a simple REST API for folders. The
simplest call is to retrieve the contents of the root folder:>>> client.open('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>You can also instantiate the client providing a URL:>>> client = testing.RESTClient('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>HTTPS URLs are also supported>>> client = testing.RESTClient('https://localhost/')
Using SSL
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>Getting ResourcesTheopen()method implicitely uses the “GET” HTTP method. An alternative
would be to use this:>>> client.get('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>There are several other pieces of information of the response that are
available:>>> client.url
'http://localhost/'
>>> client.status
200
>>> client.reason
'Ok'
>>> client.fullStatus
'200 Ok'
>>> client.headers
[('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'),
 ('Content-Length', '204'),
 ('Content-Type', 'text/xml;charset=utf-8')]If we try to access a non-existent resource, no exception is raised, but the
status is ‘404’ (not found) of course:>>> client.get('http://localhost/unknown')
>>> client.fullStatus
'404 Not Found'
>>> client.contents
''
>>> client.headers
[('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'),
 ('Content-Length', '0')]As in the original test browser, I can turn off the Zope error handling and
the Python exception will propagate through the publisher:>>> client.handleErrors = False
>>> client.get('http://localhost/unknown')
Traceback (most recent call last):
...
NotFound: Object: <zope.site.folder.Folder ...>, name: u'unknown'>>> client.handleErrors = TrueAs RESTive APIs often use query string key-value pairs to parameterize the
request, this REST client has strong support for it. For example, you can
simply specify the parameters in the URL:>>> client.get('http://localhost/?noitems=1')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>You can also specify the parameter via an argument:>>> client.get('http://localhost/', params={'noitems': 1})
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>You can even combine the two methods of specifying parameters:>>> client.get('http://localhost/?noitems=1', params={'notitle': 1})
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>But our little demo API can do more. Parameters can also be specified as a
header with a special prefix. Headers can be globally specified and are then
used for every request:>>> client.requestHeaders['demo-noitems'] = 'on'
>>> client.get('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>There is also a headers argument to the “open” methods that specify the header
once:>>> client.get('http://localhost/', headers={'demo-notitle': 1})
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>>>> del client.requestHeaders['demo-noitems']Finally, when dealing with a real site, a socket error might occur. The error
is propagated, but the error number and message are recorded:>>> from z3c.rest.client import RESTClient
>>> realClient = RESTClient()
>>> realClient.open('http://localhost:65000')
Traceback (most recent call last):
...
error: (61, 'Connection refused')>>> realClient.fullStatus
'61 Connection refused'Creating new resourcesLet’s now create a new resource in the server root. Our little sample
application will simply create another collection:>>> client.put(
...     'http://localhost/folder1',
...     '''<?xml version="1.0" ?>
...        <folder />''')>>> client.fullStatus
'401 Unauthorized'Accessing the folder resource is available to everyone. But if you want to
modify any resource, you have to log in:>>> client.setCredentials('globalmgr', 'globalmgrpw')So let’s try this again:>>> client.put(
...     'http://localhost/folder1',
...     '''<?xml version="1.0" ?>
...        <folder />''')>>> client.fullStatus
'201 Created'
>>> client.headers
[('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'),
 ('Content-Length', '0'),
 ('Location', 'http://localhost/folder1')]We can now look at the root container and see the item there:>>> client.get('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder1"
          xlink:title="folder1"/>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>By the way, you can now use a relative URL to access thefolder1resource:>>> client.get('folder1')>>> client.url
'http://localhost/folder1'>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name>folder1</name>
  <title></title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>When we try to create a resource on top of a non-existent resource, we get a
404 error:>>> client.put(
...     'http://localhost/folder2/folder21',
...     '''<?xml version="1.0" ?>
...        <folder />''')>>> client.fullStatus
'404 Not Found'Modifying ResourcesModifying a given resource can be done via POST or PUT, but they have different
semantics. Let’s have a look at POST first. We would now like to change the
title of the folder; this can be done as follows:>>> client.post(
...     'http://localhost/folder1',
...     '''<?xml version="1.0" ?>
...        <folder>
...          <title>My Folder 1</title>
...        </folder>''')>>> client.fullStatus
'200 Ok'>>> client.get()
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name>folder1</name>
  <title>My Folder 1</title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>As mentioned above, it must also work for PUT:>>> client.put(
...     'http://localhost/folder1',
...     '''<?xml version="1.0" ?>
...        <folder>
...          <title>Folder 1</title>
...        </folder>''')>>> client.fullStatus
'200 Ok'>>> client.get()
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name>folder1</name>
  <title>Folder 1</title>
  <items>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>Deleting ResourcesDeleting a resource is as simple as all of the other methods. Let’s delete ourfolder1:>>> client.delete('http://localhost/folder1')>>> client.fullStatus
'200 Ok'So the resource is really gone:>>> client.get()
>>> client.fullStatus
'404 Not Found'It should not be possible to delete a non-existing resource:>>> client.delete('http://localhost/folder2')
>>> client.fullStatus
'404 Not Found'Also, we cannot delete the root folder:>>> client.delete('http://localhost/')
>>> client.fullStatus
'405 Method Not Allowed'Searching the Response DataWhile not required, most REST services are XML-based. Thus, the client
supports inspecting the result XML using XPath. Let’s create a couple of
folders for this to be more interesting:>>> client.put(
...     'http://localhost/folder1',
...     '''<?xml version="1.0" ?>
...        <folder />''')>>> client.put(
...     'http://localhost/folder2',
...     '''<?xml version="1.0" ?>
...        <folder />''')Next we get the root folder resource:>>> client.get('http://localhost/')
>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder1"
          xlink:title="folder1"/>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder2"
          xlink:title="folder2"/>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>But in general, inspecting the XML output on the string level is tedious. So
let’s write a cool XPath expression that extracts the xlink title of all
items:>>> nsmap = {'xlink': "http://www.w3.org/1999/xlink"}
>>> client.xpath('//folder/items/item/@xlink:title', nsmap)
['folder1', 'folder2']Oftentimes, however, we specifically query for one result. In those cases we
do not want to receive a list:>>> client.xpath('//folder/items/item[@xlink:title="folder1"]', nsmap, True)
<Element item ...>Now, if multiple matches are detected, even though we only expect one, then aValueErroris raised:>>> client.xpath('//folder/items/item', nsmap, True)
Traceback (most recent call last):
...
ValueError: XPath expression returned more than one result.Accessing LinksSince we want the REST client to behave like a browser – at least a little
bit – we can also use thegetLink()method to access links:>>> client.getLink('folder1')
<XLink title='folder1' url='http://localhost/folder1'>By default, the link is looked up by title. But you can also look it up by
URL:>>> client.getLink(url='http://localhost/folder1')
<XLink title='folder1' url='http://localhost/folder1'>If you forget to specify a title or URL, you receive aValueError:>>> client.getLink()
Traceback (most recent call last):
...
ValueError: You must specify a title or URL.Links can also be relative, such as the one for ACL:>>> client.open('http://localhost/folder1')
>>> client.getLink('ACL')
<XLink title='ACL' url='http://localhost/folder1/acl'>>>> client.open('http://localhost/folder1/')
>>> client.getLink('ACL')
<XLink title='ACL' url='http://localhost/folder1/acl'>The cool part about the link is that you can click it:>>> client.open('http://localhost/')
>>> client.url
'http://localhost/'>>> client.getLink('folder1').click()>>> client.url
'http://localhost/folder1'Moving through timeLike in a real browser, you can go back to a previous state. For example,
currently we are looking atfolder1, …>>> client.url
'http://localhost/folder1'but if I go back one step, I am back at the root folder:>>> client.goBack()>>> client.url
'http://localhost/'>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder1"
          xlink:title="folder1"/>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder2"
          xlink:title="folder2"/>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>But going back in history is only cool, if you can also reload. So let’s
deletefolder2:>>> client.getLink('folder2').click()
>>> client.delete()Now we go back 2 steps:>>> client.goBack(2)>>> client.url
'http://localhost/'>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder1"
          xlink:title="folder1"/>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder2"
          xlink:title="folder2"/>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>As expected, the contents has not changed yet. So let’s reload:>>> client.reload()>>> client.url
'http://localhost/'>>> print client.contents
<?xml version="1.0" ?>
<folder xmlns:xlink="http://www.w3.org/1999/xlink">
  <name></name>
  <title></title>
  <items>
    <item xlink:type="simple"
          xlink:href="http://localhost/folder1"
          xlink:title="folder1"/>
  </items>
  <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/>
</folder>Note that going back zero steps does nothing:>>> client.url
'http://localhost/'>>> client.getLink('folder1').click()
>>> client.goBack(0)>>> client.url
'http://localhost/folder1'Also, if you try to go back beyond the beginning of time, a value error is
raised:>>> client.goBack(1000)
Traceback (most recent call last):
...
ValueError: There is not enough history.Absolute URLsAs mentioned above, we allow specifying relative URLs, a call with an absolute
URL has been made. A function calledabsoluteURL()is used to compute the
new absolute URL.>>> from z3c.rest.client import absoluteURLThe basic functionality is simple:>>> absoluteURL('http://localhost/folder1/', 'folder11')
'http://localhost/folder1/folder11'It also detects, if the new part of the URL is absolute, in which case it
replaces the original base URL:>>> absoluteURL('http://einstein/folder1', 'http://localhost/folder11')
'http://localhost/folder11'If the base URL does not have a trailing slash, it is added automatically:>>> absoluteURL('http://localhost/folder1', 'folder11')
'http://localhost/folder1/folder11'Any slashes at the end of the new URL are preserved:>>> absoluteURL('http://localhost/folder1', 'folder11/')
'http://localhost/folder1/folder11/'>>> absoluteURL('http://einstein/folder1', 'http://localhost/folder11/')
'http://localhost/folder11/'The function also handles more complex URLs containing a query string
correctly:>>> absoluteURL('http://localhost/folder1', 'folder11?max=1')
'http://localhost/folder1/folder11?max=1'>>> absoluteURL('http://localhost/folder1', 'folder11/?max=1')
'http://localhost/folder1/folder11/?max=1'If the base URL contains a query string, the resulting URL will as well:>>> absoluteURL('http://localhost/folder1?max=1', 'folder11/')
'http://localhost/folder1/folder11/?max=1'>>> absoluteURL('http://localhost/folder1/?max=1', 'folder11/')
'http://localhost/folder1/folder11/?max=1'>>> absoluteURL('http://localhost/folder1?max=1', 'folder11')
'http://localhost/folder1/folder11?max=1'>>> absoluteURL('http://localhost/folder1/?max=1', 'folder11')
'http://localhost/folder1/folder11?max=1'If both, the base and relative URL provide query strings, they are merged:>>> absoluteURL('http://localhost/folder1/?max=1', 'folder11?min=0')
'http://localhost/folder1/folder11?max=1&min=0'>>> absoluteURL('http://localhost/folder1/?max=1', 'folder11?min=0&start=0')
'http://localhost/folder1/folder11?max=1&min=0&start=0'>>> absoluteURL('http://localhost/folder1/?max=1&start=0', 'folder11?min=0')
'http://localhost/folder1/folder11?max=1&start=0&min=0'Null ResourcesIt is sometimes necessary to traverse to resources that do not yet exist. In
particular, this is needed when creating resources using “PUT” or “POST”. It
is the responsibility of the traverser to handle those cases correctly and
produce the null resources. This document only describes their behavior.A null resource is easily instantiated using the container and the name of the
resource:>>> class Folder(object):
...     __parent__ = __name__ = None
...     child = None
...
...     def __init__(self, name=''):
...         self.name = self.__name__ = name
...
...     def __repr__(self):
...         return '<Folder %r>' %self.name
>>> folder = Folder()>>> from z3c.rest import null
>>> resource = null.NullResource(folder, 'resource')>>> from zope.app.http.interfaces import INullResource
>>> INullResource.providedBy(resource)
TrueNull resources are locations, so security is available:>>> from zope.location.interfaces import ILocation
>>> ILocation.providedBy(resource)
TrueThe container is also the parent:>>> resource.container
<Folder ''>
>>> resource.__parent__
<Folder ''>The name of the resource is available at:>>> resource.name
'resource'
>>> resource.__name__
'resource'There is a special implementation of “PUT” for null resources. It works by
looking up a view called “NullPUT” for the container. This way, one null
resource implementation can be used for all container implementations.>>> import StringIO
>>> from z3c.rest import rest
>>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> nullPut = null.NullPUT(resource, request)
>>> nullPut.PUT()Since no view called “NullPUT” exists for ourFolderclass, we get a 501
return status:>>> request.response.getStatusString()
'501 Not Implemented'Let’s now register a simple NullPUT view:>>> class FolderAPI(rest.RESTView):
...
...     def NullPUT(self, resource):
...         self.context.child = Folder(resource.name)
...         self.context.child.__parent__ = self.context
...         return self.context.child>>> import zope.component
>>> from z3c.rest import interfaces
>>> zope.component.provideAdapter(
...     FolderAPI, (Folder, interfaces.IRESTRequest), name='NullPUT')Let’s make sure our location structure is correctly setup, so that absolute
URL will work:>>> from zope.traversing.interfaces import IContainmentRoot
>>> import zope.interface
>>> zope.interface.alsoProvides(folder, IContainmentRoot)Now we are ready to PUT the new resource:>>> request = rest.RESTRequest(
...     StringIO.StringIO(), {'SERVER_URL': 'http://localhost/'})>>> nullPut = null.NullPUT(resource, request)
>>> nullPut.PUT()>>> request.response.getStatusString()
'201 Created'
>>> request.response.getHeader('Location')
'http://localhost/resource'>>> folder.child
<Folder 'resource'>REST Traverser ComponentsBeing able to control and extend traversal is essential to any RESTive
API. This package uses the pluggable traverser implementation of thez3c.traverserpackage to provide a flexible traversal mechanism.REST Pluggable TraverserThe REST pluggable traverser is registered for all types of components. Its
implementation is fully tested in thez3c.traverserpackage.>>> from z3c.rest import traverser>>> import StringIO
>>> from z3c.rest import rest
>>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> pluggable = traverser.RESTPluggableTraverser(object(), request)
>>> pluggable
<z3c.rest.traverser.RESTPluggableTraverser object at ...>Item Container Traverser PluginThe item mapping interface – from which item container inherits – is the
most minimal mapping interface in Python. Thus, once traversing through this
item container is implemented, it can be used by all other container
interfaces and implementations.Let’s start by creating a very simple item container implementation:>>> import zope.interface
>>> from zope.app.container.interfaces import IItemContainer>>> class SimpleContainer(dict):
...     zope.interface.implements(IItemContainer)
...     def __init__(self, name=''):
...         self.name = name
...     def __repr__(self):
...         return '<Container name=%s>' %self.name>>> container = SimpleContainer()>>> container['sub1'] = SimpleContainer('sub1')
>>> container['sub2'] = SimpleContainer('sub2')After creating a traverser plugin instance,>>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> containerTraverser = traverser.ContainerItemTraverserPlugin(
...     container, request)we can traverse to a sub-object of that container:>>> containerTraverser.publishTraverse(request, 'sub1')
<Container name=sub1>If no proper sub-item can be found, some interesting can happen. In a normal
case,NotFoundis raised:>>> containerTraverser.publishTraverse(request, 'unknown')
Traceback (most recent call last):
...
NotFound: Object: <Container name=>, name: 'unknown'However, if the request is a PUT request, we must generate a null resource:>>> request.method = 'PUT'
>>> containerTraverser.publishTraverse(request, 'unknown')
<NullResource 'unknown'>However, a null resource is only created, if the current resource is the last
one in the traversal stack:>>> request.setTraversalStack(('sub11',))
>>> containerTraverser.publishTraverse(request, 'unknown')
Traceback (most recent call last):
...
NotFound: Object: <Container name=>, name: 'unknown'And that’s it.Publisher Hooks for REST RequestsReading this document requires – to some extend – that the reader is
familiar with the basic steps of the publication process.The Publication Request FactoryThe Zope publication process starts when a WSGI server sends the request
environment and response initialization callable to the Zope WSGI Publisher
application _[1]. The WSGI publisher application is then responsible for
processing the request in the publisher and stream out the result.In order to process a request in the publisher, we have to create a valid
publisher request object. The WSGI publisher application uses a request
factory for this purpose. This package implements this factory to ensure that
a special REST request (based on HTTP Request) is created at all times.The request factory is instantiated using a ZODB database object:>>> from ZODB.DB import DB
>>> from ZODB.DemoStorage import DemoStorage
>>> db = DB(DemoStorage())Let’s now create the factory:>>> from z3c.rest import rest
>>> RequestFactory = rest.RESTPublicationRequestFactory(db)When a request comes in from the server, the request is created as follows:>>> import StringIO
>>> inStream = StringIO.StringIO('Input stream')>>> env = {'HTTP_ACCEPT_LANGUAGE': 'en-US,en',
...        'SERVER_URL': 'http://localhost:8080/'}>>> request = RequestFactory(inStream, env)We now got a valid request that we can send through the publisher:>>> request
<z3c.rest.rest.RESTRequest instance URL=http://localhost:8080>The request, however, is only responsible for representing the network request
in the publisher and has no direct knowledge of the application. But the
request connects to an application-specific – in this case Zope 3 –
component known as the publication.>>> request.publication
<zope.app.publication.http.HTTPPublication object at ...>Since we do not need a special REST publication, we are simply reusing the
more generic HTTP version. The publication will be the same for all
requests. It also contains the reference to the database:>>> request.publication.db
<ZODB.DB.DB object at ...>Unfortunately, it takes a lot more setup to send the request through the
publisher successfully. The publication requires many other aspects of
publishing to be available, including traversal, security, and a properly
constructed database. However, we can still see a failure:>>> from zope.publisher import publish
>>> publish.publish(request)
<z3c.rest.rest.RESTRequest instance URL=http://localhost:8080>
>>> print request.response.consumeBody()
<?xml version="1.0" ?>
<error>
  <name>ComponentLookupError</name>
  <explanation>(&lt;InterfaceClass ...IAuthentication&gt;, u'')</explanation>
</error>Let’ unwind a bit. Originally, we started with the desire to create a
Publisher WSGI Application instance that internally uses a REST request. All
that you need to do is:>>> from zope.app import wsgi
>>> app = wsgi.WSGIPublisherApplication(
...     db, rest.RESTPublicationRequestFactory)
>>> app
<zope.app.wsgi.WSGIPublisherApplication object at ...>When the WSGI server sends a request to the WSGI application, the following
happens:>>> status = None
>>> headers = None
>>> def start_response(s, h):
...     global status
...     global headers
...     status, headers = s, h>>> wsgiEnv = {'wsgi.input': inStream}
>>> wsgiEnv.update(env)>>> print '\n'.join(app(wsgiEnv, start_response))
<?xml version="1.0" ?>
<error>
  <name>ComponentLookupError</name>
  <explanation>(&lt;InterfaceClass ...IAuthentication&gt;, u'')</explanation>
</error>The REST RequestFor most parts, the REST request is identical to the HTTP request, so I won’t
go into too much detail about the HTTP request API.The REST request mainly extends the HTTP request in that it parses the query
string of the URL into a set of parameters. This happens duringprocessInputs().If there is no query string, the paramaters mapping is empty:>>> request = RequestFactory(
...     StringIO.StringIO(), {})
>>> request.processInputs()
>>> request.parameters
{}So let’s now pass a few parameters:>>> request = RequestFactory(
...     StringIO.StringIO(),
...     {'QUERY_STRING': 'format=html&action=delete&item=1&item=3'})
>>> request.processInputs()
>>> pprint(request.parameters)
{'action': 'delete',
 'format': 'html',
 'item': ['1',
          '3']}We also override some of the request’s mapping methods, so that the parameters
and environment values are available as part of the request:>>> sorted(request.keys())
['QUERY_STRING', 'action', 'format', 'item']>>> request.get('QUERY_STRING')
'format=html&action=delete&item=1&item=3'
>>> request.get('action')
'delete'
>>> request.get('unknwon', 'default')
'default'The REST ResponseThe REST Response is pretty much identical to the HTTP Response, except that
its exception handler returns XML instead of HTML. This method, however, is
only used for classic and string exceptions.Starting with a response, …>>> response = rest.RESTResponse()… we can now call the handler:>>> class MyException(Exception):
...     pass>>> response.handleException((MyException, MyException(), None))
>>> response._status
500
>>> response._reason
'Internal Server Error'
>>> print '\n'.join(response._result)
<?xml version="1.0" ?>
<error>
  <name>MyException</name>
  <explanation></explanation>
</error>Let’s try a string exception too:>>> response.handleException(('StringException', 'Some details', None))
>>> response._status
500
>>> response._reason
'Internal Server Error'
>>> print '\n'.join(response._result)
<?xml version="1.0" ?>
<error>
  <name>StringException</name>
  <explanation>Some details</explanation>
</error>TheRedirectexception is special. It actuaually causes the request to be
redirected.>>> response._request = rest.RESTRequest(None, {})>>> from zope.publisher.interfaces import Redirect
>>> response.handleException(
...     (Redirect, Redirect('http://localhost'), None), trusted=True)
>>> response._status
302
>>> response._reason
'Moved Temporarily'
>>> response._headers['location']
['http://localhost']REST ViewsUnlike browser views, a REST view doesnotrepresent its own sub-resource
(such as “index.html”). Instead it merely defines the behavior of the HTTP
methods for a particular content component.Here is an example:>>> class ObjectAPI(rest.RESTView):
...
...     def GET(self):
...          return str(self.context)TheRESTViewbase class provides a suitable constructor:>>> class Object(object):
...     def __repr__(self):
...         return '<Object>'
>>> myobj = Object()>>> request = RequestFactory(
...     StringIO.StringIO(), {'SERVER_URL': 'http://localhost:8080/myobj'})>>> view = ObjectAPI(myobj, request)When the publisher traverses tomyobj, it will look up a view based on the
HTTP mehtod, such as “GET”. It then also expects to find a method of that same
name and calls it _[2].>>> view.GET()
'<Object>'The REST View, like all other views, exposes its context and the request:>>> view.context
<Object>
>>> view.request
<z3c.rest.rest.RESTRequest instance URL=http://localhost:8080/myobj>Also, a view must be located, so it has a parent as well:>>> view.__parent__
<Object>You can set it to something else of course:>>> view.__parent__ = 1
>>> view.__parent__
1CHANGES0.4.0 (2010-10-05)Added undeclared but necessary install dependency onzope.app.http.0.3.0 (2010-10-05)Added not declared test dependencies.Updated test set up and fixed tests to run with ZTK 1.0.Using Python’sdoctestmodule instead of depreactedzope.testing.doctest.0.2.5 (2008-09-16)Bug/Misfeature: Finally get handling of URL merging working as desired. Also
added extensive tests to document the behavior.0.2.4 (2008-09-04)RESTClient() now correctly interpretshttps://URLs.0.2.3 (2008-03-20)Bug/Misfeature: Sigh, getting the trailing slash handled correctly turned
out to be a big pain. I really hope I got it working the way it should be
for a REST client now.0.2.2 (2008-03-19)Bug/Misfeature: The client always added a slash to the end of the URL. But
some REST APIs are very sensitive to this. Now the slash is only preserved
if present, but nothing will be added otherwise.0.2.1 (2008-03-06)Bug: Sometimes the response body was not read and the contents of the client
was empty. Unfortunately, this problem could not be reliably reproduced, but
debugging showed that the connection was closed to early. (Roy Mathew)Feature: Make the package Python 2.4 and 2.5 compatible.Feature: Require lxml 2.0 for z3c.rest.0.2.0 (2008-03-03)Feature: Made the HTTP caller pluggable for the REST client, allowing
request types other thanRESTRequest.0.1.0 (2008-03-03)Initial ReleasePublisher hooks to build dedicated REST serversError view supportPluggable REST traverser based onz3c.traverserREST clientMinimal sample application