summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/config/schema.cfg6
-rw-r--r--src/mailman/queue/docs/rest.txt1
-rw-r--r--src/mailman/rest/docs/basic.txt1
-rw-r--r--src/mailman/rest/docs/helpers.txt59
-rw-r--r--src/mailman/rest/helpers.py73
-rw-r--r--src/mailman/rest/root.py88
-rw-r--r--src/mailman/rest/webservice.py50
-rw-r--r--src/mailman/rest/wsgiapp.py2
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')