From 296d14ea3081c0041a7445a2b157e57f5214c708 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Thu, 25 Feb 2010 19:01:32 -0500 Subject: Refactoring the REST support by removing unnecessary stuff and moving other stuff around. We no longer need APIValueError or IResolvePathNames. Also, refactor the creation of the REST server so that it could be used with other WSGI frameworks. --- src/mailman/interfaces/rest.py | 44 -------------------- src/mailman/model/listmanager.py | 3 +- src/mailman/queue/rest.py | 2 +- src/mailman/rest/adapters.py | 14 ++----- src/mailman/rest/webservice.py | 86 ++------------------------------------- src/mailman/rest/wsgiapp.py | 87 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 140 deletions(-) delete mode 100644 src/mailman/interfaces/rest.py create mode 100644 src/mailman/rest/wsgiapp.py (limited to 'src') diff --git a/src/mailman/interfaces/rest.py b/src/mailman/interfaces/rest.py deleted file mode 100644 index 9ec7914ad..000000000 --- a/src/mailman/interfaces/rest.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2009-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 . - -"""Interfaces for the RESTful admin server.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'APIValueError', - 'IResolvePathNames', - ] - - -from zope.interface import Interface - -from mailman.core.errors import MailmanError - - - -class APIValueError(MailmanError, ValueError): - """A `ValueError` from the REST API.""" - - - -class IResolvePathNames(Interface): - """A marker interface objects that implement simple traversal.""" - - def get(name): - """Traverse to a contained object.""" diff --git a/src/mailman/model/listmanager.py b/src/mailman/model/listmanager.py index cc3226b80..3d0448e8e 100644 --- a/src/mailman/model/listmanager.py +++ b/src/mailman/model/listmanager.py @@ -32,7 +32,6 @@ from zope.interface import implements from mailman.config import config from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError -from mailman.interfaces.rest import IResolvePathNames from mailman.model.mailinglist import MailingList @@ -40,7 +39,7 @@ from mailman.model.mailinglist import MailingList class ListManager: """An implementation of the `IListManager` interface.""" - implements(IListManager, IResolvePathNames) + implements(IListManager) # pylint: disable-msg=R0201 def create(self, fqdn_listname): diff --git a/src/mailman/queue/rest.py b/src/mailman/queue/rest.py index a14f0b081..a7c9727e2 100644 --- a/src/mailman/queue/rest.py +++ b/src/mailman/queue/rest.py @@ -32,7 +32,7 @@ import signal import logging from mailman.queue import Runner -from mailman.rest.webservice import make_server +from mailman.rest.wsgiapp import make_server log = logging.getLogger('mailman.http') diff --git a/src/mailman/rest/adapters.py b/src/mailman/rest/adapters.py index 2ae5d497e..6acfe3866 100644 --- a/src/mailman/rest/adapters.py +++ b/src/mailman/rest/adapters.py @@ -36,7 +36,6 @@ from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.listmanager import IListManager, NoSuchListError from mailman.interfaces.member import DeliveryMode from mailman.interfaces.membership import ISubscriptionService -from mailman.interfaces.rest import APIValueError @@ -77,15 +76,10 @@ class SubscriptionService: mlist = getUtility(IListManager).get(fqdn_listname) if mlist is None: raise NoSuchListError(fqdn_listname) - # Convert from string to enum. Turn Python's ValueErrors into one - # suitable for the REST API. - try: - mode = (DeliveryMode.regular - if delivery_mode is None - else DeliveryMode(delivery_mode)) - except ValueError: - raise APIValueError( - 'Invalid delivery_mode: {0}'.format(delivery_mode)) + # Convert from string to enum. + mode = (DeliveryMode.regular + if delivery_mode is None + else DeliveryMode(delivery_mode)) if real_name is None: real_name, at, domain = address.partition('@') if len(at) == 0: diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py index d7f7d231e..9db29f133 100644 --- a/src/mailman/rest/webservice.py +++ b/src/mailman/rest/webservice.py @@ -21,9 +21,7 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ - 'AdminWebServiceApplication', - 'AdminWebServiceRequest', - 'make_server', + 'Root', ] @@ -31,10 +29,8 @@ import json import hashlib import logging -from restish.app import RestishApp from restish import http, resource -from wsgiref.simple_server import ( - make_server as wsgi_server, WSGIRequestHandler) +from wsgiref.simple_server import make_server as wsgi_server from zope.component import getUtility from zope.interface import implements @@ -51,8 +47,6 @@ from mailman.interfaces.mailinglist import IMailingList from mailman.interfaces.member import ( AlreadySubscribedError, IMember, MemberRole) from mailman.interfaces.membership import ISubscriptionService -from mailman.interfaces.rest import APIValueError, IResolvePathNames -#from mailman.rest.publication import AdminWebServicePublication COMMASPACE = ', ' @@ -370,7 +364,7 @@ class AllMembers(_MemberBase): return http.bad_request([], b'No such list') except InvalidEmailAddressError: return http.bad_request([], b'Invalid email address') - except APIValueError as error: + except ValueError as error: return http.bad_request([], str(error)) # wsgiref wants headers to be bytes, not unicodes. location = b'http://localhost:8001/3.0/lists/{0}/member/{1}'.format( @@ -399,77 +393,3 @@ class AllMembers(_MemberBase): member_data = self._format_member(member) entries.append(member_data) return http.ok([], json.dumps(response)) - - - - -## 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): - """Handler class which just logs output to the right place.""" - - def log_message(self, format, *args): - """See `BaseHTTPRequestHandler`.""" - 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.""" - app = AdminWebServiceApplication(Root()) - host = config.webservice.hostname - port = int(config.webservice.port) - server = wsgi_server( - host, port, app, - handler_class=AdminWebServiceWSGIRequestHandler) - return server diff --git a/src/mailman/rest/wsgiapp.py b/src/mailman/rest/wsgiapp.py new file mode 100644 index 000000000..cfb8cba50 --- /dev/null +++ b/src/mailman/rest/wsgiapp.py @@ -0,0 +1,87 @@ +# 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 . + +"""Basic WSGI Application object for REST server.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'make_application', + 'make_server', + ] + + +import logging + +from restish.app import RestishApp +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 + + +log = logging.getLogger('mailman.http') + + + +class AdminWebServiceWSGIRequestHandler(WSGIRequestHandler): + """Handler class which just logs output to the right place.""" + + def log_message(self, format, *args): + """See `BaseHTTPRequestHandler`.""" + log.info('%s - - %s', self.address_string(), format % args) + + +class AdminWebServiceApplication(RestishApp): + """Connect the restish WSGI application to Mailman's database.""" + + 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_application(): + """Create the WSGI application. + + Use this if you want to integrate Mailman's REST server with your own WSGI + server. + """ + return AdminWebServiceApplication(Root()) + + +def make_server(): + """Create the Mailman REST server. + + Use this if you just want to run Mailman's wsgiref-based REST server. + """ + host = config.webservice.hostname + port = int(config.webservice.port) + server = wsgi_server( + host, port, make_application(), + handler_class=AdminWebServiceWSGIRequestHandler) + return server -- cgit v1.2.3-70-g09d2