diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/config/schema.cfg | 6 | ||||
| -rw-r--r-- | src/mailman/queue/docs/rest.txt | 1 | ||||
| -rw-r--r-- | src/mailman/rest/docs/basic.txt | 1 | ||||
| -rw-r--r-- | src/mailman/rest/docs/helpers.txt | 59 | ||||
| -rw-r--r-- | src/mailman/rest/helpers.py | 73 | ||||
| -rw-r--r-- | src/mailman/rest/root.py | 88 | ||||
| -rw-r--r-- | src/mailman/rest/webservice.py | 50 | ||||
| -rw-r--r-- | src/mailman/rest/wsgiapp.py | 2 |
8 files changed, 224 insertions, 56 deletions
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 824c265fe..4f3475b3f 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -291,13 +291,13 @@ port: 8001 # Whether or not requests to the web service are secured through SSL. use_https: no -# Default view permission for the admin web service. -view_permission: None - # Whether or not to show tracebacks in an HTTP response for a request that # raised an exception. show_tracebacks: yes +# The API version number for the current API. +api_version: 3.0 + [language.master] # Template for language definitions. The section name must be [language.xx] diff --git a/src/mailman/queue/docs/rest.txt b/src/mailman/queue/docs/rest.txt index b46f0f304..2df4da9e4 100644 --- a/src/mailman/queue/docs/rest.txt +++ b/src/mailman/queue/docs/rest.txt @@ -14,7 +14,6 @@ The RESTful server can be used to access basic version information. http_etag: "..." mailman_version: GNU Mailman 3.0... (...) python_version: ... - resource_type_link: http://localhost:8001/3.0/#system self_link: http://localhost:8001/3.0/system diff --git a/src/mailman/rest/docs/basic.txt b/src/mailman/rest/docs/basic.txt index 6d928ab85..643d6d906 100644 --- a/src/mailman/rest/docs/basic.txt +++ b/src/mailman/rest/docs/basic.txt @@ -16,7 +16,6 @@ returned. http_etag: "..." mailman_version: GNU Mailman 3.0... (...) python_version: ... - resource_type_link: http://localhost:8001/3.0/#system self_link: http://localhost:8001/3.0/system diff --git a/src/mailman/rest/docs/helpers.txt b/src/mailman/rest/docs/helpers.txt new file mode 100644 index 000000000..9304bbb17 --- /dev/null +++ b/src/mailman/rest/docs/helpers.txt @@ -0,0 +1,59 @@ +================ +REST API helpers +================ + +There are a number of helpers that make building out the REST API easier. + + +Resource paths +============== + +For example, most resources don't have to worry about where they are rooted. +They only need to know where they are relative to the root URI, and this +function can return them the full path to the resource. + + >>> from mailman.rest.helpers import path_to + >>> print path_to('system') + http://localhost:8001/3.0/system + +Parameters like the scheme, host, port, and API version number can be set in +the configuration file. + + >>> config.push('helpers', """ + ... [webservice] + ... hostname: geddy + ... port: 2112 + ... use_https: yes + ... api_version: 4.2 + ... """) + + >>> print path_to('system') + https://geddy:2112/4.2/system + + +Etags +===== + +HTTP etags are a way for clients to decide whether their copy of a resource +has changed or not. Mailman's REST API calculates this in a cheap and dirty +way. Pass in the dictionary representing the resource and that dictionary +gets modified to contain the etag under the `http_etag` key. + + >>> from mailman.rest.helpers import etag + >>> resource = dict(geddy='bass', alex='guitar', neil='drums') + >>> json_data = etag(resource) + >>> print resource['http_etag'] + "43942176d8d5bb4414ccf35e2720ccd5251e66da" + +For convenience, the etag function also returns the JSON representation of the +dictionary after tagging, since that's almost always what you want. + + >>> import json + >>> data = json.loads(json_data) + + # This is pretty close to what we want, so it's convenient to use. + >>> dump_msgdata(data) + alex : guitar + geddy : bass + http_etag: "43942176d8d5bb4414ccf35e2720ccd5251e66da" + neil : drums diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py new file mode 100644 index 000000000..d1afdb0d9 --- /dev/null +++ b/src/mailman/rest/helpers.py @@ -0,0 +1,73 @@ +# Copyright (C) 2010 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Web service helpers.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'etag', + 'path_to', + ] + + +import json +import hashlib + +from lazr.config import as_boolean +from mailman.config import config + + + +def path_to(resource): + """Return the url path to a resource. + + :param resource: The canonical path to the resource, relative to the + system base URI. + :type resource: string + :return: The full path to the resource. + :rtype: string + """ + return '{0}://{1}:{2}/{3}/{4}'.format( + ('https' if as_boolean(config.webservice.use_https) else 'http'), + config.webservice.hostname, + config.webservice.port, + config.webservice.api_version, + (resource[1:] if resource.startswith('/') else resource), + ) + + + +def etag(resource): + """Calculate the etag and return a JSON representation. + + The input is a dictionary representing the resource. This dictionary must + not contain an `http_etag` key. This function calculates the etag by + using the sha1 hexdigest of the repr of the dictionary. It then inserts + this value under the `http_etag` key, and returns the JSON representation + of the modified dictionary. + + :param resource: The original resource representation. + :type resource: dictionary + :return: JSON representation of the modified dictionary. + :rtype string + """ + assert 'http_etag' not in resource, 'Resource already etagged' + etag = hashlib.sha1(repr(resource)).hexdigest() + resource['http_etag'] = '"{0}"'.format(etag) + return json.dumps(resource) diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py new file mode 100644 index 000000000..e3ba1b62e --- /dev/null +++ b/src/mailman/rest/root.py @@ -0,0 +1,88 @@ +# Copyright (C) 2010 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman 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 General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""The root of the REST API.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'Root', + ] + + +import json +import hashlib + +from restish import http, resource + +from mailman.config import config +from mailman.core.system import system +from mailman.rest.helpers import etag, path_to +from mailman.rest.webservice import ( + ADomain, AList, AllDomains, AllLists, AllMembers) + + + +class Root(resource.Resource): + """The RESTful root resource. + + At the root of the tree are the API version numbers. Everything else + lives underneath those. Currently there is only one API version number, + and we start at 3.0 to match the Mailman version number. That may not + always be the case though. + """ + @resource.child(config.webservice.api_version) + def api_version(self, request, segments): + return TopLevel() + + +class TopLevel(resource.Resource): + """Top level collections and entries.""" + + @resource.child() + def system(self, request, segments): + """/<api>/system""" + resource = dict( + mailman_version=system.mailman_version, + python_version=system.python_version, + self_link=path_to('system'), + ) + return http.ok([], etag(resource)) + + @resource.child() + def domains(self, request, segments): + if len(segments) == 0: + return AllDomains() + elif len(segments) == 1: + return ADomain(segments[0]), [] + else: + return http.bad_request() + + @resource.child() + def lists(self, request, segments): + if len(segments) == 0: + return AllLists() + else: + list_name = segments.pop(0) + return AList(list_name), segments + + @resource.child() + def members(self, request, segments): + if len(segments) == 0: + return AllMembers() + return http.bad_request() diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py index 9db29f133..03c2097af 100644 --- a/src/mailman/rest/webservice.py +++ b/src/mailman/rest/webservice.py @@ -21,7 +21,6 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ - 'Root', ] @@ -37,7 +36,6 @@ from zope.interface import implements from mailman.app.membership import delete_member from mailman.config import config -from mailman.core.system import system from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.domain import ( BadDomainSpecificationError, IDomain, IDomainManager) @@ -54,54 +52,6 @@ log = logging.getLogger('mailman.http') -class Root(resource.Resource): - """The RESTful root resource.""" - - @resource.child('3.0') - def api_version(self, request, segments): - return TopLevel() - - -class TopLevel(resource.Resource): - """Top level collections and entries.""" - - @resource.child() - def system(self, request, segments): - response = dict( - mailman_version=system.mailman_version, - python_version=system.python_version, - resource_type_link='http://localhost:8001/3.0/#system', - self_link='http://localhost:8001/3.0/system', - ) - etag = hashlib.sha1(repr(response)).hexdigest() - response['http_etag'] = '"{0}"'.format(etag) - return http.ok([], json.dumps(response)) - - @resource.child() - def domains(self, request, segments): - if len(segments) == 0: - return AllDomains() - elif len(segments) == 1: - return ADomain(segments[0]), [] - else: - return http.bad_request() - - @resource.child() - def lists(self, request, segments): - if len(segments) == 0: - return AllLists() - else: - list_name = segments.pop(0) - return AList(list_name), segments - - @resource.child() - def members(self, request, segments): - if len(segments) == 0: - return AllMembers() - return http.bad_request() - - - class _DomainBase(resource.Resource): """Shared base class for domain representations.""" diff --git a/src/mailman/rest/wsgiapp.py b/src/mailman/rest/wsgiapp.py index cfb8cba50..4ee674fa4 100644 --- a/src/mailman/rest/wsgiapp.py +++ b/src/mailman/rest/wsgiapp.py @@ -33,7 +33,7 @@ from wsgiref.simple_server import WSGIRequestHandler from wsgiref.simple_server import make_server as wsgi_server from mailman.config import config -from mailman.rest.webservice import Root +from mailman.rest.root import Root log = logging.getLogger('mailman.http') |
