././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3252187
wadllib-1.3.6/ 0000755 0001750 0001750 00000000000 00000000000 014642 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/COPYING.txt 0000644 0001750 0001750 00000016727 00000000000 016530 0 ustar 00cjwatson cjwatson 0000000 0000000 GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
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.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1626257220.0
wadllib-1.3.6/HACKING.rst 0000644 0001750 0001750 00000002060 00000000000 016436 0 ustar 00cjwatson cjwatson 0000000 0000000 ..
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 .
============
Introduction
============
To run this project's tests, use `tox `.
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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548740.0
wadllib-1.3.6/NEWS.rst 0000644 0001750 0001750 00000006570 00000000000 016160 0 ustar 00cjwatson cjwatson 0000000 0000000 ================
NEWS for wadllib
================
1.3.6 (2021-09-13)
==================
- Remove buildout support in favour of tox. [bug=922605]
- Adjust versioning strategy to avoid importing pkg_resources, which is slow
in large environments.
1.3.5 (2021-01-20)
==================
- Drop support for Python 3.2, 3.3, and 3.4.
- Accept Unicode parameter values again when performing multipart/form-data
encoding on Python 2 (broken in 1.3.3).
1.3.4 (2020-04-29)
==================
- Advertise support for Python 3.8.
- Add Python 3.9 compatibility by using xml.etree.ElementTree if
xml.etree.cElementTree does not exist. [bug=1870294]
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 tags that go beneath 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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3252187
wadllib-1.3.6/PKG-INFO 0000644 0001750 0001750 00000111432 00000000000 015741 0 ustar 00cjwatson cjwatson 0000000 0000000 Metadata-Version: 2.1
Name: wadllib
Version: 1.3.6
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 .
=======
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,
... 'Some 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__
>>> 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
>>> known_resource.linked_resource
>>> 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 or tag. Sometimes a representation
is defined within a WADL 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 or 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):
... 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=u"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=u"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', 'multipart/form-data')
>>> media_type, doc = method.build_representation(
... text_field=u"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()
================
NEWS for wadllib
================
1.3.6 (2021-09-13)
==================
- Remove buildout support in favour of tox. [bug=922605]
- Adjust versioning strategy to avoid importing pkg_resources, which is slow
in large environments.
1.3.5 (2021-01-20)
==================
- Drop support for Python 3.2, 3.3, and 3.4.
- Accept Unicode parameter values again when performing multipart/form-data
encoding on Python 2 (broken in 1.3.3).
1.3.4 (2020-04-29)
==================
- Advertise support for Python 3.8.
- Add Python 3.9 compatibility by using xml.etree.ElementTree if
xml.etree.cElementTree does not exist. [bug=1870294]
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 tags that go beneath 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 :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Provides-Extra: docs
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1532076792.0
wadllib-1.3.6/README.rst 0000644 0001750 0001750 00000001416 00000000000 016333 0 ustar 00cjwatson cjwatson 0000000 0000000 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 .
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3252187
wadllib-1.3.6/setup.cfg 0000644 0001750 0001750 00000000046 00000000000 016463 0 ustar 00cjwatson cjwatson 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548694.0
wadllib-1.3.6/setup.py 0000755 0001750 0001750 00000005513 00000000000 016363 0 ustar 00cjwatson cjwatson 0000000 0000000 #!/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 .
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):
marker = '.. pypi description ends here'
res = []
for value in docname_or_string:
if value.endswith('.rst'):
with open(value) as f:
value = f.read()
idx = value.find(marker)
if idx >= 0:
value = value[:idx]
res.append(value)
if not value.endswith('\n'):
res.append('')
return '\n'.join(res)
# end generic helpers
install_requires = [
'importlib-metadata; python_version < "3.8"',
'lazr.uri',
'setuptools',
]
setup(
name='wadllib',
version='1.3.6',
packages=find_packages('src'),
package_dir={'':'src'},
package_data={
'': ['*.xml', '*.json'],
},
include_package_data=True,
zip_safe=False,
maintainer='LAZR Developers',
maintainer_email='lazr-developers@lists.launchpad.net',
description=open('README.rst').readline().strip(),
long_description=generate(
'src/wadllib/docs/index.rst',
'NEWS.rst'),
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 :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
],
extras_require=dict(
docs=['Sphinx'],
),
test_suite='wadllib.tests',
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3132186
wadllib-1.3.6/src/ 0000755 0001750 0001750 00000000000 00000000000 015431 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3172188
wadllib-1.3.6/src/wadllib/ 0000755 0001750 0001750 00000000000 00000000000 017047 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548694.0
wadllib-1.3.6/src/wadllib/__init__.py 0000644 0001750 0001750 00000002124 00000000000 021157 0 ustar 00cjwatson cjwatson 0000000 0000000 # 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
# .
import sys
try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
__version__ = importlib_metadata.version("wadllib")
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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1611139666.0
wadllib-1.3.6/src/wadllib/application.py 0000644 0001750 0001750 00000135752 00000000000 021741 0 ustar 00cjwatson cjwatson 0000000 0000000 # Copyright 2008-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 .
"""Navigate the resources exposed by a web service.
The wadllib library helps a web client navigate the resources
exposed by a web service. The service defines its resources in a
single WADL file. wadllib parses this file and gives access to the
resources defined inside. The client code can see the capabilities of
a given resource and make the corresponding HTTP requests.
If a request returns a representation of the resource, the client can
bind the string representation to the wadllib Resource object.
"""
__metaclass__ = type
__all__ = [
'Application',
'Link',
'Method',
'NoBoundRepresentationError',
'Parameter',
'RepresentationDefinition',
'ResponseDefinition',
'Resource',
'ResourceType',
'WADLError',
]
import datetime
from email.utils import quote
import io
import json
import random
import re
import sys
import time
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from lazr.uri import URI, merge
from wadllib import (
_make_unicode,
_string_types,
)
from wadllib.iso_strptime import iso_strptime
NS_MAP = "xmlns:map"
XML_SCHEMA_NS_URI = 'http://www.w3.org/2001/XMLSchema'
def wadl_tag(tag_name):
"""Scope a tag name with the WADL namespace."""
return '{http://research.sun.com/wadl/2006/10}' + tag_name
def wadl_xpath(tag_name):
"""Turn a tag name into an XPath path."""
return './' + wadl_tag(tag_name)
def _merge_dicts(*dicts):
"""Merge any number of dictionaries, some of which may be None."""
final = {}
for dict in dicts:
if dict is not None:
final.update(dict)
return final
class WADLError(Exception):
"""An exception having to do with the state of the WADL application."""
pass
class NoBoundRepresentationError(WADLError):
"""An unbound resource was used where wadllib expected a bound resource.
To obtain the value of a resource's parameter, you first must bind
the resource to a representation. Otherwise the resource has no
idea what the value is and doesn't even know if you've given it a
parameter name that makes sense.
"""
class UnsupportedMediaTypeError(WADLError):
"""A media type was given that's not supported in this context.
A resource can only be bound to media types it has representations
of.
"""
class WADLBase(object):
"""A base class for objects that contain WADL-derived information."""
class HasParametersMixin:
"""A mixin class for objects that have associated Parameter objects."""
def params(self, styles, resource=None):
"""Find subsidiary parameters that have the given styles."""
if resource is None:
resource = self.resource
if resource is None:
raise ValueError("Could not find any particular resource")
if self.tag is None:
return []
param_tags = self.tag.findall(wadl_xpath('param'))
if param_tags is None:
return []
return [Parameter(resource, param_tag)
for param_tag in param_tags
if param_tag.attrib.get('style') in styles]
def validate_param_values(self, params, param_values,
enforce_completeness=True, **kw_param_values):
"""Make sure the given valueset is valid.
A valueset might be invalid because it contradicts a fixed
value or (if enforce_completeness is True) because it lacks a
required value.
:param params: A list of Parameter objects.
:param param_values: A dictionary of parameter values. May include
paramters whose names are not valid Python identifiers.
:param enforce_completeness: If True, this method will raise
an exception when the given value set lacks a value for a
required parameter.
:param kw_param_values: A dictionary of parameter values.
:return: A dictionary of validated parameter values.
"""
param_values = _merge_dicts(param_values, kw_param_values)
validated_values = {}
for param in params:
name = param.name
if param.fixed_value is not None:
if (name in param_values
and param_values[name] != param.fixed_value):
raise ValueError(("Value '%s' for parameter '%s' "
"conflicts with fixed value '%s'")
% (param_values[name], name,
param.fixed_value))
param_values[name] = param.fixed_value
options = [option.value for option in param.options]
if (len(options) > 0 and name in param_values
and param_values[name] not in options):
raise ValueError(("Invalid value '%s' for parameter '%s': "
'valid values are: "%s"') % (
param_values[name], name, '", "'.join(options)))
if (enforce_completeness and param.is_required
and not name in param_values):
raise ValueError("No value for required parameter '%s'"
% name)
if name in param_values:
validated_values[name] = param_values[name]
del param_values[name]
if len(param_values) > 0:
raise ValueError("Unrecognized parameter(s): '%s'"
% "', '".join(param_values.keys()))
return validated_values
class WADLResolvableDefinition(WADLBase):
"""A base class for objects whose definitions may be references."""
def __init__(self, application):
"""Initialize with a WADL application.
:param application: A WADLDefinition. Relative links are
assumed to be relative to this object's URL.
"""
self._definition = None
self.application = application
def resolve_definition(self):
"""Return the definition of this object, wherever it is.
Resource is a good example. A WADL tag
may contain a large number of nested tags describing a
resource, or it may just contain a 'type' attribute that
references a which contains those same
tags. Resource.resolve_definition() will return the original
Resource object in the first case, and a
ResourceType object in the second case.
"""
if self._definition is not None:
return self._definition
object_url = self._get_definition_url()
if object_url is None:
# The object contains its own definition.
# XXX leonardr 2008-05-28:
# This code path is not tested in Launchpad.
self._definition = self
return self
# The object makes reference to some other object. Resolve
# its URL and return it.
xml_id = self.application.lookup_xml_id(object_url)
definition = self._definition_factory(xml_id)
if definition is None:
# XXX leonardr 2008-06-
# This code path is not tested in Launchpad.
# It requires an invalid WADL file that makes
# a reference to a nonexistent tag within the
# same WADL file.
raise KeyError('No such XML ID: "%s"' % object_url)
self._definition = definition
return definition
def _definition_factory(self, id):
"""Transform an XML ID into a wadllib wrapper object.
Which kind of object it is depends on the subclass.
"""
raise NotImplementedError()
def _get_definition_url(self):
"""Find the URL that identifies an external reference.
How to do this depends on the subclass.
"""
raise NotImplementedError()
class Resource(WADLResolvableDefinition):
"""A resource, possibly bound to a representation."""
def __init__(self, application, url, resource_type,
representation=None, media_type=None,
representation_needs_processing=True,
representation_definition=None):
"""
:param application: A WADLApplication.
:param url: The URL to this resource.
:param resource_type: An ElementTree or tag.
:param representation: A string representation.
:param media_type: The media type of the representation.
:param representation_needs_processing: Set to False if the
'representation' parameter should be used as
is. Otherwise, it will be transformed from a string into
an appropriate Python data structure, depending on its
media type.
:param representation_definition: A RepresentationDefinition
object describing the structure of this
representation. Used in cases when the representation
isn't the result of sending a standard GET to the
resource.
"""
super(Resource, self).__init__(application)
self._url = url
if isinstance(resource_type, _string_types):
# We were passed the URL to a resource type. Look up the
# type object itself
self.tag = self.application.get_resource_type(resource_type).tag
else:
# We were passed an XML tag that describes a resource or
# resource type.
self.tag = resource_type
self.representation = None
if representation is not None:
if media_type == 'application/json':
if representation_needs_processing:
self.representation = json.loads(
_make_unicode(representation))
else:
self.representation = representation
else:
raise UnsupportedMediaTypeError(
"This resource doesn't define a representation for "
"media type %s" % media_type)
self.media_type = media_type
if representation is not None:
if representation_definition is not None:
self.representation_definition = representation_definition
else:
self.representation_definition = (
self.get_representation_definition(self.media_type))
@property
def url(self):
"""Return the URL to this resource."""
return self._url
@property
def type_url(self):
"""Return the URL to the type definition for this resource, if any."""
if self.tag is None:
return None
url = self.tag.attrib.get('type')
if url is not None:
# This resource is defined in the WADL file.
return url
type_id = self.tag.attrib.get('id')
if type_id is not None:
# This resource was obtained by following a link.
base = URI(self.application.markup_url).ensureSlash()
return str(base) + '#' + type_id
# This resource does not have any associated resource type.
return None
@property
def id(self):
"""Return the ID of this resource."""
return self.tag.attrib['id']
def bind(self, representation, media_type='application/json',
representation_needs_processing=True,
representation_definition=None):
"""Bind the resource to a representation of that resource.
:param representation: A string representation
:param media_type: The media type of the representation.
:param representation_needs_processing: Set to False if the
'representation' parameter should be used as
is.
:param representation_definition: A RepresentationDefinition
object describing the structure of this
representation. Used in cases when the representation
isn't the result of sending a standard GET to the
resource.
:return: A Resource bound to a particular representation.
"""
return Resource(self.application, self.url, self.tag,
representation, media_type,
representation_needs_processing,
representation_definition)
def get_representation_definition(self, media_type):
"""Get a description of one of this resource's representations."""
default_get_response = self.get_method('GET').response
for representation in default_get_response:
representation_tag = representation.resolve_definition().tag
if representation_tag.attrib.get('mediaType') == media_type:
return representation
raise UnsupportedMediaTypeError("No definition for representation "
"with media type %s." % media_type)
def get_method(self, http_method=None, media_type=None, query_params=None,
representation_params=None):
"""Look up one of this resource's methods by HTTP method.
:param http_method: The HTTP method used to invoke the desired
method. Case-insensitive and optional.
:param media_type: The media type of the representation
accepted by the method. Optional.
:param query_params: The names and values of any fixed query
parameters used to distinguish between
two methods that use the same HTTP
method. Optional.
:param representation_params: The names and values of any
fixed representation parameters used to
distinguish between two methods that use
the same HTTP method and have the same
media type. Optional.
:return: A MethodDefinition, or None if there's no definition
that fits the given constraints.
"""
for method_tag in self._method_tag_iter():
name = method_tag.attrib.get('name', '').lower()
if http_method is None or name == http_method.lower():
method = Method(self, method_tag)
if method.is_described_by(media_type, query_params,
representation_params):
return method
return None
def parameters(self, media_type=None):
"""A list of this resource's parameters.
:param media_type: Media type of the representation definition
whose parameters are being named. Must be present unless
this resource is bound to a representation.
:raise NoBoundRepresentationError: If this resource is not
bound to a representation and media_type was not provided.
"""
return self._find_representation_definition(
media_type).params(self)
def parameter_names(self, media_type=None):
"""A list naming this resource's parameters.
:param media_type: Media type of the representation definition
whose parameters are being named. Must be present unless
this resource is bound to a representation.
:raise NoBoundRepresentationError: If this resource is not
bound to a representation and media_type was not provided.
"""
return self._find_representation_definition(
media_type).parameter_names(self)
@property
def method_iter(self):
"""An iterator over the methods defined on this resource."""
for method_tag in self._method_tag_iter():
yield Method(self, method_tag)
def get_parameter(self, param_name, media_type=None):
"""Find a parameter within a representation definition.
:param param_name: Name of the parameter to find.
:param media_type: Media type of the representation definition
whose parameters are being named. Must be present unless
this resource is bound to a representation.
:raise NoBoundRepresentationError: If this resource is not
bound to a representation and media_type was not provided.
"""
definition = self._find_representation_definition(media_type)
representation_tag = definition.tag
for param_tag in representation_tag.findall(wadl_xpath('param')):
if param_tag.attrib.get('name') == param_name:
return Parameter(self, param_tag)
return None
def get_parameter_value(self, parameter):
"""Find the value of a parameter, given the Parameter object.
:raise ValueError: If the parameter value can't be converted into
its defined type.
"""
if self.representation is None:
raise NoBoundRepresentationError(
"Resource is not bound to any representation.")
if self.media_type == 'application/json':
# XXX leonardr 2008-05-28 A real JSONPath implementation
# should go here. It should execute tag.attrib['path']
# against the JSON representation.
#
# Right now the implementation assumes the JSON
# representation is a hash and treats tag.attrib['name'] as a
# key into the hash.
if parameter.style != 'plain':
raise NotImplementedError(
"Don't know how to find value for a parameter of "
"type %s." % parameter.style)
value = self.representation[parameter.name]
if value is not None:
namespace_url, data_type = self._dereference_namespace(
parameter.tag, parameter.type)
if (namespace_url == XML_SCHEMA_NS_URI
and data_type in ['dateTime', 'date']):
try:
# Parse it as an ISO 8601 date and time.
value = iso_strptime(value)
except ValueError:
# Parse it as an ISO 8601 date.
try:
value = datetime.datetime(
*(time.strptime(value, "%Y-%m-%d")[0:6]))
except ValueError:
# Raise an unadorned ValueError so the client
# can treat the value as a string if they
# want.
raise ValueError(value)
return value
raise NotImplementedError("Path traversal not implemented for "
"a representation of media type %s."
% self.media_type)
def _dereference_namespace(self, tag, value):
"""Splits a value into namespace URI and value.
:param tag: A tag to use as context when mapping namespace
names to URIs.
"""
if value is not None and ':' in value:
namespace, value = value.split(':', 1)
else:
namespace = ''
ns_map = tag.get(NS_MAP)
namespace_url = ns_map.get(namespace, None)
return namespace_url, value
def _definition_factory(self, id):
"""Given an ID, find a ResourceType for that ID."""
return self.application.resource_types.get(id)
def _get_definition_url(self):
"""Return the URL that shows where a resource is 'really' defined.
If a resource's capabilities are defined by reference, the
tag's 'type' attribute will contain the URL to the
that defines them.
"""
return self.tag.attrib.get('type')
def _find_representation_definition(self, media_type=None):
"""Get the most appropriate representation definition.
If media_type is provided, the most appropriate definition is
the definition of the representation of that media type.
If this resource is bound to a representation, the most
appropriate definition is the definition of that
representation. Otherwise, the most appropriate definition is
the definition of the representation served in response to a
standard GET.
:param media_type: Media type of the definition to find. Must
be present unless the resource is bound to a
representation.
:raise NoBoundRepresentationError: If this resource is not
bound to a representation and media_type was not provided.
:return: A RepresentationDefinition
"""
if self.representation is not None:
# We know that when this object was created, a
# representation definition was either looked up, or
# directly passed in along with the representation.
definition = self.representation_definition.resolve_definition()
elif media_type is not None:
definition = self.get_representation_definition(media_type)
else:
raise NoBoundRepresentationError(
"Resource is not bound to any representation, and no media "
"media type was specified.")
return definition.resolve_definition()
def _method_tag_iter(self):
"""Iterate over this resource's tags."""
definition = self.resolve_definition().tag
for method_tag in definition.findall(wadl_xpath('method')):
yield method_tag
class Method(WADLBase):
"""A wrapper around an XML tag.
"""
def __init__(self, resource, method_tag):
"""Initialize with a tag.
:param method_tag: An ElementTree tag.
"""
self.resource = resource
self.application = self.resource.application
self.tag = method_tag
@property
def request(self):
"""Return the definition of a request that invokes the WADL method."""
return RequestDefinition(self, self.tag.find(wadl_xpath('request')))
@property
def response(self):
"""Return the definition of the response to the WADL method."""
return ResponseDefinition(self.resource,
self.tag.find(wadl_xpath('response')))
@property
def id(self):
"""The XML ID of the WADL method definition."""
return self.tag.attrib.get('id')
@property
def name(self):
"""The name of the WADL method definition.
This is also the name of the HTTP method (GET, POST, etc.)
that should be used to invoke the WADL method.
"""
return self.tag.attrib.get('name').lower()
def build_request_url(self, param_values=None, **kw_param_values):
"""Return the request URL to use to invoke this method."""
return self.request.build_url(param_values, **kw_param_values)
def build_representation(self, media_type=None,
param_values=None, **kw_param_values):
"""Build a representation to be sent when invoking this method.
:return: A 2-tuple of (media_type, representation).
"""
return self.request.representation(
media_type, param_values, **kw_param_values)
def is_described_by(self, media_type=None, query_values=None,
representation_values=None):
"""Returns true if this method fits the given constraints.
:param media_type: The method must accept this media type as a
representation.
:param query_values: These key-value pairs must be acceptable
as values for this method's query
parameters. This need not be a complete set
of parameters acceptable to the method.
:param representation_values: These key-value pairs must be
acceptable as values for this method's
representation parameters. Again, this need
not be a complete set of parameters
acceptable to the method.
"""
representation = None
if media_type is not None:
representation = self.request.get_representation_definition(
media_type)
if representation is None:
return False
if query_values is not None and len(query_values) > 0:
request = self.request
if request is None:
# This method takes no special request
# parameters, so it can't match.
return False
try:
request.validate_param_values(
request.query_params, query_values, False)
except ValueError:
return False
# At this point we know the media type and query values match.
if (representation_values is None
or len(representation_values) == 0):
return True
if representation is not None:
return representation.is_described_by(
representation_values)
for representation in self.request.representations:
try:
representation.validate_param_values(
representation.params(self.resource),
representation_values, False)
return True
except ValueError:
pass
return False
class RequestDefinition(WADLBase, HasParametersMixin):
"""A wrapper around the description of the request invoking a method."""
def __init__(self, method, request_tag):
"""Initialize with a tag.
:param resource: The resource to which this request can be sent.
:param request_tag: An ElementTree tag.
"""
self.method = method
self.resource = self.method.resource
self.application = self.resource.application
self.tag = request_tag
@property
def query_params(self):
"""Return the query parameters for this method."""
return self.params(['query'])
@property
def representations(self):
for definition in self.tag.findall(wadl_xpath('representation')):
yield RepresentationDefinition(
self.application, self.resource, definition)
def get_representation_definition(self, media_type=None):
"""Return the appropriate representation definition."""
for representation in self.representations:
if media_type is None or representation.media_type == media_type:
return representation
return None
def representation(self, media_type=None, param_values=None,
**kw_param_values):
"""Build a representation to be sent along with this request.
:return: A 2-tuple of (media_type, representation).
"""
definition = self.get_representation_definition(media_type)
if definition is None:
raise TypeError("Cannot build representation of media type %s"
% media_type)
return definition.bind(param_values, **kw_param_values)
def build_url(self, param_values=None, **kw_param_values):
"""Return the request URL to use to invoke this method."""
validated_values = self.validate_param_values(
self.query_params, param_values, **kw_param_values)
url = self.resource.url
if len(validated_values) > 0:
if '?' in url:
append = '&'
else:
append = '?'
url += append + urlencode(sorted(validated_values.items()))
return url
class ResponseDefinition(HasParametersMixin):
"""A wrapper around the description of a response to a method."""
# XXX leonardr 2008-05-29 it would be nice to have
# ResponseDefinitions for POST operations and nonstandard GET
# operations say what representations and/or status codes you get
# back. Getting this to work with Launchpad requires work on the
# Launchpad side.
def __init__(self, resource, response_tag, headers=None):
"""Initialize with a tag.
:param response_tag: An ElementTree tag.
"""
self.application = resource.application
self.resource = resource
self.tag = response_tag
self.headers = headers
def __iter__(self):
"""Get an iterator over the representation definitions.
These are the representations returned in response to an
invocation of this method.
"""
path = wadl_xpath('representation')
for representation_tag in self.tag.findall(path):
yield RepresentationDefinition(
self.resource.application, self.resource, representation_tag)
def bind(self, headers):
"""Bind the response to a set of HTTP headers.
A WADL response can have associated header parameters, but no
other kind.
"""
return ResponseDefinition(self.resource, self.tag, headers)
def get_parameter(self, param_name):
"""Find a header parameter within the response."""
for param_tag in self.tag.findall(wadl_xpath('param')):
if (param_tag.attrib.get('name') == param_name
and param_tag.attrib.get('style') == 'header'):
return Parameter(self, param_tag)
return None
def get_parameter_value(self, parameter):
"""Find the value of a parameter, given the Parameter object."""
if self.headers is None:
raise NoBoundRepresentationError(
"Response object is not bound to any headers.")
if parameter.style != 'header':
raise NotImplementedError(
"Don't know how to find value for a parameter of "
"type %s." % parameter.style)
return self.headers.get(parameter.name)
def get_representation_definition(self, media_type):
"""Get one of the possible representations of the response."""
if self.tag is None:
return None
for representation in self:
if representation.media_type == media_type:
return representation
return None
class RepresentationDefinition(WADLResolvableDefinition, HasParametersMixin):
"""A definition of the structure of a representation."""
def __init__(self, application, resource, representation_tag):
super(RepresentationDefinition, self).__init__(application)
self.resource = resource
self.tag = representation_tag
def params(self, resource):
return super(RepresentationDefinition, self).params(
['query', 'plain'], resource)
def parameter_names(self, resource):
"""Return the names of all parameters."""
return [param.name for param in self.params(resource)]
@property
def media_type(self):
"""The media type of the representation described here."""
return self.resolve_definition().tag.attrib['mediaType']
def _make_boundary(self, all_parts):
"""Make a random boundary that does not appear in `all_parts`."""
_width = len(repr(sys.maxsize - 1))
_fmt = '%%0%dd' % _width
token = random.randrange(sys.maxsize)
boundary = ('=' * 15) + (_fmt % token) + '=='
if all_parts is None:
return boundary
b = boundary
counter = 0
while True:
pattern = ('^--' + re.escape(b) + '(--)?$').encode('ascii')
if not re.search(pattern, all_parts, flags=re.MULTILINE):
break
b = boundary + '.' + str(counter)
counter += 1
return b
def _write_headers(self, buf, headers):
"""Write MIME headers to a file object."""
for key, value in headers:
buf.write(key.encode('UTF-8'))
buf.write(b': ')
buf.write(value.encode('UTF-8'))
buf.write(b'\r\n')
buf.write(b'\r\n')
def _write_boundary(self, buf, boundary, closing=False):
"""Write a multipart boundary to a file object."""
buf.write(b'--')
buf.write(boundary.encode('UTF-8'))
if closing:
buf.write(b'--')
buf.write(b'\r\n')
def _generate_multipart_form(self, parts):
"""Generate a multipart/form-data message.
This is very loosely based on the email module in the Python standard
library. However, that module doesn't really support directly embedding
binary data in a form: various versions of Python have mangled line
separators in different ways, and none of them get it quite right.
Since we only need a tiny subset of MIME here, it's easier to implement
it ourselves.
:return: a tuple of two elements: the Content-Type of the message, and
the entire encoded message as a byte string.
"""
# Generate the subparts first so that we can calculate a safe boundary.
encoded_parts = []
for is_binary, name, value in parts:
buf = io.BytesIO()
if is_binary:
ctype = 'application/octet-stream'
# RFC 7578 says that the filename parameter isn't mandatory
# in our case, but without it cgi.FieldStorage tries to
# decode as text on Python 3.
cdisp = 'form-data; name="%s"; filename="%s"' % (
quote(name), quote(name))
else:
ctype = 'text/plain; charset="utf-8"'
cdisp = 'form-data; name="%s"' % quote(name)
self._write_headers(buf, [
('MIME-Version', '1.0'),
('Content-Type', ctype),
('Content-Disposition', cdisp),
])
if is_binary:
if not isinstance(value, bytes):
raise TypeError('bytes payload expected: %s' % type(value))
buf.write(value)
else:
if not isinstance(value, _string_types):
raise TypeError(
'string payload expected: %s' % type(value))
lines = re.split(r'\r\n|\r|\n', value)
for line in lines[:-1]:
buf.write(line.encode('UTF-8'))
buf.write(b'\r\n')
buf.write(lines[-1].encode('UTF-8'))
encoded_parts.append(buf.getvalue())
# Create a suitable boundary.
boundary = self._make_boundary(b'\r\n'.join(encoded_parts))
# Now we can write the multipart headers, followed by all the parts.
buf = io.BytesIO()
ctype = 'multipart/form-data; boundary="%s"' % quote(boundary)
self._write_headers(buf, [
('MIME-Version', '1.0'),
('Content-Type', ctype),
])
for encoded_part in encoded_parts:
self._write_boundary(buf, boundary)
buf.write(encoded_part)
buf.write(b'\r\n')
self._write_boundary(buf, boundary, closing=True)
return ctype, buf.getvalue()
def bind(self, param_values, **kw_param_values):
"""Bind the definition to parameter values, creating a document.
:return: A 2-tuple (media_type, document).
"""
definition = self.resolve_definition()
params = definition.params(self.resource)
validated_values = self.validate_param_values(
params, param_values, **kw_param_values)
media_type = self.media_type
if media_type == 'application/x-www-form-urlencoded':
doc = urlencode(sorted(validated_values.items()))
elif media_type == 'multipart/form-data':
parts = []
missing = object()
for param in params:
value = validated_values.get(param.name, missing)
if value is not missing:
parts.append((param.type == 'binary', param.name, value))
media_type, doc = self._generate_multipart_form(parts)
elif media_type == 'application/json':
doc = json.dumps(validated_values)
else:
raise ValueError("Unsupported media type: '%s'" % media_type)
return media_type, doc
def _definition_factory(self, id):
"""Turn a representation ID into a RepresentationDefinition."""
return self.application.representation_definitions.get(id)
def _get_definition_url(self):
"""Find the URL containing the representation's 'real' definition.
If a representation's structure is defined by reference, the
tag's 'href' attribute will contain the URL
to the that defines the structure.
"""
return self.tag.attrib.get('href')
class Parameter(WADLBase):
"""One of the parameters of a representation definition."""
def __init__(self, value_container, tag):
"""Initialize with respect to a value container.
:param value_container: Usually the resource whose representation
has this parameter. If the resource is bound to a representation,
you'll be able to find the value of this parameter in the
representation. This may also be a server response whose headers
define a value for this parameter.
:tag: The ElementTree tag for this parameter.
"""
self.application = value_container.application
self.value_container = value_container
self.tag = tag
@property
def name(self):
"""The name of this parameter."""
return self.tag.attrib.get('name')
@property
def style(self):
"""The style of this parameter."""
return self.tag.attrib.get('style')
@property
def type(self):
"""The XSD type of this parameter."""
return self.tag.attrib.get('type')
@property
def fixed_value(self):
"""The value to which this parameter is fixed, if any.
A fixed parameter must be present in invocations of a WADL
method, and it must have a particular value. This is commonly
used to designate one parameter as containing the name of the
server-side operation to be invoked.
"""
return self.tag.attrib.get('fixed')
@property
def is_required(self):
"""Whether or not a value for this parameter is required."""
return (self.tag.attrib.get('required', 'false').lower()
in ['1', 'true'])
def get_value(self):
"""The value of this parameter in the bound representation/headers.
:raise NoBoundRepresentationError: If this parameter's value
container is not bound to a representation or a set of
headers.
"""
return self.value_container.get_parameter_value(self)
@property
def options(self):
"""Return the set of acceptable values for this parameter."""
return [Option(self, option_tag)
for option_tag in self.tag.findall(wadl_xpath('option'))]
@property
def link(self):
"""Get the link to another resource.
The link may be examined and, if its type is of a known WADL
description, it may be followed.
:return: A Link object, or None.
"""
link_tag = self.tag.find(wadl_xpath('link'))
if link_tag is None:
return None
return Link(self, link_tag)
@property
def linked_resource(self):
"""Follow a link from this parameter to a new resource.
This only works for parameters whose WADL definition includes a
tag that points to a known WADL description.
:return: A Resource object for the resource at the other end
of the link.
"""
link = self.link
if link is None:
raise ValueError("This parameter isn't a link to anything.")
return link.follow
class Option(WADLBase):
"""One of a set of possible values for a parameter."""
def __init__(self, parameter, option_tag):
"""Initialize the option.
:param parameter: A Parameter.
:param link_tag: An ElementTree tag.
"""
self.parameter = parameter
self.tag = option_tag
@property
def value(self):
return self.tag.attrib.get('value')
class Link(WADLResolvableDefinition):
"""A link from one resource to another.
Calling resolve_definition() on a Link will give you a Resource for the
type of resource linked to. An alias for this is 'follow'.
"""
def __init__(self, parameter, link_tag):
"""Initialize the link.
:param parameter: A Parameter.
:param link_tag: An ElementTree tag.
"""
super(Link, self).__init__(parameter.application)
self.parameter = parameter
self.tag = link_tag
@property
def follow(self):
"""Follow the link to another Resource."""
if not self.can_follow:
raise WADLError("Cannot follow a link when the target has no "
"WADL description. Try using a general HTTP "
"client instead.")
return self.resolve_definition()
@property
def can_follow(self):
"""Can this link be followed within wadllib?
wadllib can follow a link if it points to a resource that has
a WADL definition.
"""
try:
definition_url = self._get_definition_url()
except WADLError:
return False
return True
def _definition_factory(self, id):
"""Turn a resource type ID into a ResourceType."""
return Resource(
self.application, self.parameter.get_value(),
self.application.resource_types.get(id).tag)
def _get_definition_url(self):
"""Find the URL containing the definition ."""
type = self.tag.attrib.get('resource_type')
if type is None:
raise WADLError("Parameter is a link, but not to a resource "
"with a known WADL description.")
return type
class ResourceType(WADLBase):
"""A wrapper around an XML tag."""
def __init__(self, resource_type_tag):
"""Initialize with a tag.
:param resource_type_tag: An ElementTree tag.
"""
self.tag = resource_type_tag
class Application(WADLBase):
"""A WADL document made programmatically accessible."""
def __init__(self, markup_url, markup):
"""Parse WADL and find the most important parts of the document.
:param markup_url: The URL from which this document was obtained.
:param markup: The WADL markup itself, or an open filehandle to it.
"""
self.markup_url = markup_url
if hasattr(markup, 'read'):
self.doc = self._from_stream(markup)
else:
self.doc = self._from_string(markup)
self.resources = self.doc.find(wadl_xpath('resources'))
self.resource_base = self.resources.attrib.get('base')
self.representation_definitions = {}
self.resource_types = {}
for representation in self.doc.findall(wadl_xpath('representation')):
id = representation.attrib.get('id')
if id is not None:
definition = RepresentationDefinition(
self, None, representation)
self.representation_definitions[id] = definition
for resource_type in self.doc.findall(wadl_xpath('resource_type')):
id = resource_type.attrib['id']
self.resource_types[id] = ResourceType(resource_type)
def _from_stream(self, stream):
"""Turns markup into a document.
Just a wrapper around ElementTree which keeps track of namespaces.
"""
events = "start", "start-ns", "end-ns"
root = None
ns_map = []
for event, elem in ET.iterparse(stream, events):
if event == "start-ns":
ns_map.append(elem)
elif event == "end-ns":
ns_map.pop()
elif event == "start":
if root is None:
root = elem
elem.set(NS_MAP, dict(ns_map))
return ET.ElementTree(root)
def _from_string(self, markup):
"""Turns markup into a document."""
if not isinstance(markup, bytes):
markup = markup.encode("UTF-8")
return self._from_stream(io.BytesIO(markup))
def get_resource_type(self, resource_type_url):
"""Retrieve a resource type by the URL of its description."""
xml_id = self.lookup_xml_id(resource_type_url)
resource_type = self.resource_types.get(xml_id)
if resource_type is None:
raise KeyError('No such XML ID: "%s"' % resource_type_url)
return resource_type
def lookup_xml_id(self, url):
"""A helper method for locating a part of a WADL document.
:param url: The URL (with anchor) of the desired part of the
WADL document.
:return: The XML ID corresponding to the anchor.
"""
markup_uri = URI(self.markup_url).ensureNoSlash()
markup_uri.fragment = None
if url.startswith('http'):
# It's an absolute URI.
this_uri = URI(url).ensureNoSlash()
else:
# It's a relative URI.
this_uri = markup_uri.resolve(url)
possible_xml_id = this_uri.fragment
this_uri.fragment = None
if this_uri == markup_uri:
# The URL pointed elsewhere within the same WADL document.
# Return its fragment.
return possible_xml_id
# XXX leonardr 2008-05-28:
# This needs to be implemented eventually for Launchpad so
# that a script using this client can navigate from a WADL
# representation of a non-root resource to its definition at
# the server root.
raise NotImplementedError("Can't look up definition in another "
"url (%s)" % url)
def get_resource_by_path(self, path):
"""Locate one of the resources described by this document.
:param path: The path to the resource.
"""
# XXX leonardr 2008-05-27 This method only finds top-level
# resources. That's all we need for Launchpad because we don't
# define nested resources yet.
matching = [resource for resource in self.resources
if resource.attrib['path'] == path]
if len(matching) < 1:
return None
if len(matching) > 1:
raise WADLError("More than one resource defined with path %s"
% path)
return Resource(
self, merge(self.resource_base, path, True), matching[0])
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3212187
wadllib-1.3.6/src/wadllib/docs/ 0000755 0001750 0001750 00000000000 00000000000 017777 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1626257220.0
wadllib-1.3.6/src/wadllib/docs/Makefile 0000644 0001750 0001750 00000001135 00000000000 021437 0 ustar 00cjwatson cjwatson 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = wadllib
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548740.0
wadllib-1.3.6/src/wadllib/docs/NEWS.rst 0000644 0001750 0001750 00000006570 00000000000 021315 0 ustar 00cjwatson cjwatson 0000000 0000000 ================
NEWS for wadllib
================
1.3.6 (2021-09-13)
==================
- Remove buildout support in favour of tox. [bug=922605]
- Adjust versioning strategy to avoid importing pkg_resources, which is slow
in large environments.
1.3.5 (2021-01-20)
==================
- Drop support for Python 3.2, 3.3, and 3.4.
- Accept Unicode parameter values again when performing multipart/form-data
encoding on Python 2 (broken in 1.3.3).
1.3.4 (2020-04-29)
==================
- Advertise support for Python 3.8.
- Add Python 3.9 compatibility by using xml.etree.ElementTree if
xml.etree.cElementTree does not exist. [bug=1870294]
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 tags that go beneath 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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1626257220.0
wadllib-1.3.6/src/wadllib/docs/index.rst 0000644 0001750 0001750 00000063716 00000000000 021655 0 ustar 00cjwatson cjwatson 0000000 0000000 ..
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 .
=======
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,
... 'Some 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__
>>> 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
>>> known_resource.linked_resource
>>> 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 or tag. Sometimes a representation
is defined within a WADL 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 or 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):
... 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=u"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=u"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', 'multipart/form-data')
>>> media_type, doc = method.build_representation(
... text_field=u"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()
.. pypi description ends here
.. toctree::
:glob:
NEWS
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/iso_strptime.py 0000644 0001750 0001750 00000005511 00000000000 022144 0 ustar 00cjwatson cjwatson 0000000 0000000 # 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 .
"""
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\d{4})\-(?P\d{2})\-(?P\d{2})
# separator
T
# pattern matching time
(?P\d{2})\:(?P\d{2})\:(?P\d{2})
# pattern matching optional microseconds
(\.(?P\d{6}))?
# pattern matching optional timezone offset
(?P[\-\+]\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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3212187
wadllib-1.3.6/src/wadllib/tests/ 0000755 0001750 0001750 00000000000 00000000000 020211 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/__init__.py 0000644 0001750 0001750 00000001260 00000000000 022321 0 ustar 00cjwatson cjwatson 0000000 0000000 # 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
# .
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3252187
wadllib-1.3.6/src/wadllib/tests/data/ 0000755 0001750 0001750 00000000000 00000000000 021122 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/__init__.py 0000644 0001750 0001750 00000000000 00000000000 023221 0 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/data-types-wadl.xml 0000644 0001750 0001750 00000001650 00000000000 024646 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/launchpad-wadl.xml 0000644 0001750 0001750 00000554515 00000000000 024547 0 ustar 00cjwatson cjwatson 0000000 0000000
The root of the web service.
The name of the operation being invoked.
The name of the operation being invoked.
<strong>Search text</strong>
The name of the operation being invoked.
Display Name: This team's name as you would like it displayed throughout Launchpad.
Name: A short unique name, beginning with a lower-case letter or number, and containing only letters, numbers, dots, hyphens, or plus signs.
Subscription period: Number of days a new subscription lasts before expiring. You can customize the length of an individual subscription when approving it. Leave this empty or set to 0 for subscriptions to never expire.
Team Description: Use plain text; URLs will be linkified
Renewal period: Number of days a subscription lasts after being renewed. You can customize the lengths of individual renewals, but this is what's used for auto-renewed and user-renewed memberships.
<strong>Subscription policy</strong>
The name of the operation being invoked.
The name of the operation being invoked.
The name of the operation being invoked.
<strong>Search text</strong>
The name of the operation being invoked.
<strong>Search text</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Owner</strong>
<strong>Nickname</strong>
<strong>IRC network</strong>
<strong>Nickname</strong>
<strong>IRC network</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Name: At least one lowercase letter or number, followed by letters, dots, hyphens or plusses. Keep this name short, as it is used in URLs.
Part of: Super-project. In Launchpad, we can setup a special "project group" that is an overarching initiative that includes several related projects. For example, the Mozilla Project produces Firefox, Thunderbird and Gecko. This information is used to group those projects in a coherent way. If you make this project part of a group, the group preferences and decisions around bug tracking, translation and security policy will apply to this project.
Name: At least one lowercase letter or number, followed by letters, dots, hyphens or plusses. Keep this name short, as it is used in URLs.
Part of: Super-project. In Launchpad, we can setup a special "project group" that is an overarching initiative that includes several related projects. For example, the Mozilla Project produces Firefox, Thunderbird and Gecko. This information is used to group those projects in a coherent way. If you make this project part of a group, the group preferences and decisions around bug tracking, translation and security policy will apply to this project.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Wiki host</strong>
<strong>Owner</strong>
<strong>The URL for this wiki home page.</strong>
<strong>Wikiname</strong>
<strong>Wiki host</strong>
<strong>Wikiname</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>The state of this membership</strong>
<strong>Member</strong>
<strong>Team</strong>
<strong>The state of this membership</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>The latitude of this object.</strong>
<strong>The time zone of this object.</strong>
<strong>The longitude of this object.</strong>
<strong>Team</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Mugshot: A large image of exactly 192x192 pixels, that will be displayed on the team page in Launchpad. It should be no bigger than 100kb in size.
<strong>List of Jabber IDs of this Person.</strong>
<strong>All WikiNames of this Person.</strong>
All subteams of this team.:
A subteam is any team that is a member (either directly or
indirectly) of this team. As an example, let's say we have
this hierarchy of teams:
Rosetta Translators
Rosetta pt Translators
Rosetta pt_BR Translators
In this case, both 'Rosetta pt Translators' and 'Rosetta pt_BR
Translators' are subteams of the 'Rosetta Translators' team,
and all members of both subteams are considered members of
"Rosetta Translators".
<strong>When someone's membership is about to expire, notify them and</strong>
<strong>All teams in which this person is an indirect member.</strong>
<strong>The longitude of this object.</strong>
Subscription policy: 'Moderated' means all subscriptions must be approved. 'Open' means any user can join without approval. 'Restricted' means new members can be added only by a team administrator.
<strong>All teams in which this person is a participant.</strong>
<strong>Is this object a team?</strong>
Preferred email address: The preferred email address for this person. The one we'll use to communicate with them.
Open membership invitations.: All TeamMemberships which represent an invitation (to join a team) sent to this person.
Confirmed e-mails of this person.: Confirmed e-mails are the ones in the VALIDATED state
<strong>List of languages known by this person</strong>
<strong>Hide my email addresses from other Launchpad users</strong>
All participants of this team.: List of all direct and indirect people and teams who, one way or another, are a part of this team. If you want a method to check if a given person is a member of a team, you should probably look at IPerson.inTeam().
<strong>This is an active user or a team.</strong>
All superteams of this team.:
A superteam is any team that this team is a member of. For
example, let's say we have this hierarchy of teams, and we are
the "Rosetta pt_BR Translators":
Rosetta Translators
Rosetta pt Translators
Rosetta pt_BR Translators
In this case, we will return both 'Rosetta pt Translators' and
'Rosetta Translators', because we are member of both of them.
<strong>The latitude of this object.</strong>
<strong>All `ITeamMembership`s for Teams this Person is an active member of.</strong>
<strong>Team Owner</strong>
Mailing List Auto-subscription Policy: This attribute determines whether a person is automatically subscribed to a team's mailing list when the person joins said team.
Subscription period: Number of days a new subscription lasts before expiring. You can customize the length of an individual subscription when approving it. Leave this empty or set to 0 for subscriptions to never expire.
<strong>All members whose membership is in the EXPIRED state</strong>
Active `ITeamMembership`s for this object's members.: Active TeamMemberships are the ones with the ADMIN or APPROVED status. The results are ordered using Person.sortingColumns.
Visibility: Public visibility is standard, and Private Membership means that a team's members are hidden.
Renewal period: Number of days a subscription lasts after being renewed. You can customize the lengths of individual renewals, but this is what's used for auto-renewed and user-renewed memberships.
<strong>List of members with ADMIN or APPROVED status</strong>
<strong>Date Created</strong>
<strong>All members whose membership is in the INVITED state</strong>
Display Name: This team's name as you would like it displayed throughout Launchpad.
Team Description: Use plain text; URLs will be linkified
Name: A short unique name, beginning with a lower-case letter or number, and containing only letters, numbers, dots, hyphens, or plus signs.
Time zone: The time zone of where you live.
<strong>All members whose membership is in the PROPOSED state</strong>
<strong>List of this team's admins.</strong>
<strong>List of IRC nicknames of this Person.</strong>
Karma: The cached total karma for this person.
<strong>All members whose membership is in the DEACTIVATED state</strong>
Homepage Content: The content of your home page. Edit this and it will be displayed for all the world to see.
Mugshot: A large image of exactly 192x192 pixels, that will be displayed on the team page in Launchpad. It should be no bigger than 100kb in size.
<strong>When someone's membership is about to expire, notify them and</strong>
Subscription policy: 'Moderated' means all subscriptions must be approved. 'Open' means any user can join without approval. 'Restricted' means new members can be added only by a team administrator.
<strong>Hide my email addresses from other Launchpad users</strong>
<strong>Team Owner</strong>
Mailing List Auto-subscription Policy: This attribute determines whether a person is automatically subscribed to a team's mailing list when the person joins said team.
Subscription period: Number of days a new subscription lasts before expiring. You can customize the length of an individual subscription when approving it. Leave this empty or set to 0 for subscriptions to never expire.
Visibility: Public visibility is standard, and Private Membership means that a team's members are hidden.
Renewal period: Number of days a subscription lasts after being renewed. You can customize the lengths of individual renewals, but this is what's used for auto-renewed and user-renewed memberships.
Display Name: This team's name as you would like it displayed throughout Launchpad.
Team Description: Use plain text; URLs will be linkified
Name: A short unique name, beginning with a lower-case letter or number, and containing only letters, numbers, dots, hyphens, or plus signs.
Time zone: The time zone of where you live.
Homepage Content: The content of your home page. Edit this and it will be displayed for all the world to see.
The canonical link to this resource.
The link to the WADL description of this resource.
Name: A unique name, used in URLs, identifying the project
group. All lowercase, no special characters.
Examples: apache, mozilla, gimp.
Name: A unique name, used in URLs, identifying the project
group. All lowercase, no special characters.
Examples: apache, mozilla, gimp.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>The attachment content.</strong>
<strong>The message that was created when we added this attachment.</strong>
Attachment Type: The type of the attachment, for example Patch or Unspecified.
<strong>The bug the attachment belongs to.</strong>
Title: A short and descriptive description of the attachment
<strong>The attachment content.</strong>
<strong>The message that was created when we added this attachment.</strong>
Attachment Type: The type of the attachment, for example Patch or Unspecified.
<strong>The bug the attachment belongs to.</strong>
Title: A short and descriptive description of the attachment
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Person</strong>
<strong>Email Address</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Contact details: The contact details for the external bug tracker (so that, for example, its administrators can be contacted about a security breach).
Name: An URL-friendly name for the bug tracker, such as "mozilla-bugs".
<strong>Bug Tracker Type</strong>
Title: A descriptive label for this tracker to show in listings.
Location: The top-level URL for the bug tracker, or an upstream email address. This must be accurate so that Launchpad can link to external bug reports.
<strong>The remote watches on this bug tracker.</strong>
Summary: A brief introduction or overview of this bug tracker instance.
Location aliases: A list of URLs or email addresses that all lead to the same bug tracker, or commonly seen typos, separated by whitespace.
<strong>Owner</strong>
Contact details: The contact details for the external bug tracker (so that, for example, its administrators can be contacted about a security breach).
Name: An URL-friendly name for the bug tracker, such as "mozilla-bugs".
<strong>Bug Tracker Type</strong>
Title: A descriptive label for this tracker to show in listings.
Location: The top-level URL for the bug tracker, or an upstream email address. This must be accurate so that Launchpad can link to external bug reports.
Summary: A brief introduction or overview of this bug tracker instance.
Location aliases: A list of URLs or email addresses that all lead to the same bug tracker, or commonly seen typos, separated by whitespace.
<strong>Owner</strong>
<strong>The state of this membership</strong>
<strong>Member</strong>
<strong>Team</strong>
<strong>The state of this membership</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>Team</strong>
<strong>The latitude of this object.</strong>
<strong>The time zone of this object.</strong>
<strong>The longitude of this object.</strong>
<strong>Team</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Mugshot: A large image of exactly 192x192 pixels, that will be displayed on your home page in Launchpad. Traditionally this is a great big picture of your grinning face. Make the most of it! It should be no bigger than 100kb in size.
<strong>List of Jabber IDs of this Person.</strong>
<strong>All WikiNames of this Person.</strong>
All subteams of this team.:
A subteam is any team that is a member (either directly or
indirectly) of this team. As an example, let's say we have
this hierarchy of teams:
Rosetta Translators
Rosetta pt Translators
Rosetta pt_BR Translators
In this case, both 'Rosetta pt Translators' and 'Rosetta pt_BR
Translators' are subteams of the 'Rosetta Translators' team,
and all members of both subteams are considered members of
"Rosetta Translators".
<strong>All teams in which this person is an indirect member.</strong>
<strong>The longitude of this object.</strong>
<strong>All teams in which this person is a participant.</strong>
<strong>Is this object a team?</strong>
Preferred email address: The preferred email address for this person. The one we'll use to communicate with them.
Open membership invitations.: All TeamMemberships which represent an invitation (to join a team) sent to this person.
Confirmed e-mails of this person.: Confirmed e-mails are the ones in the VALIDATED state
<strong>List of languages known by this person</strong>
<strong>Hide my email addresses from other Launchpad users</strong>
All participants of this team.: List of all direct and indirect people and teams who, one way or another, are a part of this team. If you want a method to check if a given person is a member of a team, you should probably look at IPerson.inTeam().
<strong>This is an active user or a team.</strong>
All superteams of this team.:
A superteam is any team that this team is a member of. For
example, let's say we have this hierarchy of teams, and we are
the "Rosetta pt_BR Translators":
Rosetta Translators
Rosetta pt Translators
Rosetta pt_BR Translators
In this case, we will return both 'Rosetta pt Translators' and
'Rosetta Translators', because we are member of both of them.
<strong>The latitude of this object.</strong>
<strong>All `ITeamMembership`s for Teams this Person is an active member of.</strong>
<strong>Team Owner</strong>
Mailing List Auto-subscription Policy: This attribute determines whether a person is automatically subscribed to a team's mailing list when the person joins said team.
<strong>All members whose membership is in the EXPIRED state</strong>
Active `ITeamMembership`s for this object's members.: Active TeamMemberships are the ones with the ADMIN or APPROVED status. The results are ordered using Person.sortingColumns.
Visibility: Public visibility is standard, and Private Membership means that a team's members are hidden.
<strong>List of members with ADMIN or APPROVED status</strong>
<strong>Date Created</strong>
<strong>All members whose membership is in the INVITED state</strong>
Display Name: Your name as you would like it displayed throughout Launchpad. Most people use their full name here.
Name: A short unique name, beginning with a lower-case letter or number, and containing only letters, numbers, dots, hyphens, or plus signs.
Time zone: The time zone of where you live.
<strong>All members whose membership is in the PROPOSED state</strong>
<strong>List of this team's admins.</strong>
<strong>List of IRC nicknames of this Person.</strong>
Karma: The cached total karma for this person.
<strong>All members whose membership is in the DEACTIVATED state</strong>
Homepage Content: The content of your home page. Edit this and it will be displayed for all the world to see.
Mugshot: A large image of exactly 192x192 pixels, that will be displayed on your home page in Launchpad. Traditionally this is a great big picture of your grinning face. Make the most of it! It should be no bigger than 100kb in size.
<strong>Hide my email addresses from other Launchpad users</strong>
<strong>Team Owner</strong>
Mailing List Auto-subscription Policy: This attribute determines whether a person is automatically subscribed to a team's mailing list when the person joins said team.
Visibility: Public visibility is standard, and Private Membership means that a team's members are hidden.
Display Name: Your name as you would like it displayed throughout Launchpad. Most people use their full name here.
Name: A short unique name, beginning with a lower-case letter or number, and containing only letters, numbers, dots, hyphens, or plus signs.
Time zone: The time zone of where you live.
Homepage Content: The content of your home page. Edit this and it will be displayed for all the world to see.
The canonical link to this resource.
The link to the WADL description of this resource.
Person: The person's Launchpad ID or e-mail address. You can only subscribe someone who has a Launchpad account.
<strong>Bug</strong>
Subscribed by: The person who created this subscription.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Owner</strong>
<strong>Jabber user ID</strong>
<strong>Jabber user ID</strong>
<strong>Person</strong>
Bug System: You can register new bug trackers from the Launchpad Bugs home page.
Remote Bug: The bug number of this bug in the remote bug tracker.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>List of bug attachments.</strong>
<strong>MultiJoin of the bugs which are dups of this one</strong>
<strong>Date Last Updated</strong>
This bug report should be private: Private bug reports are visible only to their subscribers.
<strong>Date Made Private</strong>
Who Made Private: The person who set this bug private.
<strong>The owner's IPerson</strong>
<strong>BugTasks on this bug, sorted upstream, then ubuntu, then other distroseriess.</strong>
<strong>Bug ID</strong>
<strong>Duplicate Of</strong>
Summary: A one-line summary of the problem.
Tags: Separated by whitespace.
<strong>This bug is a security vulnerability</strong>
Description: A detailed description of the problem,
including the steps required to reproduce it.
<strong>Subscriptions.</strong>
Does the bug's state permit expiration?: Expiration is permitted when the bug is not valid anywhere, a message was sent to the bug reporter, and the bug is associated with pillars that have enabled bug expiration.
Nickname: A short and unique name.
Add one only if you often need to retype the URL
but have trouble remembering the bug number.
<strong>The messages related to this object, in reverse order of creation (so newest first).</strong>
<strong>All bug watches associated with this bug.</strong>
<strong>Date Created</strong>
<strong>Can the Incomplete bug expire if it becomes inactive? Expiration may happen when the bug permits expiration, and a bugtask cannot be confirmed.</strong>
True or False depending on whether this bug is considered completely addressed. A bug is Launchpad is completely addressed when there are no tasks that are still open for the bug.
<strong>Date of last bug message</strong>
This bug report should be private: Private bug reports are visible only to their subscribers.
<strong>The owner's IPerson</strong>
<strong>Duplicate Of</strong>
Summary: A one-line summary of the problem.
Tags: Separated by whitespace.
<strong>This bug is a security vulnerability</strong>
Description: A detailed description of the problem,
including the steps required to reproduce it.
Nickname: A short and unique name.
Add one only if you often need to retype the URL
but have trouble remembering the bug number.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Release Series</strong>
Version: The specific version number assigned to this release. Letters and numbers are acceptable, for releases like "1.2rc3".
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Parent</strong>
<strong>All the text/plain chunks joined together as a unicode string.</strong>
<strong>A list of BugAttachments connected to this message.</strong>
<strong>Person</strong>
<strong>Date Created</strong>
<strong>Subject</strong>
<strong>All the text/plain chunks joined together as a unicode string.</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Name: Only letters, numbers, and simple punctuation are allowed.
<strong>The product or distribution of this milestone.</strong>
Name: Only letters, numbers, and simple punctuation are allowed.
<strong>The product or distribution of this milestone.</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Importance</strong>
<strong>Status</strong>
<strong>Assigned to</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
Date Closed: The date on which this task was marked either Fix Committed or Fix Released.
<strong>Status</strong>
<strong>The target as presented in mail notifications</strong>
Date Confirmed: The date on which this task was marked Confirmed.
IBugTasks related to this one, namely other IBugTasks on the same IBug.
<strong>Importance</strong>
Date left new: The date on which this task was marked with a status higher than New.
<strong>The title of the bug related to this bugtask</strong>
Date Triaged: The date on which this task was marked Triaged.
Remote Bug Details: Select the bug watch that represents this task in the relevant bug tracker. If none of the bug watches represents this particular bug task, leave it as (None). Linking the remote bug watch with the task in this way means that a change in the remote bug status will change the status of this bug task in Launchpad.
<strong>Assigned to</strong>
True or False depending on whether or not there is more work required on this bug task.
Date Fix Committed: The date on which this task was marked Fix Committed.
<strong>The short, descriptive name of the target</strong>
Date Fix Relesaed: The date on which this task was marked Fix Released.
<strong>The owner</strong>
Date Created: The date on which this task was created.
Date In Progress: The date on which this task was marked In Progress.
<strong>Bug</strong>
Date Assigned: The date on which this task was assigned to someone.
Remote Bug Details: Select the bug watch that represents this task in the relevant bug tracker. If none of the bug watches represents this particular bug task, leave it as (None). Linking the remote bug watch with the task in this way means that a change in the remote bug status will change the status of this bug task in Launchpad.
<strong>The owner</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Remote Status</strong>
<strong>Remote Importance</strong>
<strong>Bug watch title</strong>
<strong>The URL at which to view the remote bug.</strong>
<strong>Last Error Type</strong>
Remote Bug: The bug number of this bug in the remote bug tracker.
The tasks which this watch will affect. In Launchpad, a bug watch can be linked to one or more tasks, and if it is linked and we notice a status change in the watched bug then we will try to update the Launchpad bug task accordingly.
Bug System: You can register new bug trackers from the Launchpad Bugs home page.
<strong>Last Checked</strong>
<strong>Owner</strong>
<strong>Date Created</strong>
<strong>Bug</strong>
<strong>Last Changed</strong>
<strong>Remote Status</strong>
<strong>Remote Importance</strong>
<strong>Last Error Type</strong>
Remote Bug: The bug number of this bug in the remote bug tracker.
Bug System: You can register new bug trackers from the Launchpad Bugs home page.
<strong>Last Checked</strong>
<strong>Last Changed</strong>
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>Project</strong>
Name: The name of the series is a short, unique name that identifies it, being used in URLs. It must be all lowercase, with no special characters. For example, '2.0' or 'trunk'.
<strong>Project</strong>
Name: The name of the series is a short, unique name that identifies it, being used in URLs. It must be all lowercase, with no special characters. For example, '2.0' or 'trunk'.
The canonical link to this resource.
The link to the WADL description of this resource.
<strong>The state of this membership</strong>
<strong>Comment on the last change</strong>
<strong>Member</strong>
<strong>Date expires</strong>
<strong>Last person who change this</strong>
<strong>Team</strong>
Date joined: The date in which this membership was made active for the first time.
<strong>Member</strong>
<strong>Date expires</strong>
<strong>Last person who change this</strong>
<strong>Team</strong>
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/links-wadl.xml 0000644 0001750 0001750 00000002004 00000000000 023705 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/multipart-binary-wadl.xml 0000644 0001750 0001750 00000002163 00000000000 026076 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/options-wadl.xml 0000644 0001750 0001750 00000001572 00000000000 024271 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/person-limi.json 0000644 0001750 0001750 00000004532 00000000000 024257 0 ustar 00cjwatson cjwatson 0000000 0000000 {"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"} ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/personset-page2.json 0000644 0001750 0001750 00000031050 00000000000 025032 0 ustar 00cjwatson cjwatson 0000000 0000000 {"total_size": 63, "prev_collection_link": "http:\/\/api.launchpad.dev\/beta\/people?ws.start=0&ws.size=5", "start": 5, "next_collection_link": "http:\/\/api.launchpad.dev\/beta\/people?ws.start=10&ws.size=5", "entries": [{"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/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\/~bug-watch-updater\/members_details", "hide_email_addresses": true, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater", "preferred_email_address_link": null, "date_created": "2006-05-23T12:49:30.483464+00:00", "display_name": "Bug Watch Updater", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/expired_members", "homepage_content": null, "name": "bug-watch-updater", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-watch-updater\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/irc_nicknames", "is_valid": true, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/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\/~carlos\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~carlos", "preferred_email_address_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/+email\/carlos@canonical.com", "date_created": "2005-06-06T08:59:51.615543+00:00", "display_name": "Carlos Perell\u00f3 Mar\u00edn", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/expired_members", "homepage_content": null, "name": "carlos", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/mugshot", "is_team": false, "karma": 9, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~carlos\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/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\/~valyag\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~valyag", "preferred_email_address_link": null, "date_created": "2005-06-06T08:59:51.562857+00:00", "display_name": "Carlos Valdivia Yag\u00fce", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/expired_members", "homepage_content": null, "name": "valyag", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~valyag\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/irc_nicknames", "is_valid": true, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/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\/~cprov\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~cprov", "preferred_email_address_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/+email\/celso.providelo@canonical.com", "date_created": "2005-06-06T08:59:51.597050+00:00", "display_name": "Celso Providelo", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/expired_members", "homepage_content": null, "name": "cprov", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~cprov\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/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\/~kiko\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~kiko", "preferred_email_address_link": null, "date_created": "2005-06-06T08:59:51.594941+00:00", "display_name": "Christian Reis", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/expired_members", "homepage_content": null, "name": "kiko", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~kiko\/indirect_participations"}], "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#people"} ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/personset.json 0000644 0001750 0001750 00000031337 00000000000 024046 0 ustar 00cjwatson cjwatson 0000000 0000000 {"total_size": 63, "start": 0, "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#people", "next_collection_link": "http:\/\/api.launchpad.dev\/beta\/people?ws.start=5&ws.size=5", "entries": [{"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"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/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\/~jorge-gonzalez-gonzalez\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez", "preferred_email_address_link": null, "date_created": "2005-06-06T08:59:51.559519+00:00", "display_name": "Aloriel", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/expired_members", "homepage_content": null, "name": "jorge-gonzalez-gonzalez", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~jorge-gonzalez-gonzalez\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/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\/~spiv\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~spiv", "preferred_email_address_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/+email\/andrew.bennetts@ubuntulinux.com", "date_created": "2005-06-06T08:59:51.551196+00:00", "display_name": "Andrew Bennetts", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/expired_members", "homepage_content": null, "name": "spiv", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~spiv\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/irc_nicknames", "is_valid": false, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/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\/~andrelop\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop", "preferred_email_address_link": null, "date_created": "2005-06-06T08:59:51.561685+00:00", "display_name": "Andr\u00e9 Lu\u00eds Lopes", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/expired_members", "homepage_content": null, "name": "andrelop", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~andrelop\/indirect_participations"}, {"languages_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/languages", "members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/members", "sub_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/sub_teams", "open_membership_invitations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/open_membership_invitations", "proposed_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/proposed_members", "memberships_details_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/memberships_details", "invited_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/invited_members", "deactivated_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/deactivated_members", "irc_nicknames_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/irc_nicknames", "is_valid": true, "latitude": null, "confirmed_email_addresses_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/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\/~bug-importer\/members_details", "hide_email_addresses": false, "admins_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/admins", "visibility": "Public", "self_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer", "preferred_email_address_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/+email\/bug-importer@launchpad.net", "date_created": "2005-12-06T09:48:58.287679+00:00", "display_name": "Bug Importer", "expired_members_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/expired_members", "homepage_content": null, "name": "bug-importer", "resource_type_link": "http:\/\/api.launchpad.dev\/beta\/#person", "super_teams_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/super_teams", "participants_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/participants", "time_zone": null, "longitude": null, "mugshot_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/mugshot", "is_team": false, "karma": 0, "wiki_names_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/wiki_names", "participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/participations", "jabber_ids_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/jabber_ids", "indirect_participations_collection_link": "http:\/\/api.launchpad.dev\/beta\/~bug-importer\/indirect_participations"}]} ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326800433.0
wadllib-1.3.6/src/wadllib/tests/data/root.json 0000644 0001750 0001750 00000000210 00000000000 022771 0 ustar 00cjwatson cjwatson 0000000 0000000 {"people_collection_link": "http:\/\/api.launchpad.dev\/beta\/people", "bugs_collection_link": "http:\/\/api.launchpad.dev\/beta\/bugs"} ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1626257220.0
wadllib-1.3.6/src/wadllib/tests/test_docs.py 0000644 0001750 0001750 00000003373 00000000000 022560 0 ustar 00cjwatson cjwatson 0000000 0000000 # 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 .
"""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 = []
if pkg_resources.resource_exists('wadllib', 'docs'):
for name in pkg_resources.resource_listdir('wadllib', 'docs'):
if name.endswith('.rst'):
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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1631548750.3172188
wadllib-1.3.6/src/wadllib.egg-info/ 0000755 0001750 0001750 00000000000 00000000000 020541 5 ustar 00cjwatson cjwatson 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548750.0
wadllib-1.3.6/src/wadllib.egg-info/PKG-INFO 0000644 0001750 0001750 00000111432 00000000000 021640 0 ustar 00cjwatson cjwatson 0000000 0000000 Metadata-Version: 2.1
Name: wadllib
Version: 1.3.6
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 .
=======
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,
... 'Some 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__
>>> 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
>>> known_resource.linked_resource
>>> 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 or tag. Sometimes a representation
is defined within a WADL 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 or 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):
... 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=u"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=u"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', 'multipart/form-data')
>>> media_type, doc = method.build_representation(
... text_field=u"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()
================
NEWS for wadllib
================
1.3.6 (2021-09-13)
==================
- Remove buildout support in favour of tox. [bug=922605]
- Adjust versioning strategy to avoid importing pkg_resources, which is slow
in large environments.
1.3.5 (2021-01-20)
==================
- Drop support for Python 3.2, 3.3, and 3.4.
- Accept Unicode parameter values again when performing multipart/form-data
encoding on Python 2 (broken in 1.3.3).
1.3.4 (2020-04-29)
==================
- Advertise support for Python 3.8.
- Add Python 3.9 compatibility by using xml.etree.ElementTree if
xml.etree.cElementTree does not exist. [bug=1870294]
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 tags that go beneath 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 :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Provides-Extra: docs
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548750.0
wadllib-1.3.6/src/wadllib.egg-info/SOURCES.txt 0000644 0001750 0001750 00000001561 00000000000 022430 0 ustar 00cjwatson cjwatson 0000000 0000000 COPYING.txt
HACKING.rst
NEWS.rst
README.rst
setup.py
src/wadllib/__init__.py
src/wadllib/application.py
src/wadllib/iso_strptime.py
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/docs/Makefile
src/wadllib/docs/NEWS.rst
src/wadllib/docs/index.rst
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 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548750.0
wadllib-1.3.6/src/wadllib.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 00000000000 024607 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1326807790.0
wadllib-1.3.6/src/wadllib.egg-info/not-zip-safe 0000644 0001750 0001750 00000000001 00000000000 022767 0 ustar 00cjwatson cjwatson 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548750.0
wadllib-1.3.6/src/wadllib.egg-info/requires.txt 0000644 0001750 0001750 00000000121 00000000000 023133 0 ustar 00cjwatson cjwatson 0000000 0000000 lazr.uri
setuptools
[:python_version < "3.8"]
importlib-metadata
[docs]
Sphinx
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1631548750.0
wadllib-1.3.6/src/wadllib.egg-info/top_level.txt 0000644 0001750 0001750 00000000010 00000000000 023262 0 ustar 00cjwatson cjwatson 0000000 0000000 wadllib