Import Upstream version 1.3.3
This commit is contained in:
commit
cba51cb853
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
|
@ -0,0 +1,41 @@
|
|||
..
|
||||
This file is part of wadllib.
|
||||
|
||||
wadllib is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation, version 3 of the License.
|
||||
|
||||
wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
This project uses zc.buildout for development.
|
||||
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
These are guidelines for hacking on the wadllib project. But first,
|
||||
please see the common hacking guidelines at:
|
||||
|
||||
http://dev.launchpad.net/Hacking
|
||||
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
If you find bugs in this package, you can report them here:
|
||||
|
||||
https://launchpad.net/wadllib
|
||||
|
||||
If you want to discuss this package, join the team and mailing list here:
|
||||
|
||||
https://launchpad.net/~lazr-developers
|
||||
|
||||
or send a message to:
|
||||
|
||||
lazr-developers@lists.launchpad.net
|
|
@ -0,0 +1,3 @@
|
|||
recursive-include src *.json *.xml *.txt
|
||||
include ez_setup.py
|
||||
include COPYING.txt HACKING.txt
|
|
@ -0,0 +1,823 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: wadllib
|
||||
Version: 1.3.3
|
||||
Summary: Navigate HTTP resources using WADL files as guides.
|
||||
Home-page: https://launchpad.net/wadllib
|
||||
Maintainer: LAZR Developers
|
||||
Maintainer-email: lazr-developers@lists.launchpad.net
|
||||
License: LGPL v3
|
||||
Download-URL: https://launchpad.net/wadllib/+download
|
||||
Description: ..
|
||||
Copyright (C) 2008-2013 Canonical Ltd.
|
||||
|
||||
This file is part of wadllib.
|
||||
|
||||
wadllib is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation, version 3 of the License.
|
||||
|
||||
wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
wadllib
|
||||
*******
|
||||
|
||||
An Application object represents a web service described by a WADL
|
||||
file.
|
||||
|
||||
>>> import os
|
||||
>>> import sys
|
||||
>>> import pkg_resources
|
||||
>>> from wadllib.application import Application
|
||||
|
||||
The first argument to the Application constructor is the URL at which
|
||||
the WADL file was found. The second argument may be raw WADL markup.
|
||||
|
||||
>>> wadl_string = pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', 'launchpad-wadl.xml')
|
||||
>>> wadl = Application("http://api.launchpad.dev/beta/", wadl_string)
|
||||
|
||||
Or the second argument may be an open filehandle containing the markup.
|
||||
|
||||
>>> cleanups = []
|
||||
>>> def application_for(filename, url="http://www.example.com/"):
|
||||
... wadl_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', filename)
|
||||
... cleanups.append(wadl_stream)
|
||||
... return Application(url, wadl_stream)
|
||||
>>> wadl = application_for("launchpad-wadl.xml",
|
||||
... "http://api.launchpad.dev/beta/")
|
||||
|
||||
|
||||
Link navigation
|
||||
===============
|
||||
|
||||
The preferred technique for finding a resource is to start at one of
|
||||
the resources defined in the WADL file, and follow links. This code
|
||||
retrieves the definition of the root resource.
|
||||
|
||||
>>> service_root = wadl.get_resource_by_path('')
|
||||
>>> service_root.url
|
||||
'http://api.launchpad.dev/beta/'
|
||||
>>> service_root.type_url
|
||||
'#service-root'
|
||||
|
||||
The service root resource supports GET.
|
||||
|
||||
>>> get_method = service_root.get_method('get')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
>>> get_method = service_root.get_method('GET')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
If we want to invoke this method, we send a GET request to the service
|
||||
root URL.
|
||||
|
||||
>>> get_method.name
|
||||
'get'
|
||||
>>> get_method.build_request_url()
|
||||
'http://api.launchpad.dev/beta/'
|
||||
|
||||
The WADL description of a resource knows which representations are
|
||||
available for that resource. In this case, the server root resource
|
||||
has a a JSON representation, and it defines parameters like
|
||||
'people_collection_link', a link to a list of people in Launchpad. We
|
||||
should be able to use the get_parameter() method to get the WADL
|
||||
definition of the 'people_collection_link' parameter and find out more
|
||||
about it--for instance, is it a link to another resource?
|
||||
|
||||
>>> def test_raises(exc_class, method, *args, **kwargs):
|
||||
... try:
|
||||
... method(*args, **kwargs)
|
||||
... except Exception:
|
||||
... # Contortion to support Python < 2.6 and >= 3 simultaneously.
|
||||
... e = sys.exc_info()[1]
|
||||
... if isinstance(e, exc_class):
|
||||
... print(e)
|
||||
... return
|
||||
... raise
|
||||
... raise Exception("Expected exception %s not raised" % exc_class)
|
||||
|
||||
>>> from wadllib.application import NoBoundRepresentationError
|
||||
>>> link_name = 'people_collection_link'
|
||||
>>> test_raises(
|
||||
... NoBoundRepresentationError, service_root.get_parameter, link_name)
|
||||
Resource is not bound to any representation, and no media media type was specified.
|
||||
|
||||
Oops. The code has no way to know whether 'people_collection_link' is
|
||||
a parameter of the JSON representation or some other kind of
|
||||
representation. We can pass a media type to get_parameter and let it
|
||||
know which representation the parameter lives in.
|
||||
|
||||
>>> link_parameter = service_root.get_parameter(
|
||||
... link_name, 'application/json')
|
||||
>>> test_raises(NoBoundRepresentationError, link_parameter.get_value)
|
||||
Resource is not bound to any representation.
|
||||
|
||||
Oops again. The parameter is available, but it has no value, because
|
||||
there's no actual data associated with the resource. The browser can
|
||||
look up the description of the GET method to make an actual GET
|
||||
request to the service root, and bind the resulting representation to
|
||||
the WADL description of the service root.
|
||||
|
||||
You can't bind just any representation to a WADL resource description.
|
||||
It has to be of a media type understood by the WADL description.
|
||||
|
||||
>>> from wadllib.application import UnsupportedMediaTypeError
|
||||
>>> test_raises(
|
||||
... UnsupportedMediaTypeError, service_root.bind,
|
||||
... '<html>Some HTML</html>', 'text/html')
|
||||
This resource doesn't define a representation for media type text/html
|
||||
|
||||
The WADL description of the service root resource has a JSON
|
||||
representation. Here it is.
|
||||
|
||||
>>> json_representation = service_root.get_representation_definition(
|
||||
... 'application/json')
|
||||
>>> json_representation.media_type
|
||||
'application/json'
|
||||
|
||||
We already have a WADL representation of the service root resource, so
|
||||
let's try binding it to that JSON representation. We use test JSON
|
||||
data from a file to simulate the result of a GET request to the
|
||||
service root.
|
||||
|
||||
>>> def get_testdata(filename):
|
||||
... return pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', filename + '.json')
|
||||
|
||||
>>> def bind_to_testdata(resource, filename):
|
||||
... return resource.bind(get_testdata(filename), 'application/json')
|
||||
|
||||
The return value is a new Resource object that's "bound" to that JSON
|
||||
test data.
|
||||
|
||||
>>> bound_service_root = bind_to_testdata(service_root, 'root')
|
||||
>>> sorted([param.name for param in bound_service_root.parameters()])
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> sorted(bound_service_root.parameter_names())
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> [method.id for method in bound_service_root.method_iter]
|
||||
['service-root-get']
|
||||
|
||||
Now the bound resource object has a JSON representation, and now
|
||||
'people_collection_link' makes sense. We can follow the
|
||||
'people_collection_link' to a new Resource object.
|
||||
|
||||
>>> link_parameter = bound_service_root.get_parameter(link_name)
|
||||
>>> link_parameter.style
|
||||
'plain'
|
||||
>>> print(link_parameter.get_value())
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource = link_parameter.linked_resource
|
||||
>>> personset_resource.__class__
|
||||
<class 'wadllib.application.Resource'>
|
||||
>>> print(personset_resource.url)
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource.type_url
|
||||
'http://api.launchpad.dev/beta/#people'
|
||||
|
||||
This new resource is a collection of people.
|
||||
|
||||
>>> personset_resource.id
|
||||
'people'
|
||||
|
||||
The "collection of people" resource supports a standard GET request as
|
||||
well as a special GET and an overloaded POST. The get_method() method
|
||||
is used to retrieve WADL definitions of the possible HTTP requests you
|
||||
might make. Here's how to get the WADL definition of the standard GET
|
||||
request.
|
||||
|
||||
>>> get_method = personset_resource.get_method('get')
|
||||
>>> get_method.id
|
||||
'people-get'
|
||||
|
||||
The method name passed into get_method() is treated case-insensitively.
|
||||
|
||||
>>> personset_resource.get_method('GET').id
|
||||
'people-get'
|
||||
|
||||
To invoke the special GET request, the client sets the 'ws.op' query
|
||||
parameter to the fixed string 'findPerson'.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'findPerson'})
|
||||
>>> find_method.id
|
||||
'people-findPerson'
|
||||
|
||||
Given an end-user's values for the non-fixed parameters, it's possible
|
||||
to get the URL that should be used to invoke the method.
|
||||
|
||||
>>> print(find_method.build_request_url(text='foo'))
|
||||
http://api.launchpad.dev/beta/people?text=foo&ws.op=findPerson
|
||||
|
||||
>>> print(find_method.build_request_url(
|
||||
... {'ws.op' : 'findPerson', 'text' : 'bar'}))
|
||||
http://api.launchpad.dev/beta/people?text=bar&ws.op=findPerson
|
||||
|
||||
An error occurs if the end-user gives an incorrect value for a fixed
|
||||
parameter value, or omits a required parameter.
|
||||
|
||||
>>> find_method.build_request_url()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'text'
|
||||
|
||||
>>> find_method.build_request_url(
|
||||
... {'ws.op' : 'findAPerson', 'text' : 'foo'})
|
||||
... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Value 'findAPerson' for parameter 'ws.op' conflicts
|
||||
with fixed value 'findPerson'
|
||||
|
||||
To invoke the overloaded POST request, the client sets the 'ws.op'
|
||||
query variable to the fixed string 'newTeam':
|
||||
|
||||
>>> create_team_method = personset_resource.get_method(
|
||||
... 'post', representation_params={'ws.op' : 'newTeam'})
|
||||
>>> create_team_method.id
|
||||
'people-newTeam'
|
||||
|
||||
findMethod() returns None when there's no WADL method matching the
|
||||
name or the fixed parameters.
|
||||
|
||||
>>> print(personset_resource.get_method('nosuchmethod'))
|
||||
None
|
||||
|
||||
>>> print(personset_resource.get_method(
|
||||
... 'post', query_params={'ws_op' : 'nosuchparam'}))
|
||||
None
|
||||
|
||||
Let's say the browser makes a GET request to the person set resource
|
||||
and gets back a representation. We can bind that representation to our
|
||||
description of the person set resource.
|
||||
|
||||
>>> bound_personset = bind_to_testdata(personset_resource, 'personset')
|
||||
>>> bound_personset.get_parameter("start").get_value()
|
||||
0
|
||||
>>> bound_personset.get_parameter("total_size").get_value()
|
||||
63
|
||||
|
||||
We can keep following links indefinitely, so long as we bind to a
|
||||
representation to each resource as we get it, and use the
|
||||
representation to find the next link.
|
||||
|
||||
>>> next_page_link = bound_personset.get_parameter("next_collection_link")
|
||||
>>> print(next_page_link.get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> page_two = next_page_link.linked_resource
|
||||
>>> bound_page_two = bind_to_testdata(page_two, 'personset-page2')
|
||||
>>> print(bound_page_two.url)
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> bound_page_two.get_parameter("start").get_value()
|
||||
5
|
||||
>>> print(bound_page_two.get_parameter("next_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=10&ws.size=5
|
||||
|
||||
Let's say the browser makes a POST request that invokes the 'newTeam'
|
||||
named operation. The response will include a number of HTTP headers,
|
||||
including 'Location', which points the way to the newly created team.
|
||||
|
||||
>>> headers = { 'Location' : 'http://api.launchpad.dev/~newteam' }
|
||||
>>> response = create_team_method.response.bind(headers)
|
||||
>>> location_parameter = response.get_parameter('Location')
|
||||
>>> location_parameter.get_value()
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team = location_parameter.linked_resource
|
||||
>>> new_team.url
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team.type_url
|
||||
'http://api.launchpad.dev/beta/#team'
|
||||
|
||||
Examining links
|
||||
---------------
|
||||
|
||||
The 'linked_resource' property of a parameter lets you follow a link
|
||||
to another object. The 'link' property of a parameter lets you examine
|
||||
links before following them.
|
||||
|
||||
>>> import json
|
||||
>>> links_wadl = application_for('links-wadl.xml')
|
||||
>>> service_root = links_wadl.get_resource_by_path('')
|
||||
>>> representation = json.dumps(
|
||||
... {'scalar_value': 'foo',
|
||||
... 'known_link': 'http://known/',
|
||||
... 'unknown_link': 'http://unknown/'})
|
||||
>>> bound_root = service_root.bind(representation)
|
||||
|
||||
>>> print(bound_root.get_parameter("scalar_value").link)
|
||||
None
|
||||
|
||||
>>> known_resource = bound_root.get_parameter("known_link")
|
||||
>>> unknown_resource = bound_root.get_parameter("unknown_link")
|
||||
|
||||
>>> print(known_resource.link.can_follow)
|
||||
True
|
||||
>>> print(unknown_resource.link.can_follow)
|
||||
False
|
||||
|
||||
A link whose type is unknown is a link to a resource not described by
|
||||
WADL. Following this link using .linked_resource or .link.follow will
|
||||
cause a wadllib error. You'll need to follow the link using a general
|
||||
HTTP library or some other tool.
|
||||
|
||||
>>> known_resource.link.follow
|
||||
<wadllib.application.Resource object ...>
|
||||
>>> known_resource.linked_resource
|
||||
<wadllib.application.Resource object ...>
|
||||
|
||||
>>> from wadllib.application import WADLError
|
||||
>>> test_raises(WADLError, getattr, unknown_resource.link, 'follow')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
>>> test_raises(WADLError, getattr, unknown_resource, 'linked_resource')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
Creating a Resource from a representation definition
|
||||
====================================================
|
||||
|
||||
Although every representation is a representation of some HTTP
|
||||
resource, an HTTP resource doesn't necessarily correspond directly to
|
||||
a WADL <resource> or <resource_type> tag. Sometimes a representation
|
||||
is defined within a WADL <method> tag.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'find'})
|
||||
>>> find_method.id
|
||||
'people-find'
|
||||
|
||||
>>> representation_definition = (
|
||||
... find_method.response.get_representation_definition(
|
||||
... 'application/json'))
|
||||
|
||||
There may be no WADL <resource> or <resource_type> tag for the
|
||||
representation defined here. That's why wadllib makes it possible to
|
||||
instantiate an anonymous Resource object using only the representation
|
||||
definition.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> anonymous_resource = Resource(
|
||||
... wadl, "http://foo/", representation_definition.tag)
|
||||
|
||||
We can bind this resource to a representation, as long as we
|
||||
explicitly pass in the representation definition.
|
||||
|
||||
>>> anonymous_resource = anonymous_resource.bind(
|
||||
... get_testdata('personset'), 'application/json',
|
||||
... representation_definition=representation_definition)
|
||||
|
||||
Once the resource is bound to a representation, we can get its
|
||||
parameter values.
|
||||
|
||||
>>> print(anonymous_resource.get_parameter(
|
||||
... 'total_size', 'application/json').get_value())
|
||||
63
|
||||
|
||||
Resource instantiation
|
||||
======================
|
||||
|
||||
If you happen to have the URL to an object lying around, and you know
|
||||
its type, you can construct a Resource object directly instead of
|
||||
by following links.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> limi_person = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person")
|
||||
>>> sorted([method.id for method in limi_person.method_iter])[:3]
|
||||
['person-acceptInvitationToBeMemberOf', 'person-addMember', 'person-declineInvitationToBeMemberOf']
|
||||
|
||||
>>> bound_limi = bind_to_testdata(limi_person, 'person-limi')
|
||||
>>> sorted(bound_limi.parameter_names())[:3]
|
||||
['admins_collection_link', 'confirmed_email_addresses_collection_link',
|
||||
'date_created']
|
||||
>>> languages_link = bound_limi.get_parameter("languages_collection_link")
|
||||
>>> print(languages_link.get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
You can bind a Resource to a representation when you create it.
|
||||
|
||||
>>> limi_data = get_testdata('person-limi')
|
||||
>>> bound_limi = Resource(
|
||||
... wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", limi_data,
|
||||
... "application/json")
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
By default the representation is treated as a string and processed
|
||||
according to the media type you pass into the Resource constructor. If
|
||||
you've already processed the representation, pass in False for the
|
||||
'representation_needs_processing' argument.
|
||||
|
||||
>>> from wadllib import _make_unicode
|
||||
>>> processed_limi_data = json.loads(_make_unicode(limi_data))
|
||||
>>> bound_limi = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", processed_limi_data,
|
||||
... "application/json", False)
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
Most of the time, the representation of a resource is of the type
|
||||
you'd get by sending a standard GET to that resource. If that's not
|
||||
the case, you can specify a RepresentationDefinition as the
|
||||
'representation_definition' argument to bind() or the Resource
|
||||
constructor, to show what the representation really looks like. Here's
|
||||
an example.
|
||||
|
||||
There's a method on a person resource such as bound_limi that's
|
||||
identified by a distinctive query argument: ws.op=getMembersByStatus.
|
||||
|
||||
>>> method = bound_limi.get_method(
|
||||
... query_params={'ws.op' : 'findPathToTeam'})
|
||||
|
||||
Invoke this method with a GET request and you'll get back a page from
|
||||
a list of people.
|
||||
|
||||
>>> people_page_repr_definition = (
|
||||
... method.response.get_representation_definition('application/json'))
|
||||
>>> people_page_repr_definition.tag.attrib['href']
|
||||
'http://api.launchpad.dev/beta/#person-page'
|
||||
|
||||
As it happens, we have a page from a list of people to use as test data.
|
||||
|
||||
>>> people_page_repr = get_testdata('personset')
|
||||
|
||||
If we bind the resource to the result of the method invocation as
|
||||
happened above, we don't be able to access any of the parameters we'd
|
||||
expect. wadllib will think the representation is of type
|
||||
'person-full', the default GET type for bound_limi.
|
||||
|
||||
>>> bad_people_page = bound_limi.bind(people_page_repr)
|
||||
>>> print(bad_people_page.get_parameter('total_size'))
|
||||
None
|
||||
|
||||
Since we don't actually have a 'person-full' representation, we won't
|
||||
be able to get values for the parameters of that kind of
|
||||
representation.
|
||||
|
||||
>>> bad_people_page.get_parameter('name').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'name'
|
||||
|
||||
So that's a dead end. *But*, if we pass the correct representation
|
||||
type into bind(), we can access the parameters associated with a
|
||||
'person-page' representation.
|
||||
|
||||
>>> people_page = bound_limi.bind(
|
||||
... people_page_repr,
|
||||
... representation_definition=people_page_repr_definition)
|
||||
>>> people_page.get_parameter('total_size').get_value()
|
||||
63
|
||||
|
||||
If you invoke the method and ask for a media type other than JSON, you
|
||||
won't get anything.
|
||||
|
||||
>>> print(method.response.get_representation_definition('text/html'))
|
||||
None
|
||||
|
||||
Data type conversion
|
||||
--------------------
|
||||
|
||||
The values of date and dateTime parameters are automatically converted to
|
||||
Python datetime objects.
|
||||
|
||||
>>> data_type_wadl = application_for('data-types-wadl.xml')
|
||||
>>> service_root = data_type_wadl.get_resource_by_path('')
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2007-10-20',
|
||||
... 'a_datetime': '2005-06-06T08:59:51.619713+00:00'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
A 'date' field can include a timestamp, and a 'datetime' field can
|
||||
omit one. wadllib will turn both into datetime objects.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2005-06-06T08:59:51.619713+00:00',
|
||||
... 'a_datetime': '2007-10-20'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
If a date or dateTime parameter has a null value, you get None. If the
|
||||
value is a string that can't be parsed to a datetime object, you get a
|
||||
ValueError.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': 'foo', 'a_datetime': None})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: foo
|
||||
>>> print(bound_root.get_parameter('a_datetime').get_value())
|
||||
None
|
||||
|
||||
Representation creation
|
||||
=======================
|
||||
|
||||
You must provide a representation when invoking certain methods. The
|
||||
representation() method helps you build one without knowing the
|
||||
details of how a representation is put together.
|
||||
|
||||
>>> create_team_method.build_representation(
|
||||
... display_name='Joe Bloggs', name='joebloggs')
|
||||
('application/x-www-form-urlencoded', 'display_name=Joe+Bloggs&name=joebloggs&ws.op=newTeam')
|
||||
|
||||
The return value of build_representation is a 2-tuple containing the
|
||||
media type of the built representation, and the string representation
|
||||
itself. Along with the resource's URL, this is all you need to send
|
||||
the representation to a web server.
|
||||
|
||||
>>> bound_limi.get_method('patch').build_representation(name='limi2')
|
||||
('application/json', '{"name": "limi2"}')
|
||||
|
||||
Representations may require values for certain parameters.
|
||||
|
||||
>>> create_team_method.build_representation()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'display_name'
|
||||
|
||||
>>> bound_limi.get_method('put').build_representation(name='limi2')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'mugshot_link'
|
||||
|
||||
Some representations may safely include binary data.
|
||||
|
||||
>>> binary_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', 'multipart-binary-wadl.xml')
|
||||
>>> cleanups.append(binary_stream)
|
||||
>>> binary_wadl = Application(
|
||||
... "http://www.example.com/", binary_stream)
|
||||
>>> service_root = binary_wadl.get_resource_by_path('')
|
||||
|
||||
Define a helper that processes the representation the same way
|
||||
zope.publisher would.
|
||||
|
||||
>>> import cgi
|
||||
>>> import io
|
||||
>>> def assert_message_parts(media_type, doc, expected):
|
||||
... if sys.version_info[0] == 3 and sys.version_info[1] < 3:
|
||||
... # We can't do much due to https://bugs.python.org/issue18013.
|
||||
... for value in expected:
|
||||
... if not isinstance(value, bytes):
|
||||
... value = value.encode('UTF-8')
|
||||
... assert value in doc
|
||||
... return
|
||||
... environ = {
|
||||
... 'REQUEST_METHOD': 'POST',
|
||||
... 'CONTENT_TYPE': media_type,
|
||||
... 'CONTENT_LENGTH': str(len(doc)),
|
||||
... }
|
||||
... kwargs = (
|
||||
... {'encoding': 'UTF-8'} if sys.version_info[0] >= 3 else {})
|
||||
... fs = cgi.FieldStorage(
|
||||
... fp=io.BytesIO(doc), environ=environ, keep_blank_values=1,
|
||||
... **kwargs)
|
||||
... values = []
|
||||
... def append_values(fields):
|
||||
... for field in fields:
|
||||
... if field.list:
|
||||
... append_values(field.list)
|
||||
... else:
|
||||
... values.append(field.value)
|
||||
... append_values(fs.list)
|
||||
... assert values == expected, (
|
||||
... 'Expected %s, got %s' % (expected, values))
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text", binary_field=b"\x01\x02\r\x81\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(media_type, doc, ['text', b'\x01\x02\r\x81\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\n", binary_field=b"\x01\x02\r\x81\n\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\n', b'\x01\x02\r\x81\n\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\r\nmore\r\n",
|
||||
... binary_field=b"\x01\x02\r\n\x81\r\x82\n")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\nmore\r\n', b'\x01\x02\r\n\x81\r\x82\n'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'text/unknown')
|
||||
>>> method.build_representation(field="value")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Unsupported media type: 'text/unknown'
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
Some parameters take values from a predefined list of options.
|
||||
|
||||
>>> option_wadl = application_for('options-wadl.xml')
|
||||
>>> definitions = option_wadl.representation_definitions
|
||||
>>> service_root = option_wadl.get_resource_by_path('')
|
||||
>>> definition = definitions['service-root-json']
|
||||
>>> param = definition.params(service_root)[0]
|
||||
>>> print(param.name)
|
||||
has_options
|
||||
>>> sorted([option.value for option in param.options])
|
||||
['Value 1', 'Value 2']
|
||||
|
||||
Such parameters cannot take values that are not in the list.
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Value 1'})
|
||||
{'has_options': 'Value 1'}
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Invalid value'})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value 'Invalid value' for parameter
|
||||
'has_options': valid values are: "Value 1", "Value 2"
|
||||
|
||||
|
||||
Error conditions
|
||||
================
|
||||
|
||||
You'll get None if you try to look up a nonexistent resource.
|
||||
|
||||
>>> print(wadl.get_resource_by_path('nosuchresource'))
|
||||
None
|
||||
|
||||
You'll get an exception if you try to look up a nonexistent resource
|
||||
type.
|
||||
|
||||
>>> print(wadl.get_resource_type('#nosuchtype'))
|
||||
Traceback (most recent call last):
|
||||
KeyError: 'No such XML ID: "#nosuchtype"'
|
||||
|
||||
You'll get None if you try to look up a method whose parameters don't
|
||||
match any defined method.
|
||||
|
||||
>>> print(bound_limi.get_method(
|
||||
... 'post', representation_params={ 'foo' : 'bar' }))
|
||||
None
|
||||
|
||||
.. cleanup
|
||||
>>> for stream in cleanups:
|
||||
... stream.close()
|
||||
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
||||
docs/*
|
||||
|
||||
================
|
||||
NEWS for wadllib
|
||||
================
|
||||
|
||||
1.3.3 (2018-07-20)
|
||||
==================
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
- Add tox testing support.
|
||||
- Implement a subset of MIME multipart/form-data encoding locally rather
|
||||
than using the standard library's email module, which doesn't have good
|
||||
handling of binary parts and corrupts bytes in them that look like line
|
||||
endings in various ways depending on the Python version. [bug=1729754]
|
||||
|
||||
1.3.2 (2013-02-25)
|
||||
==================
|
||||
|
||||
- Impose sort order to avoid test failures due to hash randomization.
|
||||
LP: #1132125
|
||||
- Be sure to close streams opened by pkg_resources.resource_stream() to avoid
|
||||
test suite complaints.
|
||||
|
||||
|
||||
1.3.1 (2012-03-22)
|
||||
==================
|
||||
|
||||
- Correct the double pass through _from_string causing datetime issues
|
||||
|
||||
|
||||
1.3.0 (2012-01-27)
|
||||
==================
|
||||
|
||||
- Add Python 3 compatibility
|
||||
|
||||
- Add the ability to inspect links before following them.
|
||||
|
||||
- Ensure that the sample data is packaged.
|
||||
|
||||
1.2.0 (2011-02-03)
|
||||
==================
|
||||
|
||||
- It's now possible to examine a link before following it, to see
|
||||
whether it has a WADL description or whether it needs to be fetched
|
||||
with a general HTTP client.
|
||||
|
||||
- It's now possible to iterate over a resource's Parameter objects
|
||||
with the .parameters() method.
|
||||
|
||||
1.1.8 (2010-10-27)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (yet again). This time to include the version.txt file
|
||||
used by setup.py.
|
||||
|
||||
1.1.7 (2010-10-26)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (again) to include the sample data used in tests.
|
||||
|
||||
1.1.6 (2010-10-21)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed to include the sample data used in tests.
|
||||
|
||||
1.1.5 (2010-05-04)
|
||||
==================
|
||||
|
||||
- Fixed a bug (Launchpad bug 274074) that prevented the lookup of
|
||||
parameter values in resources associated directly with a
|
||||
representation definition (rather than a resource type with a
|
||||
representation definition). Bug fix provided by James Westby.
|
||||
|
||||
1.1.4 (2009-09-15)
|
||||
==================
|
||||
|
||||
- Fixed a bug that crashed wadllib unless all parameters of a
|
||||
multipart representation were provided.
|
||||
|
||||
1.1.3 (2009-08-26)
|
||||
==================
|
||||
|
||||
- Remove unnecessary build dependencies.
|
||||
|
||||
- Add missing dependencies to setup file.
|
||||
|
||||
- Remove sys.path hack from setup.py.
|
||||
|
||||
1.1.2 (2009-08-20)
|
||||
==================
|
||||
|
||||
- Consistently handle different versions of simplejson.
|
||||
|
||||
1.1.1 (2009-07-14)
|
||||
==================
|
||||
|
||||
- Make wadllib aware of the <option> tags that go beneath <param> tags.
|
||||
|
||||
1.1 (2009-07-09)
|
||||
================
|
||||
|
||||
- Make wadllib capable of recognizing and generating
|
||||
multipart/form-data representations, including representations that
|
||||
incorporate binary parameters.
|
||||
|
||||
|
||||
1.0 (2009-03-23)
|
||||
================
|
||||
|
||||
- Initial release on PyPI
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Provides-Extra: docs
|
|
@ -0,0 +1,20 @@
|
|||
Navigate HTTP resources using WADL files as guides.
|
||||
|
||||
wadllib should work with Python >= 2.6.
|
||||
|
||||
..
|
||||
Copyright (C) 2008-2009 Canonical Ltd.
|
||||
|
||||
This file is part of wadllib.
|
||||
|
||||
wadllib is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation, version 3 of the License.
|
||||
|
||||
wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,241 @@
|
|||
#!python
|
||||
"""Bootstrap setuptools installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
file in the same directory with it, and add this to the top of your setup.py::
|
||||
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
If you want to require a specific version of setuptools, set a download
|
||||
mirror, or use an alternate download directory, you can do so by supplying
|
||||
the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
import sys
|
||||
DEFAULT_VERSION = "0.6c11"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
|
||||
|
||||
md5_data = {
|
||||
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
|
||||
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
|
||||
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
|
||||
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
|
||||
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
|
||||
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
|
||||
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
|
||||
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
|
||||
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
|
||||
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
|
||||
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
|
||||
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
|
||||
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
|
||||
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
|
||||
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
|
||||
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
|
||||
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
|
||||
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
|
||||
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
|
||||
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
|
||||
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
|
||||
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
|
||||
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
|
||||
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
|
||||
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
|
||||
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
|
||||
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
|
||||
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
|
||||
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
|
||||
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
|
||||
}
|
||||
|
||||
import sys, os
|
||||
|
||||
def _validate_md5(egg_name, data):
|
||||
if egg_name in md5_data:
|
||||
from md5 import md5
|
||||
digest = md5(data).hexdigest()
|
||||
if digest != md5_data[egg_name]:
|
||||
print >>sys.stderr, (
|
||||
"md5 validation of %s failed! (Possible download problem?)"
|
||||
% egg_name
|
||||
)
|
||||
sys.exit(2)
|
||||
return data
|
||||
|
||||
|
||||
def use_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
download_delay=15, min_version=None
|
||||
):
|
||||
"""Automatically find/download setuptools and make it available on sys.path
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end with
|
||||
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
|
||||
it is not already available. If `download_delay` is specified, it should
|
||||
be the number of seconds that will be paused before initiating a download,
|
||||
should one be required. If an older version of setuptools is installed,
|
||||
this routine will print a message to ``sys.stderr`` and raise SystemExit in
|
||||
an attempt to abort the calling script.
|
||||
"""
|
||||
# Work around a hack in the ez_setup.py file from simplejson==1.7.3.
|
||||
if min_version:
|
||||
version = min_version
|
||||
|
||||
was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
|
||||
def do_download():
|
||||
egg = download_setuptools(version, download_base, to_dir, download_delay)
|
||||
sys.path.insert(0, egg)
|
||||
import setuptools; setuptools.bootstrap_install_from = egg
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
return do_download()
|
||||
try:
|
||||
pkg_resources.require("setuptools>="+version); return
|
||||
except pkg_resources.VersionConflict, e:
|
||||
if was_imported:
|
||||
print >>sys.stderr, (
|
||||
"The required version of setuptools (>=%s) is not available, and\n"
|
||||
"can't be installed while this script is running. Please install\n"
|
||||
" a more recent version first, using 'easy_install -U setuptools'."
|
||||
"\n\n(Currently using %r)"
|
||||
) % (version, e.args[0])
|
||||
sys.exit(2)
|
||||
else:
|
||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
||||
return do_download()
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return do_download()
|
||||
|
||||
def download_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
delay = 15
|
||||
):
|
||||
"""Download setuptools from a specified location and return its filename
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end
|
||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||
`delay` is the number of seconds to pause before an actual download attempt.
|
||||
"""
|
||||
import urllib2, shutil
|
||||
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
|
||||
url = download_base + egg_name
|
||||
saveto = os.path.join(to_dir, egg_name)
|
||||
src = dst = None
|
||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||
try:
|
||||
from distutils import log
|
||||
if delay:
|
||||
log.warn("""
|
||||
---------------------------------------------------------------------------
|
||||
This script requires setuptools version %s to run (even to display
|
||||
help). I will attempt to download it for you (from
|
||||
%s), but
|
||||
you may need to enable firewall access for this script first.
|
||||
I will start the download in %d seconds.
|
||||
|
||||
(Note: if this machine does not have network access, please obtain the file
|
||||
|
||||
%s
|
||||
|
||||
and place it in this directory before rerunning this script.)
|
||||
---------------------------------------------------------------------------""",
|
||||
version, download_base, delay, url
|
||||
); from time import sleep; sleep(delay)
|
||||
log.warn("Downloading %s", url)
|
||||
src = urllib2.urlopen(url)
|
||||
# Read/write all in one block, so we don't create a corrupt file
|
||||
# if the download is interrupted.
|
||||
data = _validate_md5(egg_name, src.read())
|
||||
dst = open(saveto,"wb"); dst.write(data)
|
||||
finally:
|
||||
if src: src.close()
|
||||
if dst: dst.close()
|
||||
return os.path.realpath(saveto)
|
||||
|
||||
def main(argv, version=DEFAULT_VERSION):
|
||||
"""Install or upgrade setuptools and EasyInstall"""
|
||||
try:
|
||||
import setuptools
|
||||
except ImportError:
|
||||
egg = None
|
||||
try:
|
||||
egg = download_setuptools(version, delay=0)
|
||||
sys.path.insert(0,egg)
|
||||
from setuptools.command.easy_install import main
|
||||
return main(list(argv)+[egg]) # we're done here
|
||||
finally:
|
||||
if egg and os.path.exists(egg):
|
||||
os.unlink(egg)
|
||||
else:
|
||||
if setuptools.__version__ == '0.0.1':
|
||||
print >>sys.stderr, (
|
||||
"You have an obsolete version of setuptools installed. Please\n"
|
||||
"remove it from your system entirely before rerunning this script."
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
req = "setuptools>="+version
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(req)
|
||||
except pkg_resources.VersionConflict:
|
||||
try:
|
||||
from setuptools.command.easy_install import main
|
||||
except ImportError:
|
||||
from easy_install import main
|
||||
main(list(argv)+[download_setuptools(delay=0)])
|
||||
sys.exit(0) # try to force an exit
|
||||
else:
|
||||
if argv:
|
||||
from setuptools.command.easy_install import main
|
||||
main(argv)
|
||||
else:
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
|
||||
|
||||
def update_md5(filenames):
|
||||
"""Update our built-in md5 registry"""
|
||||
|
||||
import re
|
||||
from md5 import md5
|
||||
|
||||
for name in filenames:
|
||||
base = os.path.basename(name)
|
||||
f = open(name,'rb')
|
||||
md5_data[base] = md5(f.read()).hexdigest()
|
||||
f.close()
|
||||
|
||||
data = [" %r: %r,\n" % it for it in md5_data.items()]
|
||||
data.sort()
|
||||
repl = "".join(data)
|
||||
|
||||
import inspect
|
||||
srcfile = inspect.getsourcefile(sys.modules[__name__])
|
||||
f = open(srcfile, 'rb'); src = f.read(); f.close()
|
||||
|
||||
match = re.search("\nmd5_data = {\n([^}]+)}", src)
|
||||
if not match:
|
||||
print >>sys.stderr, "Internal error!"
|
||||
sys.exit(2)
|
||||
|
||||
src = src[:match.start(1)] + repl + src[match.end(1):]
|
||||
f = open(srcfile,'w')
|
||||
f.write(src)
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
|
||||
update_md5(sys.argv[2:])
|
||||
else:
|
||||
main(sys.argv[1:])
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2008-2009 Canonical Ltd. All rights reserved.
|
||||
#
|
||||
# This file is part of wadllib
|
||||
#
|
||||
# wadllib is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
try:
|
||||
from setuptools import setup, find_packages
|
||||
except ImportError:
|
||||
import ez_setup
|
||||
ez_setup.use_setuptools()
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
# generic helpers primarily for the long_description
|
||||
def generate(*docname_or_string):
|
||||
res = []
|
||||
for value in docname_or_string:
|
||||
if value.endswith('.txt'):
|
||||
f = open(value)
|
||||
value = f.read()
|
||||
f.close()
|
||||
res.append(value)
|
||||
if not value.endswith('\n'):
|
||||
res.append('')
|
||||
return '\n'.join(res)
|
||||
# end generic helpers
|
||||
|
||||
__version__ = open("src/wadllib/version.txt").read().strip()
|
||||
|
||||
install_requires = [
|
||||
'setuptools',
|
||||
'lazr.uri',
|
||||
]
|
||||
|
||||
setup(
|
||||
name='wadllib',
|
||||
version=__version__,
|
||||
packages=find_packages('src'),
|
||||
package_dir={'':'src'},
|
||||
package_data={
|
||||
'wadllib': ['version.txt'],
|
||||
'': ['*.xml', '*.json'],
|
||||
},
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
maintainer='LAZR Developers',
|
||||
maintainer_email='lazr-developers@lists.launchpad.net',
|
||||
description=open('README.txt').readline().strip(),
|
||||
long_description=generate(
|
||||
'src/wadllib/README.txt',
|
||||
'src/wadllib/NEWS.txt'),
|
||||
license='LGPL v3',
|
||||
install_requires=install_requires,
|
||||
url='https://launchpad.net/wadllib',
|
||||
download_url= 'https://launchpad.net/wadllib/+download',
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
],
|
||||
extras_require=dict(
|
||||
docs=['Sphinx',
|
||||
'z3c.recipe.sphinxdoc']
|
||||
),
|
||||
test_suite='wadllib.tests',
|
||||
)
|
|
@ -0,0 +1,823 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: wadllib
|
||||
Version: 1.3.3
|
||||
Summary: Navigate HTTP resources using WADL files as guides.
|
||||
Home-page: https://launchpad.net/wadllib
|
||||
Maintainer: LAZR Developers
|
||||
Maintainer-email: lazr-developers@lists.launchpad.net
|
||||
License: LGPL v3
|
||||
Download-URL: https://launchpad.net/wadllib/+download
|
||||
Description: ..
|
||||
Copyright (C) 2008-2013 Canonical Ltd.
|
||||
|
||||
This file is part of wadllib.
|
||||
|
||||
wadllib is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation, version 3 of the License.
|
||||
|
||||
wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
wadllib
|
||||
*******
|
||||
|
||||
An Application object represents a web service described by a WADL
|
||||
file.
|
||||
|
||||
>>> import os
|
||||
>>> import sys
|
||||
>>> import pkg_resources
|
||||
>>> from wadllib.application import Application
|
||||
|
||||
The first argument to the Application constructor is the URL at which
|
||||
the WADL file was found. The second argument may be raw WADL markup.
|
||||
|
||||
>>> wadl_string = pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', 'launchpad-wadl.xml')
|
||||
>>> wadl = Application("http://api.launchpad.dev/beta/", wadl_string)
|
||||
|
||||
Or the second argument may be an open filehandle containing the markup.
|
||||
|
||||
>>> cleanups = []
|
||||
>>> def application_for(filename, url="http://www.example.com/"):
|
||||
... wadl_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', filename)
|
||||
... cleanups.append(wadl_stream)
|
||||
... return Application(url, wadl_stream)
|
||||
>>> wadl = application_for("launchpad-wadl.xml",
|
||||
... "http://api.launchpad.dev/beta/")
|
||||
|
||||
|
||||
Link navigation
|
||||
===============
|
||||
|
||||
The preferred technique for finding a resource is to start at one of
|
||||
the resources defined in the WADL file, and follow links. This code
|
||||
retrieves the definition of the root resource.
|
||||
|
||||
>>> service_root = wadl.get_resource_by_path('')
|
||||
>>> service_root.url
|
||||
'http://api.launchpad.dev/beta/'
|
||||
>>> service_root.type_url
|
||||
'#service-root'
|
||||
|
||||
The service root resource supports GET.
|
||||
|
||||
>>> get_method = service_root.get_method('get')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
>>> get_method = service_root.get_method('GET')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
If we want to invoke this method, we send a GET request to the service
|
||||
root URL.
|
||||
|
||||
>>> get_method.name
|
||||
'get'
|
||||
>>> get_method.build_request_url()
|
||||
'http://api.launchpad.dev/beta/'
|
||||
|
||||
The WADL description of a resource knows which representations are
|
||||
available for that resource. In this case, the server root resource
|
||||
has a a JSON representation, and it defines parameters like
|
||||
'people_collection_link', a link to a list of people in Launchpad. We
|
||||
should be able to use the get_parameter() method to get the WADL
|
||||
definition of the 'people_collection_link' parameter and find out more
|
||||
about it--for instance, is it a link to another resource?
|
||||
|
||||
>>> def test_raises(exc_class, method, *args, **kwargs):
|
||||
... try:
|
||||
... method(*args, **kwargs)
|
||||
... except Exception:
|
||||
... # Contortion to support Python < 2.6 and >= 3 simultaneously.
|
||||
... e = sys.exc_info()[1]
|
||||
... if isinstance(e, exc_class):
|
||||
... print(e)
|
||||
... return
|
||||
... raise
|
||||
... raise Exception("Expected exception %s not raised" % exc_class)
|
||||
|
||||
>>> from wadllib.application import NoBoundRepresentationError
|
||||
>>> link_name = 'people_collection_link'
|
||||
>>> test_raises(
|
||||
... NoBoundRepresentationError, service_root.get_parameter, link_name)
|
||||
Resource is not bound to any representation, and no media media type was specified.
|
||||
|
||||
Oops. The code has no way to know whether 'people_collection_link' is
|
||||
a parameter of the JSON representation or some other kind of
|
||||
representation. We can pass a media type to get_parameter and let it
|
||||
know which representation the parameter lives in.
|
||||
|
||||
>>> link_parameter = service_root.get_parameter(
|
||||
... link_name, 'application/json')
|
||||
>>> test_raises(NoBoundRepresentationError, link_parameter.get_value)
|
||||
Resource is not bound to any representation.
|
||||
|
||||
Oops again. The parameter is available, but it has no value, because
|
||||
there's no actual data associated with the resource. The browser can
|
||||
look up the description of the GET method to make an actual GET
|
||||
request to the service root, and bind the resulting representation to
|
||||
the WADL description of the service root.
|
||||
|
||||
You can't bind just any representation to a WADL resource description.
|
||||
It has to be of a media type understood by the WADL description.
|
||||
|
||||
>>> from wadllib.application import UnsupportedMediaTypeError
|
||||
>>> test_raises(
|
||||
... UnsupportedMediaTypeError, service_root.bind,
|
||||
... '<html>Some HTML</html>', 'text/html')
|
||||
This resource doesn't define a representation for media type text/html
|
||||
|
||||
The WADL description of the service root resource has a JSON
|
||||
representation. Here it is.
|
||||
|
||||
>>> json_representation = service_root.get_representation_definition(
|
||||
... 'application/json')
|
||||
>>> json_representation.media_type
|
||||
'application/json'
|
||||
|
||||
We already have a WADL representation of the service root resource, so
|
||||
let's try binding it to that JSON representation. We use test JSON
|
||||
data from a file to simulate the result of a GET request to the
|
||||
service root.
|
||||
|
||||
>>> def get_testdata(filename):
|
||||
... return pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', filename + '.json')
|
||||
|
||||
>>> def bind_to_testdata(resource, filename):
|
||||
... return resource.bind(get_testdata(filename), 'application/json')
|
||||
|
||||
The return value is a new Resource object that's "bound" to that JSON
|
||||
test data.
|
||||
|
||||
>>> bound_service_root = bind_to_testdata(service_root, 'root')
|
||||
>>> sorted([param.name for param in bound_service_root.parameters()])
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> sorted(bound_service_root.parameter_names())
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> [method.id for method in bound_service_root.method_iter]
|
||||
['service-root-get']
|
||||
|
||||
Now the bound resource object has a JSON representation, and now
|
||||
'people_collection_link' makes sense. We can follow the
|
||||
'people_collection_link' to a new Resource object.
|
||||
|
||||
>>> link_parameter = bound_service_root.get_parameter(link_name)
|
||||
>>> link_parameter.style
|
||||
'plain'
|
||||
>>> print(link_parameter.get_value())
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource = link_parameter.linked_resource
|
||||
>>> personset_resource.__class__
|
||||
<class 'wadllib.application.Resource'>
|
||||
>>> print(personset_resource.url)
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource.type_url
|
||||
'http://api.launchpad.dev/beta/#people'
|
||||
|
||||
This new resource is a collection of people.
|
||||
|
||||
>>> personset_resource.id
|
||||
'people'
|
||||
|
||||
The "collection of people" resource supports a standard GET request as
|
||||
well as a special GET and an overloaded POST. The get_method() method
|
||||
is used to retrieve WADL definitions of the possible HTTP requests you
|
||||
might make. Here's how to get the WADL definition of the standard GET
|
||||
request.
|
||||
|
||||
>>> get_method = personset_resource.get_method('get')
|
||||
>>> get_method.id
|
||||
'people-get'
|
||||
|
||||
The method name passed into get_method() is treated case-insensitively.
|
||||
|
||||
>>> personset_resource.get_method('GET').id
|
||||
'people-get'
|
||||
|
||||
To invoke the special GET request, the client sets the 'ws.op' query
|
||||
parameter to the fixed string 'findPerson'.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'findPerson'})
|
||||
>>> find_method.id
|
||||
'people-findPerson'
|
||||
|
||||
Given an end-user's values for the non-fixed parameters, it's possible
|
||||
to get the URL that should be used to invoke the method.
|
||||
|
||||
>>> print(find_method.build_request_url(text='foo'))
|
||||
http://api.launchpad.dev/beta/people?text=foo&ws.op=findPerson
|
||||
|
||||
>>> print(find_method.build_request_url(
|
||||
... {'ws.op' : 'findPerson', 'text' : 'bar'}))
|
||||
http://api.launchpad.dev/beta/people?text=bar&ws.op=findPerson
|
||||
|
||||
An error occurs if the end-user gives an incorrect value for a fixed
|
||||
parameter value, or omits a required parameter.
|
||||
|
||||
>>> find_method.build_request_url()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'text'
|
||||
|
||||
>>> find_method.build_request_url(
|
||||
... {'ws.op' : 'findAPerson', 'text' : 'foo'})
|
||||
... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Value 'findAPerson' for parameter 'ws.op' conflicts
|
||||
with fixed value 'findPerson'
|
||||
|
||||
To invoke the overloaded POST request, the client sets the 'ws.op'
|
||||
query variable to the fixed string 'newTeam':
|
||||
|
||||
>>> create_team_method = personset_resource.get_method(
|
||||
... 'post', representation_params={'ws.op' : 'newTeam'})
|
||||
>>> create_team_method.id
|
||||
'people-newTeam'
|
||||
|
||||
findMethod() returns None when there's no WADL method matching the
|
||||
name or the fixed parameters.
|
||||
|
||||
>>> print(personset_resource.get_method('nosuchmethod'))
|
||||
None
|
||||
|
||||
>>> print(personset_resource.get_method(
|
||||
... 'post', query_params={'ws_op' : 'nosuchparam'}))
|
||||
None
|
||||
|
||||
Let's say the browser makes a GET request to the person set resource
|
||||
and gets back a representation. We can bind that representation to our
|
||||
description of the person set resource.
|
||||
|
||||
>>> bound_personset = bind_to_testdata(personset_resource, 'personset')
|
||||
>>> bound_personset.get_parameter("start").get_value()
|
||||
0
|
||||
>>> bound_personset.get_parameter("total_size").get_value()
|
||||
63
|
||||
|
||||
We can keep following links indefinitely, so long as we bind to a
|
||||
representation to each resource as we get it, and use the
|
||||
representation to find the next link.
|
||||
|
||||
>>> next_page_link = bound_personset.get_parameter("next_collection_link")
|
||||
>>> print(next_page_link.get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> page_two = next_page_link.linked_resource
|
||||
>>> bound_page_two = bind_to_testdata(page_two, 'personset-page2')
|
||||
>>> print(bound_page_two.url)
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> bound_page_two.get_parameter("start").get_value()
|
||||
5
|
||||
>>> print(bound_page_two.get_parameter("next_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=10&ws.size=5
|
||||
|
||||
Let's say the browser makes a POST request that invokes the 'newTeam'
|
||||
named operation. The response will include a number of HTTP headers,
|
||||
including 'Location', which points the way to the newly created team.
|
||||
|
||||
>>> headers = { 'Location' : 'http://api.launchpad.dev/~newteam' }
|
||||
>>> response = create_team_method.response.bind(headers)
|
||||
>>> location_parameter = response.get_parameter('Location')
|
||||
>>> location_parameter.get_value()
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team = location_parameter.linked_resource
|
||||
>>> new_team.url
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team.type_url
|
||||
'http://api.launchpad.dev/beta/#team'
|
||||
|
||||
Examining links
|
||||
---------------
|
||||
|
||||
The 'linked_resource' property of a parameter lets you follow a link
|
||||
to another object. The 'link' property of a parameter lets you examine
|
||||
links before following them.
|
||||
|
||||
>>> import json
|
||||
>>> links_wadl = application_for('links-wadl.xml')
|
||||
>>> service_root = links_wadl.get_resource_by_path('')
|
||||
>>> representation = json.dumps(
|
||||
... {'scalar_value': 'foo',
|
||||
... 'known_link': 'http://known/',
|
||||
... 'unknown_link': 'http://unknown/'})
|
||||
>>> bound_root = service_root.bind(representation)
|
||||
|
||||
>>> print(bound_root.get_parameter("scalar_value").link)
|
||||
None
|
||||
|
||||
>>> known_resource = bound_root.get_parameter("known_link")
|
||||
>>> unknown_resource = bound_root.get_parameter("unknown_link")
|
||||
|
||||
>>> print(known_resource.link.can_follow)
|
||||
True
|
||||
>>> print(unknown_resource.link.can_follow)
|
||||
False
|
||||
|
||||
A link whose type is unknown is a link to a resource not described by
|
||||
WADL. Following this link using .linked_resource or .link.follow will
|
||||
cause a wadllib error. You'll need to follow the link using a general
|
||||
HTTP library or some other tool.
|
||||
|
||||
>>> known_resource.link.follow
|
||||
<wadllib.application.Resource object ...>
|
||||
>>> known_resource.linked_resource
|
||||
<wadllib.application.Resource object ...>
|
||||
|
||||
>>> from wadllib.application import WADLError
|
||||
>>> test_raises(WADLError, getattr, unknown_resource.link, 'follow')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
>>> test_raises(WADLError, getattr, unknown_resource, 'linked_resource')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
Creating a Resource from a representation definition
|
||||
====================================================
|
||||
|
||||
Although every representation is a representation of some HTTP
|
||||
resource, an HTTP resource doesn't necessarily correspond directly to
|
||||
a WADL <resource> or <resource_type> tag. Sometimes a representation
|
||||
is defined within a WADL <method> tag.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'find'})
|
||||
>>> find_method.id
|
||||
'people-find'
|
||||
|
||||
>>> representation_definition = (
|
||||
... find_method.response.get_representation_definition(
|
||||
... 'application/json'))
|
||||
|
||||
There may be no WADL <resource> or <resource_type> tag for the
|
||||
representation defined here. That's why wadllib makes it possible to
|
||||
instantiate an anonymous Resource object using only the representation
|
||||
definition.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> anonymous_resource = Resource(
|
||||
... wadl, "http://foo/", representation_definition.tag)
|
||||
|
||||
We can bind this resource to a representation, as long as we
|
||||
explicitly pass in the representation definition.
|
||||
|
||||
>>> anonymous_resource = anonymous_resource.bind(
|
||||
... get_testdata('personset'), 'application/json',
|
||||
... representation_definition=representation_definition)
|
||||
|
||||
Once the resource is bound to a representation, we can get its
|
||||
parameter values.
|
||||
|
||||
>>> print(anonymous_resource.get_parameter(
|
||||
... 'total_size', 'application/json').get_value())
|
||||
63
|
||||
|
||||
Resource instantiation
|
||||
======================
|
||||
|
||||
If you happen to have the URL to an object lying around, and you know
|
||||
its type, you can construct a Resource object directly instead of
|
||||
by following links.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> limi_person = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person")
|
||||
>>> sorted([method.id for method in limi_person.method_iter])[:3]
|
||||
['person-acceptInvitationToBeMemberOf', 'person-addMember', 'person-declineInvitationToBeMemberOf']
|
||||
|
||||
>>> bound_limi = bind_to_testdata(limi_person, 'person-limi')
|
||||
>>> sorted(bound_limi.parameter_names())[:3]
|
||||
['admins_collection_link', 'confirmed_email_addresses_collection_link',
|
||||
'date_created']
|
||||
>>> languages_link = bound_limi.get_parameter("languages_collection_link")
|
||||
>>> print(languages_link.get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
You can bind a Resource to a representation when you create it.
|
||||
|
||||
>>> limi_data = get_testdata('person-limi')
|
||||
>>> bound_limi = Resource(
|
||||
... wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", limi_data,
|
||||
... "application/json")
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
By default the representation is treated as a string and processed
|
||||
according to the media type you pass into the Resource constructor. If
|
||||
you've already processed the representation, pass in False for the
|
||||
'representation_needs_processing' argument.
|
||||
|
||||
>>> from wadllib import _make_unicode
|
||||
>>> processed_limi_data = json.loads(_make_unicode(limi_data))
|
||||
>>> bound_limi = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", processed_limi_data,
|
||||
... "application/json", False)
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
Most of the time, the representation of a resource is of the type
|
||||
you'd get by sending a standard GET to that resource. If that's not
|
||||
the case, you can specify a RepresentationDefinition as the
|
||||
'representation_definition' argument to bind() or the Resource
|
||||
constructor, to show what the representation really looks like. Here's
|
||||
an example.
|
||||
|
||||
There's a method on a person resource such as bound_limi that's
|
||||
identified by a distinctive query argument: ws.op=getMembersByStatus.
|
||||
|
||||
>>> method = bound_limi.get_method(
|
||||
... query_params={'ws.op' : 'findPathToTeam'})
|
||||
|
||||
Invoke this method with a GET request and you'll get back a page from
|
||||
a list of people.
|
||||
|
||||
>>> people_page_repr_definition = (
|
||||
... method.response.get_representation_definition('application/json'))
|
||||
>>> people_page_repr_definition.tag.attrib['href']
|
||||
'http://api.launchpad.dev/beta/#person-page'
|
||||
|
||||
As it happens, we have a page from a list of people to use as test data.
|
||||
|
||||
>>> people_page_repr = get_testdata('personset')
|
||||
|
||||
If we bind the resource to the result of the method invocation as
|
||||
happened above, we don't be able to access any of the parameters we'd
|
||||
expect. wadllib will think the representation is of type
|
||||
'person-full', the default GET type for bound_limi.
|
||||
|
||||
>>> bad_people_page = bound_limi.bind(people_page_repr)
|
||||
>>> print(bad_people_page.get_parameter('total_size'))
|
||||
None
|
||||
|
||||
Since we don't actually have a 'person-full' representation, we won't
|
||||
be able to get values for the parameters of that kind of
|
||||
representation.
|
||||
|
||||
>>> bad_people_page.get_parameter('name').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'name'
|
||||
|
||||
So that's a dead end. *But*, if we pass the correct representation
|
||||
type into bind(), we can access the parameters associated with a
|
||||
'person-page' representation.
|
||||
|
||||
>>> people_page = bound_limi.bind(
|
||||
... people_page_repr,
|
||||
... representation_definition=people_page_repr_definition)
|
||||
>>> people_page.get_parameter('total_size').get_value()
|
||||
63
|
||||
|
||||
If you invoke the method and ask for a media type other than JSON, you
|
||||
won't get anything.
|
||||
|
||||
>>> print(method.response.get_representation_definition('text/html'))
|
||||
None
|
||||
|
||||
Data type conversion
|
||||
--------------------
|
||||
|
||||
The values of date and dateTime parameters are automatically converted to
|
||||
Python datetime objects.
|
||||
|
||||
>>> data_type_wadl = application_for('data-types-wadl.xml')
|
||||
>>> service_root = data_type_wadl.get_resource_by_path('')
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2007-10-20',
|
||||
... 'a_datetime': '2005-06-06T08:59:51.619713+00:00'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
A 'date' field can include a timestamp, and a 'datetime' field can
|
||||
omit one. wadllib will turn both into datetime objects.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2005-06-06T08:59:51.619713+00:00',
|
||||
... 'a_datetime': '2007-10-20'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
If a date or dateTime parameter has a null value, you get None. If the
|
||||
value is a string that can't be parsed to a datetime object, you get a
|
||||
ValueError.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': 'foo', 'a_datetime': None})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: foo
|
||||
>>> print(bound_root.get_parameter('a_datetime').get_value())
|
||||
None
|
||||
|
||||
Representation creation
|
||||
=======================
|
||||
|
||||
You must provide a representation when invoking certain methods. The
|
||||
representation() method helps you build one without knowing the
|
||||
details of how a representation is put together.
|
||||
|
||||
>>> create_team_method.build_representation(
|
||||
... display_name='Joe Bloggs', name='joebloggs')
|
||||
('application/x-www-form-urlencoded', 'display_name=Joe+Bloggs&name=joebloggs&ws.op=newTeam')
|
||||
|
||||
The return value of build_representation is a 2-tuple containing the
|
||||
media type of the built representation, and the string representation
|
||||
itself. Along with the resource's URL, this is all you need to send
|
||||
the representation to a web server.
|
||||
|
||||
>>> bound_limi.get_method('patch').build_representation(name='limi2')
|
||||
('application/json', '{"name": "limi2"}')
|
||||
|
||||
Representations may require values for certain parameters.
|
||||
|
||||
>>> create_team_method.build_representation()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'display_name'
|
||||
|
||||
>>> bound_limi.get_method('put').build_representation(name='limi2')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'mugshot_link'
|
||||
|
||||
Some representations may safely include binary data.
|
||||
|
||||
>>> binary_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', 'multipart-binary-wadl.xml')
|
||||
>>> cleanups.append(binary_stream)
|
||||
>>> binary_wadl = Application(
|
||||
... "http://www.example.com/", binary_stream)
|
||||
>>> service_root = binary_wadl.get_resource_by_path('')
|
||||
|
||||
Define a helper that processes the representation the same way
|
||||
zope.publisher would.
|
||||
|
||||
>>> import cgi
|
||||
>>> import io
|
||||
>>> def assert_message_parts(media_type, doc, expected):
|
||||
... if sys.version_info[0] == 3 and sys.version_info[1] < 3:
|
||||
... # We can't do much due to https://bugs.python.org/issue18013.
|
||||
... for value in expected:
|
||||
... if not isinstance(value, bytes):
|
||||
... value = value.encode('UTF-8')
|
||||
... assert value in doc
|
||||
... return
|
||||
... environ = {
|
||||
... 'REQUEST_METHOD': 'POST',
|
||||
... 'CONTENT_TYPE': media_type,
|
||||
... 'CONTENT_LENGTH': str(len(doc)),
|
||||
... }
|
||||
... kwargs = (
|
||||
... {'encoding': 'UTF-8'} if sys.version_info[0] >= 3 else {})
|
||||
... fs = cgi.FieldStorage(
|
||||
... fp=io.BytesIO(doc), environ=environ, keep_blank_values=1,
|
||||
... **kwargs)
|
||||
... values = []
|
||||
... def append_values(fields):
|
||||
... for field in fields:
|
||||
... if field.list:
|
||||
... append_values(field.list)
|
||||
... else:
|
||||
... values.append(field.value)
|
||||
... append_values(fs.list)
|
||||
... assert values == expected, (
|
||||
... 'Expected %s, got %s' % (expected, values))
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text", binary_field=b"\x01\x02\r\x81\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(media_type, doc, ['text', b'\x01\x02\r\x81\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\n", binary_field=b"\x01\x02\r\x81\n\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\n', b'\x01\x02\r\x81\n\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\r\nmore\r\n",
|
||||
... binary_field=b"\x01\x02\r\n\x81\r\x82\n")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\nmore\r\n', b'\x01\x02\r\n\x81\r\x82\n'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'text/unknown')
|
||||
>>> method.build_representation(field="value")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Unsupported media type: 'text/unknown'
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
Some parameters take values from a predefined list of options.
|
||||
|
||||
>>> option_wadl = application_for('options-wadl.xml')
|
||||
>>> definitions = option_wadl.representation_definitions
|
||||
>>> service_root = option_wadl.get_resource_by_path('')
|
||||
>>> definition = definitions['service-root-json']
|
||||
>>> param = definition.params(service_root)[0]
|
||||
>>> print(param.name)
|
||||
has_options
|
||||
>>> sorted([option.value for option in param.options])
|
||||
['Value 1', 'Value 2']
|
||||
|
||||
Such parameters cannot take values that are not in the list.
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Value 1'})
|
||||
{'has_options': 'Value 1'}
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Invalid value'})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value 'Invalid value' for parameter
|
||||
'has_options': valid values are: "Value 1", "Value 2"
|
||||
|
||||
|
||||
Error conditions
|
||||
================
|
||||
|
||||
You'll get None if you try to look up a nonexistent resource.
|
||||
|
||||
>>> print(wadl.get_resource_by_path('nosuchresource'))
|
||||
None
|
||||
|
||||
You'll get an exception if you try to look up a nonexistent resource
|
||||
type.
|
||||
|
||||
>>> print(wadl.get_resource_type('#nosuchtype'))
|
||||
Traceback (most recent call last):
|
||||
KeyError: 'No such XML ID: "#nosuchtype"'
|
||||
|
||||
You'll get None if you try to look up a method whose parameters don't
|
||||
match any defined method.
|
||||
|
||||
>>> print(bound_limi.get_method(
|
||||
... 'post', representation_params={ 'foo' : 'bar' }))
|
||||
None
|
||||
|
||||
.. cleanup
|
||||
>>> for stream in cleanups:
|
||||
... stream.close()
|
||||
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
||||
docs/*
|
||||
|
||||
================
|
||||
NEWS for wadllib
|
||||
================
|
||||
|
||||
1.3.3 (2018-07-20)
|
||||
==================
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
- Add tox testing support.
|
||||
- Implement a subset of MIME multipart/form-data encoding locally rather
|
||||
than using the standard library's email module, which doesn't have good
|
||||
handling of binary parts and corrupts bytes in them that look like line
|
||||
endings in various ways depending on the Python version. [bug=1729754]
|
||||
|
||||
1.3.2 (2013-02-25)
|
||||
==================
|
||||
|
||||
- Impose sort order to avoid test failures due to hash randomization.
|
||||
LP: #1132125
|
||||
- Be sure to close streams opened by pkg_resources.resource_stream() to avoid
|
||||
test suite complaints.
|
||||
|
||||
|
||||
1.3.1 (2012-03-22)
|
||||
==================
|
||||
|
||||
- Correct the double pass through _from_string causing datetime issues
|
||||
|
||||
|
||||
1.3.0 (2012-01-27)
|
||||
==================
|
||||
|
||||
- Add Python 3 compatibility
|
||||
|
||||
- Add the ability to inspect links before following them.
|
||||
|
||||
- Ensure that the sample data is packaged.
|
||||
|
||||
1.2.0 (2011-02-03)
|
||||
==================
|
||||
|
||||
- It's now possible to examine a link before following it, to see
|
||||
whether it has a WADL description or whether it needs to be fetched
|
||||
with a general HTTP client.
|
||||
|
||||
- It's now possible to iterate over a resource's Parameter objects
|
||||
with the .parameters() method.
|
||||
|
||||
1.1.8 (2010-10-27)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (yet again). This time to include the version.txt file
|
||||
used by setup.py.
|
||||
|
||||
1.1.7 (2010-10-26)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (again) to include the sample data used in tests.
|
||||
|
||||
1.1.6 (2010-10-21)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed to include the sample data used in tests.
|
||||
|
||||
1.1.5 (2010-05-04)
|
||||
==================
|
||||
|
||||
- Fixed a bug (Launchpad bug 274074) that prevented the lookup of
|
||||
parameter values in resources associated directly with a
|
||||
representation definition (rather than a resource type with a
|
||||
representation definition). Bug fix provided by James Westby.
|
||||
|
||||
1.1.4 (2009-09-15)
|
||||
==================
|
||||
|
||||
- Fixed a bug that crashed wadllib unless all parameters of a
|
||||
multipart representation were provided.
|
||||
|
||||
1.1.3 (2009-08-26)
|
||||
==================
|
||||
|
||||
- Remove unnecessary build dependencies.
|
||||
|
||||
- Add missing dependencies to setup file.
|
||||
|
||||
- Remove sys.path hack from setup.py.
|
||||
|
||||
1.1.2 (2009-08-20)
|
||||
==================
|
||||
|
||||
- Consistently handle different versions of simplejson.
|
||||
|
||||
1.1.1 (2009-07-14)
|
||||
==================
|
||||
|
||||
- Make wadllib aware of the <option> tags that go beneath <param> tags.
|
||||
|
||||
1.1 (2009-07-09)
|
||||
================
|
||||
|
||||
- Make wadllib capable of recognizing and generating
|
||||
multipart/form-data representations, including representations that
|
||||
incorporate binary parameters.
|
||||
|
||||
|
||||
1.0 (2009-03-23)
|
||||
================
|
||||
|
||||
- Initial release on PyPI
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Provides-Extra: docs
|
|
@ -0,0 +1,30 @@
|
|||
COPYING.txt
|
||||
HACKING.txt
|
||||
MANIFEST.in
|
||||
README.txt
|
||||
ez_setup.py
|
||||
setup.py
|
||||
src/wadllib/NEWS.txt
|
||||
src/wadllib/README.txt
|
||||
src/wadllib/__init__.py
|
||||
src/wadllib/application.py
|
||||
src/wadllib/iso_strptime.py
|
||||
src/wadllib/version.txt
|
||||
src/wadllib.egg-info/PKG-INFO
|
||||
src/wadllib.egg-info/SOURCES.txt
|
||||
src/wadllib.egg-info/dependency_links.txt
|
||||
src/wadllib.egg-info/not-zip-safe
|
||||
src/wadllib.egg-info/requires.txt
|
||||
src/wadllib.egg-info/top_level.txt
|
||||
src/wadllib/tests/__init__.py
|
||||
src/wadllib/tests/test_docs.py
|
||||
src/wadllib/tests/data/__init__.py
|
||||
src/wadllib/tests/data/data-types-wadl.xml
|
||||
src/wadllib/tests/data/launchpad-wadl.xml
|
||||
src/wadllib/tests/data/links-wadl.xml
|
||||
src/wadllib/tests/data/multipart-binary-wadl.xml
|
||||
src/wadllib/tests/data/options-wadl.xml
|
||||
src/wadllib/tests/data/person-limi.json
|
||||
src/wadllib/tests/data/personset-page2.json
|
||||
src/wadllib/tests/data/personset.json
|
||||
src/wadllib/tests/data/root.json
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
lazr.uri
|
||||
setuptools
|
||||
|
||||
[docs]
|
||||
Sphinx
|
||||
z3c.recipe.sphinxdoc
|
|
@ -0,0 +1 @@
|
|||
wadllib
|
|
@ -0,0 +1,112 @@
|
|||
================
|
||||
NEWS for wadllib
|
||||
================
|
||||
|
||||
1.3.3 (2018-07-20)
|
||||
==================
|
||||
|
||||
- Drop support for Python < 2.6.
|
||||
- Add tox testing support.
|
||||
- Implement a subset of MIME multipart/form-data encoding locally rather
|
||||
than using the standard library's email module, which doesn't have good
|
||||
handling of binary parts and corrupts bytes in them that look like line
|
||||
endings in various ways depending on the Python version. [bug=1729754]
|
||||
|
||||
1.3.2 (2013-02-25)
|
||||
==================
|
||||
|
||||
- Impose sort order to avoid test failures due to hash randomization.
|
||||
LP: #1132125
|
||||
- Be sure to close streams opened by pkg_resources.resource_stream() to avoid
|
||||
test suite complaints.
|
||||
|
||||
|
||||
1.3.1 (2012-03-22)
|
||||
==================
|
||||
|
||||
- Correct the double pass through _from_string causing datetime issues
|
||||
|
||||
|
||||
1.3.0 (2012-01-27)
|
||||
==================
|
||||
|
||||
- Add Python 3 compatibility
|
||||
|
||||
- Add the ability to inspect links before following them.
|
||||
|
||||
- Ensure that the sample data is packaged.
|
||||
|
||||
1.2.0 (2011-02-03)
|
||||
==================
|
||||
|
||||
- It's now possible to examine a link before following it, to see
|
||||
whether it has a WADL description or whether it needs to be fetched
|
||||
with a general HTTP client.
|
||||
|
||||
- It's now possible to iterate over a resource's Parameter objects
|
||||
with the .parameters() method.
|
||||
|
||||
1.1.8 (2010-10-27)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (yet again). This time to include the version.txt file
|
||||
used by setup.py.
|
||||
|
||||
1.1.7 (2010-10-26)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed (again) to include the sample data used in tests.
|
||||
|
||||
1.1.6 (2010-10-21)
|
||||
==================
|
||||
|
||||
- This revision contains no code changes, but the build system was
|
||||
changed to include the sample data used in tests.
|
||||
|
||||
1.1.5 (2010-05-04)
|
||||
==================
|
||||
|
||||
- Fixed a bug (Launchpad bug 274074) that prevented the lookup of
|
||||
parameter values in resources associated directly with a
|
||||
representation definition (rather than a resource type with a
|
||||
representation definition). Bug fix provided by James Westby.
|
||||
|
||||
1.1.4 (2009-09-15)
|
||||
==================
|
||||
|
||||
- Fixed a bug that crashed wadllib unless all parameters of a
|
||||
multipart representation were provided.
|
||||
|
||||
1.1.3 (2009-08-26)
|
||||
==================
|
||||
|
||||
- Remove unnecessary build dependencies.
|
||||
|
||||
- Add missing dependencies to setup file.
|
||||
|
||||
- Remove sys.path hack from setup.py.
|
||||
|
||||
1.1.2 (2009-08-20)
|
||||
==================
|
||||
|
||||
- Consistently handle different versions of simplejson.
|
||||
|
||||
1.1.1 (2009-07-14)
|
||||
==================
|
||||
|
||||
- Make wadllib aware of the <option> tags that go beneath <param> tags.
|
||||
|
||||
1.1 (2009-07-09)
|
||||
================
|
||||
|
||||
- Make wadllib capable of recognizing and generating
|
||||
multipart/form-data representations, including representations that
|
||||
incorporate binary parameters.
|
||||
|
||||
|
||||
1.0 (2009-03-23)
|
||||
================
|
||||
|
||||
- Initial release on PyPI
|
|
@ -0,0 +1,692 @@
|
|||
..
|
||||
Copyright (C) 2008-2013 Canonical Ltd.
|
||||
|
||||
This file is part of wadllib.
|
||||
|
||||
wadllib is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation, version 3 of the License.
|
||||
|
||||
wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
wadllib
|
||||
*******
|
||||
|
||||
An Application object represents a web service described by a WADL
|
||||
file.
|
||||
|
||||
>>> import os
|
||||
>>> import sys
|
||||
>>> import pkg_resources
|
||||
>>> from wadllib.application import Application
|
||||
|
||||
The first argument to the Application constructor is the URL at which
|
||||
the WADL file was found. The second argument may be raw WADL markup.
|
||||
|
||||
>>> wadl_string = pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', 'launchpad-wadl.xml')
|
||||
>>> wadl = Application("http://api.launchpad.dev/beta/", wadl_string)
|
||||
|
||||
Or the second argument may be an open filehandle containing the markup.
|
||||
|
||||
>>> cleanups = []
|
||||
>>> def application_for(filename, url="http://www.example.com/"):
|
||||
... wadl_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', filename)
|
||||
... cleanups.append(wadl_stream)
|
||||
... return Application(url, wadl_stream)
|
||||
>>> wadl = application_for("launchpad-wadl.xml",
|
||||
... "http://api.launchpad.dev/beta/")
|
||||
|
||||
|
||||
Link navigation
|
||||
===============
|
||||
|
||||
The preferred technique for finding a resource is to start at one of
|
||||
the resources defined in the WADL file, and follow links. This code
|
||||
retrieves the definition of the root resource.
|
||||
|
||||
>>> service_root = wadl.get_resource_by_path('')
|
||||
>>> service_root.url
|
||||
'http://api.launchpad.dev/beta/'
|
||||
>>> service_root.type_url
|
||||
'#service-root'
|
||||
|
||||
The service root resource supports GET.
|
||||
|
||||
>>> get_method = service_root.get_method('get')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
>>> get_method = service_root.get_method('GET')
|
||||
>>> get_method.id
|
||||
'service-root-get'
|
||||
|
||||
If we want to invoke this method, we send a GET request to the service
|
||||
root URL.
|
||||
|
||||
>>> get_method.name
|
||||
'get'
|
||||
>>> get_method.build_request_url()
|
||||
'http://api.launchpad.dev/beta/'
|
||||
|
||||
The WADL description of a resource knows which representations are
|
||||
available for that resource. In this case, the server root resource
|
||||
has a a JSON representation, and it defines parameters like
|
||||
'people_collection_link', a link to a list of people in Launchpad. We
|
||||
should be able to use the get_parameter() method to get the WADL
|
||||
definition of the 'people_collection_link' parameter and find out more
|
||||
about it--for instance, is it a link to another resource?
|
||||
|
||||
>>> def test_raises(exc_class, method, *args, **kwargs):
|
||||
... try:
|
||||
... method(*args, **kwargs)
|
||||
... except Exception:
|
||||
... # Contortion to support Python < 2.6 and >= 3 simultaneously.
|
||||
... e = sys.exc_info()[1]
|
||||
... if isinstance(e, exc_class):
|
||||
... print(e)
|
||||
... return
|
||||
... raise
|
||||
... raise Exception("Expected exception %s not raised" % exc_class)
|
||||
|
||||
>>> from wadllib.application import NoBoundRepresentationError
|
||||
>>> link_name = 'people_collection_link'
|
||||
>>> test_raises(
|
||||
... NoBoundRepresentationError, service_root.get_parameter, link_name)
|
||||
Resource is not bound to any representation, and no media media type was specified.
|
||||
|
||||
Oops. The code has no way to know whether 'people_collection_link' is
|
||||
a parameter of the JSON representation or some other kind of
|
||||
representation. We can pass a media type to get_parameter and let it
|
||||
know which representation the parameter lives in.
|
||||
|
||||
>>> link_parameter = service_root.get_parameter(
|
||||
... link_name, 'application/json')
|
||||
>>> test_raises(NoBoundRepresentationError, link_parameter.get_value)
|
||||
Resource is not bound to any representation.
|
||||
|
||||
Oops again. The parameter is available, but it has no value, because
|
||||
there's no actual data associated with the resource. The browser can
|
||||
look up the description of the GET method to make an actual GET
|
||||
request to the service root, and bind the resulting representation to
|
||||
the WADL description of the service root.
|
||||
|
||||
You can't bind just any representation to a WADL resource description.
|
||||
It has to be of a media type understood by the WADL description.
|
||||
|
||||
>>> from wadllib.application import UnsupportedMediaTypeError
|
||||
>>> test_raises(
|
||||
... UnsupportedMediaTypeError, service_root.bind,
|
||||
... '<html>Some HTML</html>', 'text/html')
|
||||
This resource doesn't define a representation for media type text/html
|
||||
|
||||
The WADL description of the service root resource has a JSON
|
||||
representation. Here it is.
|
||||
|
||||
>>> json_representation = service_root.get_representation_definition(
|
||||
... 'application/json')
|
||||
>>> json_representation.media_type
|
||||
'application/json'
|
||||
|
||||
We already have a WADL representation of the service root resource, so
|
||||
let's try binding it to that JSON representation. We use test JSON
|
||||
data from a file to simulate the result of a GET request to the
|
||||
service root.
|
||||
|
||||
>>> def get_testdata(filename):
|
||||
... return pkg_resources.resource_string(
|
||||
... 'wadllib.tests.data', filename + '.json')
|
||||
|
||||
>>> def bind_to_testdata(resource, filename):
|
||||
... return resource.bind(get_testdata(filename), 'application/json')
|
||||
|
||||
The return value is a new Resource object that's "bound" to that JSON
|
||||
test data.
|
||||
|
||||
>>> bound_service_root = bind_to_testdata(service_root, 'root')
|
||||
>>> sorted([param.name for param in bound_service_root.parameters()])
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> sorted(bound_service_root.parameter_names())
|
||||
['bugs_collection_link', 'people_collection_link']
|
||||
>>> [method.id for method in bound_service_root.method_iter]
|
||||
['service-root-get']
|
||||
|
||||
Now the bound resource object has a JSON representation, and now
|
||||
'people_collection_link' makes sense. We can follow the
|
||||
'people_collection_link' to a new Resource object.
|
||||
|
||||
>>> link_parameter = bound_service_root.get_parameter(link_name)
|
||||
>>> link_parameter.style
|
||||
'plain'
|
||||
>>> print(link_parameter.get_value())
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource = link_parameter.linked_resource
|
||||
>>> personset_resource.__class__
|
||||
<class 'wadllib.application.Resource'>
|
||||
>>> print(personset_resource.url)
|
||||
http://api.launchpad.dev/beta/people
|
||||
>>> personset_resource.type_url
|
||||
'http://api.launchpad.dev/beta/#people'
|
||||
|
||||
This new resource is a collection of people.
|
||||
|
||||
>>> personset_resource.id
|
||||
'people'
|
||||
|
||||
The "collection of people" resource supports a standard GET request as
|
||||
well as a special GET and an overloaded POST. The get_method() method
|
||||
is used to retrieve WADL definitions of the possible HTTP requests you
|
||||
might make. Here's how to get the WADL definition of the standard GET
|
||||
request.
|
||||
|
||||
>>> get_method = personset_resource.get_method('get')
|
||||
>>> get_method.id
|
||||
'people-get'
|
||||
|
||||
The method name passed into get_method() is treated case-insensitively.
|
||||
|
||||
>>> personset_resource.get_method('GET').id
|
||||
'people-get'
|
||||
|
||||
To invoke the special GET request, the client sets the 'ws.op' query
|
||||
parameter to the fixed string 'findPerson'.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'findPerson'})
|
||||
>>> find_method.id
|
||||
'people-findPerson'
|
||||
|
||||
Given an end-user's values for the non-fixed parameters, it's possible
|
||||
to get the URL that should be used to invoke the method.
|
||||
|
||||
>>> print(find_method.build_request_url(text='foo'))
|
||||
http://api.launchpad.dev/beta/people?text=foo&ws.op=findPerson
|
||||
|
||||
>>> print(find_method.build_request_url(
|
||||
... {'ws.op' : 'findPerson', 'text' : 'bar'}))
|
||||
http://api.launchpad.dev/beta/people?text=bar&ws.op=findPerson
|
||||
|
||||
An error occurs if the end-user gives an incorrect value for a fixed
|
||||
parameter value, or omits a required parameter.
|
||||
|
||||
>>> find_method.build_request_url()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'text'
|
||||
|
||||
>>> find_method.build_request_url(
|
||||
... {'ws.op' : 'findAPerson', 'text' : 'foo'})
|
||||
... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Value 'findAPerson' for parameter 'ws.op' conflicts
|
||||
with fixed value 'findPerson'
|
||||
|
||||
To invoke the overloaded POST request, the client sets the 'ws.op'
|
||||
query variable to the fixed string 'newTeam':
|
||||
|
||||
>>> create_team_method = personset_resource.get_method(
|
||||
... 'post', representation_params={'ws.op' : 'newTeam'})
|
||||
>>> create_team_method.id
|
||||
'people-newTeam'
|
||||
|
||||
findMethod() returns None when there's no WADL method matching the
|
||||
name or the fixed parameters.
|
||||
|
||||
>>> print(personset_resource.get_method('nosuchmethod'))
|
||||
None
|
||||
|
||||
>>> print(personset_resource.get_method(
|
||||
... 'post', query_params={'ws_op' : 'nosuchparam'}))
|
||||
None
|
||||
|
||||
Let's say the browser makes a GET request to the person set resource
|
||||
and gets back a representation. We can bind that representation to our
|
||||
description of the person set resource.
|
||||
|
||||
>>> bound_personset = bind_to_testdata(personset_resource, 'personset')
|
||||
>>> bound_personset.get_parameter("start").get_value()
|
||||
0
|
||||
>>> bound_personset.get_parameter("total_size").get_value()
|
||||
63
|
||||
|
||||
We can keep following links indefinitely, so long as we bind to a
|
||||
representation to each resource as we get it, and use the
|
||||
representation to find the next link.
|
||||
|
||||
>>> next_page_link = bound_personset.get_parameter("next_collection_link")
|
||||
>>> print(next_page_link.get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> page_two = next_page_link.linked_resource
|
||||
>>> bound_page_two = bind_to_testdata(page_two, 'personset-page2')
|
||||
>>> print(bound_page_two.url)
|
||||
http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5
|
||||
>>> bound_page_two.get_parameter("start").get_value()
|
||||
5
|
||||
>>> print(bound_page_two.get_parameter("next_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/people?ws.start=10&ws.size=5
|
||||
|
||||
Let's say the browser makes a POST request that invokes the 'newTeam'
|
||||
named operation. The response will include a number of HTTP headers,
|
||||
including 'Location', which points the way to the newly created team.
|
||||
|
||||
>>> headers = { 'Location' : 'http://api.launchpad.dev/~newteam' }
|
||||
>>> response = create_team_method.response.bind(headers)
|
||||
>>> location_parameter = response.get_parameter('Location')
|
||||
>>> location_parameter.get_value()
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team = location_parameter.linked_resource
|
||||
>>> new_team.url
|
||||
'http://api.launchpad.dev/~newteam'
|
||||
>>> new_team.type_url
|
||||
'http://api.launchpad.dev/beta/#team'
|
||||
|
||||
Examining links
|
||||
---------------
|
||||
|
||||
The 'linked_resource' property of a parameter lets you follow a link
|
||||
to another object. The 'link' property of a parameter lets you examine
|
||||
links before following them.
|
||||
|
||||
>>> import json
|
||||
>>> links_wadl = application_for('links-wadl.xml')
|
||||
>>> service_root = links_wadl.get_resource_by_path('')
|
||||
>>> representation = json.dumps(
|
||||
... {'scalar_value': 'foo',
|
||||
... 'known_link': 'http://known/',
|
||||
... 'unknown_link': 'http://unknown/'})
|
||||
>>> bound_root = service_root.bind(representation)
|
||||
|
||||
>>> print(bound_root.get_parameter("scalar_value").link)
|
||||
None
|
||||
|
||||
>>> known_resource = bound_root.get_parameter("known_link")
|
||||
>>> unknown_resource = bound_root.get_parameter("unknown_link")
|
||||
|
||||
>>> print(known_resource.link.can_follow)
|
||||
True
|
||||
>>> print(unknown_resource.link.can_follow)
|
||||
False
|
||||
|
||||
A link whose type is unknown is a link to a resource not described by
|
||||
WADL. Following this link using .linked_resource or .link.follow will
|
||||
cause a wadllib error. You'll need to follow the link using a general
|
||||
HTTP library or some other tool.
|
||||
|
||||
>>> known_resource.link.follow
|
||||
<wadllib.application.Resource object ...>
|
||||
>>> known_resource.linked_resource
|
||||
<wadllib.application.Resource object ...>
|
||||
|
||||
>>> from wadllib.application import WADLError
|
||||
>>> test_raises(WADLError, getattr, unknown_resource.link, 'follow')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
>>> test_raises(WADLError, getattr, unknown_resource, 'linked_resource')
|
||||
Cannot follow a link when the target has no WADL
|
||||
description. Try using a general HTTP client instead.
|
||||
|
||||
Creating a Resource from a representation definition
|
||||
====================================================
|
||||
|
||||
Although every representation is a representation of some HTTP
|
||||
resource, an HTTP resource doesn't necessarily correspond directly to
|
||||
a WADL <resource> or <resource_type> tag. Sometimes a representation
|
||||
is defined within a WADL <method> tag.
|
||||
|
||||
>>> find_method = personset_resource.get_method(
|
||||
... query_params={'ws.op' : 'find'})
|
||||
>>> find_method.id
|
||||
'people-find'
|
||||
|
||||
>>> representation_definition = (
|
||||
... find_method.response.get_representation_definition(
|
||||
... 'application/json'))
|
||||
|
||||
There may be no WADL <resource> or <resource_type> tag for the
|
||||
representation defined here. That's why wadllib makes it possible to
|
||||
instantiate an anonymous Resource object using only the representation
|
||||
definition.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> anonymous_resource = Resource(
|
||||
... wadl, "http://foo/", representation_definition.tag)
|
||||
|
||||
We can bind this resource to a representation, as long as we
|
||||
explicitly pass in the representation definition.
|
||||
|
||||
>>> anonymous_resource = anonymous_resource.bind(
|
||||
... get_testdata('personset'), 'application/json',
|
||||
... representation_definition=representation_definition)
|
||||
|
||||
Once the resource is bound to a representation, we can get its
|
||||
parameter values.
|
||||
|
||||
>>> print(anonymous_resource.get_parameter(
|
||||
... 'total_size', 'application/json').get_value())
|
||||
63
|
||||
|
||||
Resource instantiation
|
||||
======================
|
||||
|
||||
If you happen to have the URL to an object lying around, and you know
|
||||
its type, you can construct a Resource object directly instead of
|
||||
by following links.
|
||||
|
||||
>>> from wadllib.application import Resource
|
||||
>>> limi_person = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person")
|
||||
>>> sorted([method.id for method in limi_person.method_iter])[:3]
|
||||
['person-acceptInvitationToBeMemberOf', 'person-addMember', 'person-declineInvitationToBeMemberOf']
|
||||
|
||||
>>> bound_limi = bind_to_testdata(limi_person, 'person-limi')
|
||||
>>> sorted(bound_limi.parameter_names())[:3]
|
||||
['admins_collection_link', 'confirmed_email_addresses_collection_link',
|
||||
'date_created']
|
||||
>>> languages_link = bound_limi.get_parameter("languages_collection_link")
|
||||
>>> print(languages_link.get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
You can bind a Resource to a representation when you create it.
|
||||
|
||||
>>> limi_data = get_testdata('person-limi')
|
||||
>>> bound_limi = Resource(
|
||||
... wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", limi_data,
|
||||
... "application/json")
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
By default the representation is treated as a string and processed
|
||||
according to the media type you pass into the Resource constructor. If
|
||||
you've already processed the representation, pass in False for the
|
||||
'representation_needs_processing' argument.
|
||||
|
||||
>>> from wadllib import _make_unicode
|
||||
>>> processed_limi_data = json.loads(_make_unicode(limi_data))
|
||||
>>> bound_limi = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
|
||||
... "http://api.launchpad.dev/beta/#person", processed_limi_data,
|
||||
... "application/json", False)
|
||||
>>> print(bound_limi.get_parameter(
|
||||
... "languages_collection_link").get_value())
|
||||
http://api.launchpad.dev/beta/~limi/languages
|
||||
|
||||
Most of the time, the representation of a resource is of the type
|
||||
you'd get by sending a standard GET to that resource. If that's not
|
||||
the case, you can specify a RepresentationDefinition as the
|
||||
'representation_definition' argument to bind() or the Resource
|
||||
constructor, to show what the representation really looks like. Here's
|
||||
an example.
|
||||
|
||||
There's a method on a person resource such as bound_limi that's
|
||||
identified by a distinctive query argument: ws.op=getMembersByStatus.
|
||||
|
||||
>>> method = bound_limi.get_method(
|
||||
... query_params={'ws.op' : 'findPathToTeam'})
|
||||
|
||||
Invoke this method with a GET request and you'll get back a page from
|
||||
a list of people.
|
||||
|
||||
>>> people_page_repr_definition = (
|
||||
... method.response.get_representation_definition('application/json'))
|
||||
>>> people_page_repr_definition.tag.attrib['href']
|
||||
'http://api.launchpad.dev/beta/#person-page'
|
||||
|
||||
As it happens, we have a page from a list of people to use as test data.
|
||||
|
||||
>>> people_page_repr = get_testdata('personset')
|
||||
|
||||
If we bind the resource to the result of the method invocation as
|
||||
happened above, we don't be able to access any of the parameters we'd
|
||||
expect. wadllib will think the representation is of type
|
||||
'person-full', the default GET type for bound_limi.
|
||||
|
||||
>>> bad_people_page = bound_limi.bind(people_page_repr)
|
||||
>>> print(bad_people_page.get_parameter('total_size'))
|
||||
None
|
||||
|
||||
Since we don't actually have a 'person-full' representation, we won't
|
||||
be able to get values for the parameters of that kind of
|
||||
representation.
|
||||
|
||||
>>> bad_people_page.get_parameter('name').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'name'
|
||||
|
||||
So that's a dead end. *But*, if we pass the correct representation
|
||||
type into bind(), we can access the parameters associated with a
|
||||
'person-page' representation.
|
||||
|
||||
>>> people_page = bound_limi.bind(
|
||||
... people_page_repr,
|
||||
... representation_definition=people_page_repr_definition)
|
||||
>>> people_page.get_parameter('total_size').get_value()
|
||||
63
|
||||
|
||||
If you invoke the method and ask for a media type other than JSON, you
|
||||
won't get anything.
|
||||
|
||||
>>> print(method.response.get_representation_definition('text/html'))
|
||||
None
|
||||
|
||||
Data type conversion
|
||||
--------------------
|
||||
|
||||
The values of date and dateTime parameters are automatically converted to
|
||||
Python datetime objects.
|
||||
|
||||
>>> data_type_wadl = application_for('data-types-wadl.xml')
|
||||
>>> service_root = data_type_wadl.get_resource_by_path('')
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2007-10-20',
|
||||
... 'a_datetime': '2005-06-06T08:59:51.619713+00:00'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
A 'date' field can include a timestamp, and a 'datetime' field can
|
||||
omit one. wadllib will turn both into datetime objects.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': '2005-06-06T08:59:51.619713+00:00',
|
||||
... 'a_datetime': '2007-10-20'})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
|
||||
>>> bound_root.get_parameter('a_datetime').get_value()
|
||||
datetime.datetime(2007, 10, 20, 0, 0)
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
datetime.datetime(2005, 6, 6, 8, ...)
|
||||
|
||||
If a date or dateTime parameter has a null value, you get None. If the
|
||||
value is a string that can't be parsed to a datetime object, you get a
|
||||
ValueError.
|
||||
|
||||
>>> representation = json.dumps(
|
||||
... {'a_date': 'foo', 'a_datetime': None})
|
||||
>>> bound_root = service_root.bind(representation, 'application/json')
|
||||
>>> bound_root.get_parameter('a_date').get_value()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: foo
|
||||
>>> print(bound_root.get_parameter('a_datetime').get_value())
|
||||
None
|
||||
|
||||
Representation creation
|
||||
=======================
|
||||
|
||||
You must provide a representation when invoking certain methods. The
|
||||
representation() method helps you build one without knowing the
|
||||
details of how a representation is put together.
|
||||
|
||||
>>> create_team_method.build_representation(
|
||||
... display_name='Joe Bloggs', name='joebloggs')
|
||||
('application/x-www-form-urlencoded', 'display_name=Joe+Bloggs&name=joebloggs&ws.op=newTeam')
|
||||
|
||||
The return value of build_representation is a 2-tuple containing the
|
||||
media type of the built representation, and the string representation
|
||||
itself. Along with the resource's URL, this is all you need to send
|
||||
the representation to a web server.
|
||||
|
||||
>>> bound_limi.get_method('patch').build_representation(name='limi2')
|
||||
('application/json', '{"name": "limi2"}')
|
||||
|
||||
Representations may require values for certain parameters.
|
||||
|
||||
>>> create_team_method.build_representation()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'display_name'
|
||||
|
||||
>>> bound_limi.get_method('put').build_representation(name='limi2')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No value for required parameter 'mugshot_link'
|
||||
|
||||
Some representations may safely include binary data.
|
||||
|
||||
>>> binary_stream = pkg_resources.resource_stream(
|
||||
... 'wadllib.tests.data', 'multipart-binary-wadl.xml')
|
||||
>>> cleanups.append(binary_stream)
|
||||
>>> binary_wadl = Application(
|
||||
... "http://www.example.com/", binary_stream)
|
||||
>>> service_root = binary_wadl.get_resource_by_path('')
|
||||
|
||||
Define a helper that processes the representation the same way
|
||||
zope.publisher would.
|
||||
|
||||
>>> import cgi
|
||||
>>> import io
|
||||
>>> def assert_message_parts(media_type, doc, expected):
|
||||
... if sys.version_info[0] == 3 and sys.version_info[1] < 3:
|
||||
... # We can't do much due to https://bugs.python.org/issue18013.
|
||||
... for value in expected:
|
||||
... if not isinstance(value, bytes):
|
||||
... value = value.encode('UTF-8')
|
||||
... assert value in doc
|
||||
... return
|
||||
... environ = {
|
||||
... 'REQUEST_METHOD': 'POST',
|
||||
... 'CONTENT_TYPE': media_type,
|
||||
... 'CONTENT_LENGTH': str(len(doc)),
|
||||
... }
|
||||
... kwargs = (
|
||||
... {'encoding': 'UTF-8'} if sys.version_info[0] >= 3 else {})
|
||||
... fs = cgi.FieldStorage(
|
||||
... fp=io.BytesIO(doc), environ=environ, keep_blank_values=1,
|
||||
... **kwargs)
|
||||
... values = []
|
||||
... def append_values(fields):
|
||||
... for field in fields:
|
||||
... if field.list:
|
||||
... append_values(field.list)
|
||||
... else:
|
||||
... values.append(field.value)
|
||||
... append_values(fs.list)
|
||||
... assert values == expected, (
|
||||
... 'Expected %s, got %s' % (expected, values))
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text", binary_field=b"\x01\x02\r\x81\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(media_type, doc, ['text', b'\x01\x02\r\x81\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\n", binary_field=b"\x01\x02\r\x81\n\r")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\n', b'\x01\x02\r\x81\n\r'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'multipart/form-data')
|
||||
>>> media_type, doc = method.build_representation(
|
||||
... text_field="text\r\nmore\r\n",
|
||||
... binary_field=b"\x01\x02\r\n\x81\r\x82\n")
|
||||
>>> print(media_type)
|
||||
multipart/form-data; boundary=...
|
||||
>>> assert_message_parts(
|
||||
... media_type, doc, ['text\r\nmore\r\n', b'\x01\x02\r\n\x81\r\x82\n'])
|
||||
|
||||
>>> method = service_root.get_method('post', 'text/unknown')
|
||||
>>> method.build_representation(field="value")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Unsupported media type: 'text/unknown'
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
Some parameters take values from a predefined list of options.
|
||||
|
||||
>>> option_wadl = application_for('options-wadl.xml')
|
||||
>>> definitions = option_wadl.representation_definitions
|
||||
>>> service_root = option_wadl.get_resource_by_path('')
|
||||
>>> definition = definitions['service-root-json']
|
||||
>>> param = definition.params(service_root)[0]
|
||||
>>> print(param.name)
|
||||
has_options
|
||||
>>> sorted([option.value for option in param.options])
|
||||
['Value 1', 'Value 2']
|
||||
|
||||
Such parameters cannot take values that are not in the list.
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Value 1'})
|
||||
{'has_options': 'Value 1'}
|
||||
|
||||
>>> definition.validate_param_values(
|
||||
... [param], {'has_options': 'Invalid value'})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value 'Invalid value' for parameter
|
||||
'has_options': valid values are: "Value 1", "Value 2"
|
||||
|
||||
|
||||
Error conditions
|
||||
================
|
||||
|
||||
You'll get None if you try to look up a nonexistent resource.
|
||||
|
||||
>>> print(wadl.get_resource_by_path('nosuchresource'))
|
||||
None
|
||||
|
||||
You'll get an exception if you try to look up a nonexistent resource
|
||||
type.
|
||||
|
||||
>>> print(wadl.get_resource_type('#nosuchtype'))
|
||||
Traceback (most recent call last):
|
||||
KeyError: 'No such XML ID: "#nosuchtype"'
|
||||
|
||||
You'll get None if you try to look up a method whose parameters don't
|
||||
match any defined method.
|
||||
|
||||
>>> print(bound_limi.get_method(
|
||||
... 'post', representation_params={ 'foo' : 'bar' }))
|
||||
None
|
||||
|
||||
.. cleanup
|
||||
>>> for stream in cleanups:
|
||||
... stream.close()
|
||||
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
||||
docs/*
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2008-2009 Canonical Ltd. All rights reserved.
|
||||
|
||||
# This file is part of wadllib.
|
||||
#
|
||||
# wadllib is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with wadllib. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
|
||||
import pkg_resources
|
||||
__version__ = pkg_resources.resource_string(
|
||||
"wadllib", "version.txt").strip()
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
_string_types = str
|
||||
def _make_unicode(b):
|
||||
if hasattr(b, 'decode'):
|
||||
return b.decode()
|
||||
else:
|
||||
return str(b)
|
||||
else:
|
||||
_string_types = basestring
|
||||
_make_unicode = unicode
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,86 @@
|
|||
# Copyright 2009 Canonical Ltd. All rights reserved.
|
||||
|
||||
# This file is part of wadllib.
|
||||
#
|
||||
# wadllib is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
Parser for ISO 8601 time strings
|
||||
================================
|
||||
|
||||
>>> d = iso_strptime("2008-01-07T05:30:30.345323+03:00")
|
||||
>>> d
|
||||
datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(10800))
|
||||
>>> d.timetuple()
|
||||
(2008, 1, 7, 5, 30, 30, 0, 7, 0)
|
||||
>>> d.utctimetuple()
|
||||
(2008, 1, 7, 2, 30, 30, 0, 7, 0)
|
||||
>>> iso_strptime("2008-01-07T05:30:30.345323-03:00")
|
||||
datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(-10800))
|
||||
>>> iso_strptime("2008-01-07T05:30:30.345323")
|
||||
datetime.datetime(2008, 1, 7, 5, 30, 30, 345323)
|
||||
>>> iso_strptime("2008-01-07T05:30:30")
|
||||
datetime.datetime(2008, 1, 7, 5, 30, 30)
|
||||
>>> iso_strptime("2008-01-07T05:30:30+02:00")
|
||||
datetime.datetime(2008, 1, 7, 5, 30, 30, tzinfo=TimeZone(7200))
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
import datetime
|
||||
|
||||
RE_TIME = re.compile(r"""^
|
||||
# pattern matching date
|
||||
(?P<year>\d{4})\-(?P<month>\d{2})\-(?P<day>\d{2})
|
||||
# separator
|
||||
T
|
||||
# pattern matching time
|
||||
(?P<hour>\d{2})\:(?P<minutes>\d{2})\:(?P<seconds>\d{2})
|
||||
# pattern matching optional microseconds
|
||||
(\.(?P<microseconds>\d{6}))?
|
||||
# pattern matching optional timezone offset
|
||||
(?P<tz_offset>[\-\+]\d{2}\:\d{2})?
|
||||
$""", re.VERBOSE)
|
||||
|
||||
class TimeZone(datetime.tzinfo):
|
||||
|
||||
def __init__(self, tz_string):
|
||||
hours, minutes = tz_string.lstrip("-+").split(":")
|
||||
self.stdoffset = datetime.timedelta(hours=int(hours),
|
||||
minutes=int(minutes))
|
||||
if tz_string.startswith("-"):
|
||||
self.stdoffset *= -1
|
||||
|
||||
def __repr__(self):
|
||||
return "TimeZone(%s)" % (
|
||||
self.stdoffset.days*24*60*60 + self.stdoffset.seconds)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.stdoffset
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta(0)
|
||||
|
||||
|
||||
|
||||
def iso_strptime(time_str):
|
||||
x = RE_TIME.match(time_str)
|
||||
if not x:
|
||||
raise ValueError
|
||||
d = datetime.datetime(int(x.group("year")), int(x.group("month")),
|
||||
int(x.group("day")), int(x.group("hour")), int(x.group("minutes")),
|
||||
int(x.group("seconds")))
|
||||
if x.group("microseconds"):
|
||||
d = d.replace(microsecond=int(x.group("microseconds")))
|
||||
if x.group("tz_offset"):
|
||||
d = d.replace(tzinfo=TimeZone(x.group("tz_offset")))
|
||||
return d
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2008-2009 Canonical Ltd. All rights reserved.
|
||||
|
||||
# This file is part of wadllib.
|
||||
#
|
||||
# wadllib is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# wadllib is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with wadllib. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://research.sun.com/wadl/2006/10"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd">
|
||||
<resources base="http://www.example.com/">
|
||||
<resource path="" type="#service-root"/>
|
||||
</resources>
|
||||
|
||||
<resource_type id="service-root">
|
||||
<method name="GET" id="service-root-get">
|
||||
<response>
|
||||
<representation href="#service-root-json"/>
|
||||
</response>
|
||||
</method>
|
||||
</resource_type>
|
||||
|
||||
<representation id="service-root-json" mediaType="application/json">
|
||||
<param style="plain" required="true" type="xsd:date"
|
||||
path="$['a_date']" name="a_date" />
|
||||
<param style="plain" required="true" type="xsd:dateTime"
|
||||
path="$['a_datetime']" name="a_datetime" />
|
||||
</representation>
|
||||
</application>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0"?>
|
||||
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://research.sun.com/wadl/2006/10"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd">
|
||||
<resources base="http://www.example.com/">
|
||||
<resource path="" type="#service-root"/>
|
||||
</resources>
|
||||
|
||||
<resource_type id="service-root">
|
||||
<method name="GET" id="service-root-get">
|
||||
<response>
|
||||
<representation href="#service-root-json"/>
|
||||
</response>
|
||||
</method>
|
||||
</resource_type>
|
||||
|
||||
<representation id="service-root-json" mediaType="application/json">
|
||||
<param style="plain" path="$['scalar_value']" name="scalar_value" />
|
||||
<param style="plain" path="$['known_link']" name="known_link">
|
||||
<link resource_type="#service-root"/>
|
||||
</param>
|
||||
<param style="plain" path="$['unknown_link']" name="unknown_link">
|
||||
<link />
|
||||
</param>
|
||||
</representation>
|
||||
</application>
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0"?>
|
||||
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://research.sun.com/wadl/2006/10"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd">
|
||||
<resources base="http://www.example.com/">
|
||||
<resource path="" type="#service-root"/>
|
||||
</resources>
|
||||
|
||||
<resource_type id="service-root">
|
||||
<method name="POST" id="service-root-operation">
|
||||
<request>
|
||||
<representation mediaType="multipart/form-data">
|
||||
<param style="plain" name="text_field" required="true" />
|
||||
<param style="plain" name="optional_field" />
|
||||
<param style="plain" name="binary_field" type="binary"
|
||||
required="true" />
|
||||
</representation>
|
||||
</request>
|
||||
</method>
|
||||
|
||||
<method name="POST" id="mystery-operation">
|
||||
<request>
|
||||
<representation mediaType="text/unknown">
|
||||
<param style="plain" name="field" required="true" />
|
||||
</representation>
|
||||
</request>
|
||||
</method>
|
||||
|
||||
</resource_type>
|
||||
</application>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://research.sun.com/wadl/2006/10"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd">
|
||||
<resources base="http://www.example.com/">
|
||||
<resource path="" type="#service-root"/>
|
||||
</resources>
|
||||
|
||||
<resource_type id="service-root">
|
||||
<method name="GET" id="service-root-get">
|
||||
<response>
|
||||
<representation href="#service-root-json"/>
|
||||
</response>
|
||||
</method>
|
||||
</resource_type>
|
||||
|
||||
<representation id="service-root-json" mediaType="application/json">
|
||||
<param style="plain" required="true" path="$['has_options]"
|
||||
name="has_options">
|
||||
<option value="Value 1" />
|
||||
<option value="Value 2" />
|
||||
</param>
|
||||
</representation>
|
||||
</application>
|
|
@ -0,0 +1 @@
|
|||
{"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/confirmed_email_addresses", "mailing_list_auto_subscribe_policy": "Ask me when I join a team", "team_owner_link": null, "members_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~limi", "preferred_email_address_link": null, "date_created": "2005-06-06T08:59:51.619713+00:00", "display_name": "Alexander Limi", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/expired_members", "homepage_content": null, "name": "limi", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~limi\/indirect_participations"}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"people_collection_link": "http:\/\/api.launchpad.dev\/beta\/people", "bugs_collection_link": "http:\/\/api.launchpad.dev\/beta\/bugs"}
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2009-2018 Canonical Ltd. All rights reserved.
|
||||
#
|
||||
# This file is part of wadllib
|
||||
#
|
||||
# wadllib is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, version 3 of the License.
|
||||
#
|
||||
# wadllib is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with wadllib. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Test harness."""
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
__all__ = [
|
||||
'load_tests',
|
||||
]
|
||||
|
||||
import __future__
|
||||
import atexit
|
||||
import doctest
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
|
||||
|
||||
DOCTEST_FLAGS = (
|
||||
doctest.ELLIPSIS |
|
||||
doctest.NORMALIZE_WHITESPACE |
|
||||
doctest.REPORT_NDIFF)
|
||||
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
doctest_files = [
|
||||
os.path.abspath(
|
||||
pkg_resources.resource_filename('wadllib', 'README.txt'))]
|
||||
if pkg_resources.resource_exists('wadllib', 'docs'):
|
||||
for name in pkg_resources.resource_listdir('wadllib', 'docs'):
|
||||
if name.endswith('.txt'):
|
||||
doctest_files.append(
|
||||
os.path.abspath(
|
||||
pkg_resources.resource_filename(
|
||||
'wadllib', 'docs/%s' % name)))
|
||||
globs = {
|
||||
future_item: getattr(__future__, future_item)
|
||||
for future_item in ('absolute_import', 'print_function')}
|
||||
kwargs = dict(
|
||||
module_relative=False, globs=globs, optionflags=DOCTEST_FLAGS)
|
||||
atexit.register(pkg_resources.cleanup_resources)
|
||||
tests.addTest(doctest.DocFileSuite(*doctest_files, **kwargs))
|
||||
return tests
|
|
@ -0,0 +1 @@
|
|||
1.3.3
|
Loading…
Reference in New Issue