summaryrefslogtreecommitdiff
path: root/src/mailman/rest/webservice.py
diff options
context:
space:
mode:
authorBarry Warsaw2010-02-23 14:31:41 -0500
committerBarry Warsaw2010-02-23 14:31:41 -0500
commit0fa9260a3d23b7ba17de727e66bcc27a0aeacc29 (patch)
treebc45a5ba133e22eac478196436f9e5360c74ebfe /src/mailman/rest/webservice.py
parent6ebd29504f204913a1c57f64d802151fc97bfb41 (diff)
downloadmailman-0fa9260a3d23b7ba17de727e66bcc27a0aeacc29.tar.gz
mailman-0fa9260a3d23b7ba17de727e66bcc27a0aeacc29.tar.zst
mailman-0fa9260a3d23b7ba17de727e66bcc27a0aeacc29.zip
Diffstat (limited to 'src/mailman/rest/webservice.py')
-rw-r--r--src/mailman/rest/webservice.py225
1 files changed, 169 insertions, 56 deletions
diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py
index 5c42635cb..8a1e919bf 100644
--- a/src/mailman/rest/webservice.py
+++ b/src/mailman/rest/webservice.py
@@ -27,85 +27,185 @@ __all__ = [
]
+import json
+import hashlib
import logging
-# Don't use wsgiref.simple_server.make_server() because we need to override
-# BaseHTTPRequestHandler.log_message() so that logging output will go to the
-# proper Mailman logger instead of stderr, as is the default.
-from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
+from restish.app import RestishApp
+from restish import http, resource
+from wsgiref.simple_server import (
+ make_server as wsgi_server, WSGIRequestHandler)
-from lazr.restful import register_versioned_request_utility
-from lazr.restful.interfaces import (
- IServiceRootResource, IWebServiceClientRequest)
-from lazr.restful.simple import Request, RootResource
-from lazr.restful.wsgi import WSGIApplication
from zope.component import getUtility
from zope.interface import implements
-from zope.publisher.publish import publish
from mailman.config import config
from mailman.core.system import system
-from mailman.interfaces.domain import IDomain, IDomainCollection
+from mailman.interfaces.domain import (
+ BadDomainSpecificationError, IDomain, IDomainManager)
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.mailinglist import IMailingList
from mailman.interfaces.member import IMember
from mailman.interfaces.membership import ISubscriptionService
from mailman.interfaces.rest import IResolvePathNames
-from mailman.rest.publication import AdminWebServicePublication
+#from mailman.rest.publication import AdminWebServicePublication
+
+COMMASPACE = ', '
log = logging.getLogger('mailman.http')
-# Marker interfaces for multiversion lazr.restful.
-#
-# XXX 2010-02-16 barry Gah! lazr.restful's multiversion.txt document says
-# these classes should get generated, and the registrations should happen,
-# automatically. This is not the case AFAICT. Why?!
+class Root(resource.Resource):
+ """The RESTful root resource."""
-class I30Version(IWebServiceClientRequest):
- pass
+ @resource.child('3.0')
+ def api_version(self, request, segments):
+ return TopLevel()
-class IDevVersion(IWebServiceClientRequest):
- pass
+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))
-
-class AdminWebServiceRootResource(RootResource):
- """The lazr.restful non-versioned root 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()
- implements(IResolvePathNames)
- # XXX 2010-02-16 barry lazr.restful really wants this class to exist and
- # be a subclass of RootResource. Our own traversal really wants this to
- # implement IResolvePathNames. RootResource says to override
- # _build_top_level_objects() to return the top-level objects, but that
- # appears to never be called by lazr.restful, so you've got me. I don't
- # understand this, which sucks, so just ensure that it doesn't do anything
- # useful so if/when I do understand this, I can resolve the conflict
- # between the way lazr.restful wants us to do things and the way our
- # traversal wants to do things.
- def _build_top_level_objects(self):
- """See `RootResource`."""
- raise NotImplementedError('Magic suddenly got invoked')
+class _DomainBase(resource.Resource):
+ """Shared base class for domain representations."""
- def get(self, name):
- """See `IResolvePathNames`."""
- top_names = dict(
- domains=getUtility(IDomainCollection),
- lists=getUtility(IListManager),
- members=getUtility(ISubscriptionService),
- system=system,
+ def _format_domain(self, domain):
+ """Format the data for a single domain."""
+ domain_data = dict(
+ base_url=domain.base_url,
+ contact_address=domain.contact_address,
+ description=domain.description,
+ email_host=domain.email_host,
+ resource_type_link='http://localhost:8001/3.0/#domain',
+ self_link='http://localhost:8001/3.0/domains/{0}'.format(
+ domain.email_host),
+ url_host=domain.url_host,
)
- return top_names.get(name)
+ etag = hashlib.sha1(repr(domain_data)).hexdigest()
+ domain_data['http_etag'] = '"{0}"'.format(etag)
+ return domain_data
+
+
+class ADomain(_DomainBase):
+ """A domain."""
+ def __init__(self, domain):
+ self._domain = domain
-class AdminWebServiceApplication(WSGIApplication):
- """A WSGI application for the admin REST interface."""
+ @resource.GET()
+ def domain(self, request):
+ """Return a single domain end-point."""
+ domain = getUtility(IDomainManager).get(self._domain)
+ if domain is None:
+ return http.not_found()
+ return http.ok([], json.dumps(self._format_domain(domain)))
+
+
+class AllDomains(_DomainBase):
+ """The domains."""
+
+ @resource.POST()
+ def create(self, request):
+ """Create a new domain."""
+ # XXX 2010-02-23 barry Sanity check the POST arguments by
+ # introspection of the target method, or via descriptors.
+ domain_manager = getUtility(IDomainManager)
+ try:
+ # Hmmm... webob gives this to us as a string, but we need
+ # unicodes. For backward compatibility with lazr.restful style
+ # requests, ignore any ws.op parameter.
+ kws = dict((key, unicode(value))
+ for key, value in request.POST.items()
+ if key != 'ws.op')
+ domain = domain_manager.add(**kws)
+ except BadDomainSpecificationError:
+ return http.bad_request([], 'Domain exists')
+ # wsgiref wants headers to be strings, not unicodes.
+ location = 'http://localhost:8001/3.0/domains/{0}'.format(
+ domain.email_host)
+ # Include no extra headers or body.
+ return http.created(str(location), [], None)
+
+ @resource.GET()
+ def container(self, request):
+ """Return the /domains end-point."""
+ domains = list(getUtility(IDomainManager))
+ if len(domains) == 0:
+ return http.ok(
+ [], json.dumps(dict(resource_type_link=
+ 'http://localhost:8001/3.0/#domains',
+ start=None,
+ total_size=0)))
+ entries = []
+ response = dict(
+ resource_type_link='http://localhost:8001/3.0/#domains',
+ start=0,
+ total_size=len(domains),
+ entries=entries,
+ )
+ for domain in domains:
+ domain_data = self._format_domain(domain)
+ entries.append(domain_data)
+ return http.ok([], json.dumps(response))
- # The only thing we need to override is the publication class.
- publication_class = AdminWebServicePublication
+
+## class AdminWebServiceRootResource(RootResource):
+## """The lazr.restful non-versioned root resource."""
+
+## implements(IResolvePathNames)
+
+## # XXX 2010-02-16 barry lazr.restful really wants this class to exist and
+## # be a subclass of RootResource. Our own traversal really wants this to
+## # implement IResolvePathNames. RootResource says to override
+## # _build_top_level_objects() to return the top-level objects, but that
+## # appears to never be called by lazr.restful, so you've got me. I don't
+## # understand this, which sucks, so just ensure that it doesn't do anything
+## # useful so if/when I do understand this, I can resolve the conflict
+## # between the way lazr.restful wants us to do things and the way our
+## # traversal wants to do things.
+## def _build_top_level_objects(self):
+## """See `RootResource`."""
+## raise NotImplementedError('Magic suddenly got invoked')
+
+## def get(self, name):
+## """See `IResolvePathNames`."""
+## top_names = dict(
+## domains=getUtility(IDomainCollection),
+## lists=getUtility(IListManager),
+## members=getUtility(ISubscriptionService),
+## system=system,
+## )
+## return top_names.get(name)
+
+
+## class AdminWebServiceApplication(WSGIApplication):
+## """A WSGI application for the admin REST interface."""
+
+## # The only thing we need to override is the publication class.
+## publication_class = AdminWebServicePublication
class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler):
@@ -116,16 +216,29 @@ class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler):
log.info('%s - - %s', self.address_string(), format % args)
+class AdminWebServiceApplication(RestishApp):
+ """Interpose in the restish request processor."""
+
+ def __call__(self, environ, start_response):
+ """See `RestishApp`."""
+ try:
+ response = super(AdminWebServiceApplication, self).__call__(
+ environ, start_response)
+ except:
+ config.db.abort()
+ raise
+ else:
+ config.db.commit()
+ return response
+
+
def make_server():
"""Create the WSGI admin REST server."""
- # XXX 2010-02-16 barry Gah! lazr.restful's multiversion.txt document says
- # these classes should get generated, and the registrations should happen,
- # automatically. This is not the case AFAICT. Why?!
- register_versioned_request_utility(I30Version, '3.0')
- register_versioned_request_utility(IDevVersion, 'dev')
+ app = AdminWebServiceApplication(Root())
host = config.webservice.hostname
port = int(config.webservice.port)
- server = WSGIServer((host, port), AdminWebServiceWSGIRequestHandler)
- server.set_app(AdminWebServiceApplication)
+ server = wsgi_server(
+ host, port, app,
+ handler_class=AdminWebServiceWSGIRequestHandler)
return server