# HG changeset patch # User Jeff Hammel # Date 1307458989 25200 # Node ID ec815b7cb142629d118b5888efc8b72104068985 initial commit of wsgintegrate diff -r 000000000000 -r ec815b7cb142 entry-points.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/entry-points.txt Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,17 @@ +Possible entry points for wsgintegrate +====================================== + +# XXX port of legacy wsgiblob documentation + +wsgintegrate does any number of things by convention. By documenting +what it does and the current conditions used, it may be seen how it +may be extended in the future + +* match conditions: [wsgintegrate.conditions] +* loader types: [wsgintegrate.loader] (EntryPointLoader, FileLoader, PathLoader) +* formats: JSON, .ini +* servers + +Currently, IniFactory and JSONfactory handle transforming +configuration to and from a WSGIFactory. Ideally, these would be +pluggable. diff -r 000000000000 -r ec815b7cb142 flowerbed.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flowerbed.txt Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,13 @@ +Flowerbed is a web application that allows one to grow a flower from a +preselected group of WSGI apps. Flowerbed presents a nested ordered +list of web applications. You drag one of the preselected webapps to a +place on the list. This will add the web app to configuration and the +website and give a default path descriminator (e.g. /blog for +bitsyblog). You should be able to download the configuration in either +JSON or ini format. In addition, you can download a complete flower +python package created from a template that will allow the whole site +to be installed and managed locally. + +See also: + +http://k0s.org/blog/20091227221009 diff -r 000000000000 -r ec815b7cb142 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,36 @@ +from setuptools import setup, find_packages +import os + +version = '0.1.1' + +# description +try: + filename = os.path.join(os.path.dirname(__file__), 'README.txt') + description = file(filename).read() +except: + description = '' + +# dependencies +dependencies = ['webob'] +try: + import json +except ImportError: + dependencies.append('simplejson') + +setup(name='wsgintegrate', + version=version, + description='WSGI integration layer', + long_description=description, + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='', + author='Jeff Hammel', + author_email='jhammel@mozilla.com', + url='http://k0s.org/', + license='GPL', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=dependencies, + entry_points=""" + """, + ) diff -r 000000000000 -r ec815b7cb142 wsgintegrate/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/__init__.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,1 @@ +# diff -r 000000000000 -r ec815b7cb142 wsgintegrate/dispatcher.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/dispatcher.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,26 @@ +""" +multi-application dispatcher for WSGI apps +""" + +from webob import Request +from webob import exc + +class Dispatcher(object): + + def __init__(self, *apps): + self.apps = apps + self.codes = set([404]) + + def __call__(self, environ, start_response): + request = Request(environ) + for app in self.apps: + try: + response = request.get_response(app) + if response.status_int in self.codes: + continue + break + except exc.HTTPNotFound: + continue + else: + response = exc.HTTPNotFound() + return response(environ, start_response) diff -r 000000000000 -r ec815b7cb142 wsgintegrate/main.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/main.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +""" +command line entry point for wsgiblob +serves an application from a .ini file using the [server] +section specified or wsgiref otherwise +""" + +import sys +from pyloader.factory import IniFactory +from optparse import OptionParser +from server import wsgiref + +def main(args=sys.argv[1:]): + + # parse command line options + usage = '%prog [options] config-file' + parser = OptionParser(usage=usage, description=__doc__) + parser.add_option('-a', '--app', dest='app', default='', + help='which app to serve from the configuration') + parser.add_option('-p', '--port', dest='port', + type='int', default=80, + help='which port to serve on, if server not specified in configuration') + parser.add_option('--list-apps', dest='list_apps', + action='store_true', default=False, + help='list the WSGI apps available in the configuration') + parser.add_option('--print-json', dest='print_json', + action='store_true', default=False, + help='print JSON format of the configuration') + parser.add_option('--print-ini', dest='print_ini', + action='store_true', default=False, + help='print .ini format of the configuration') + options, args = parser.parse_args(args) + + # check for single configuration file + if len(args) != 1: # TODO: could munge multiple configs + parser.print_usage() + parser.exit() + config = args[0] + + # create a factory from configuration + # TODO: interpret if the configuration is .ini, JSON, etc + factory = IniFactory(config, main=options.app) + + # print configuration, if specified + if options.list_apps: + for app in sorted(factory.config.keys()): + print app + return + if options.print_json: + print factory.json_config() + return + if options.print_ini: + print factory.ini_config() + return + + wsgiref(app=factory.load(), port=options.port) + +if __name__ == '__main__': + main() diff -r 000000000000 -r ec815b7cb142 wsgintegrate/match.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/match.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,181 @@ +""" +utilities for matching requests +these are just a sample; you can add arbitrary match objects if desired +""" + +from webob import exc + +class RequestMatch(object): + """abstract base class for matching requests""" + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + """WSGI app""" + if not self.condition(environ): + raise exc.HTTPNotFound + return self.app(environ, start_response) + + def condition(self, environ): + """match condition""" + return True + +class ConditionMatch(RequestMatch): + """generic environment-based condition-checker""" + # XXX unused + def __init__(self, app, condition): + RequestMatch.__init__(self, app) + self.condition = condition + +### aspect-based checkers + +class MatchPath(RequestMatch): + """match based on PATH INFO""" + + def __init__(self, app, path): + RequestMatch.__init__(self, app) + self.path = path + + def match(self, path): + if path.startswith(self.path): + # currently only matches with str.startswith; + # different types of matches could be considered + return self.path, path[len(self.path):] + + def condition(self, environ): # XXX unused + return self.match(environ['PATH_INFO']) is not None + + def __call__(self, environ, start_response): + match = self.match(environ['PATH_INFO']) + if match is None: + raise exc.HTTPNotFound + script_name, path_info = match + + # fix up the environment for downstream applications + environ['SCRIPT_NAME'] = script_name + environ['PATH_INFO'] = path_info + + return self.app(environ, start_response) + +class MatchMethod(RequestMatch): + """match based on request method""" + + def __init__(self, app, methods): + RequestMatch.__init__(self, app) + if isinstance(methods, basestring): + methods = methods.split() + self.methods = set(methods) + + def condition(self, environ): + return environ['REQUEST_METHOD'] in self.methods + +class MatchHost(RequestMatch): + """match based on the host and port""" + + def __init__(self, app, host, port=None): + RequestMatch.__init__(self, app) + self.host = host + self.port = port + def condition(self, environ): + host = environ['HTTP_HOST'] + port = None + if ':' in host: + host, port = host.rsplit(':', 1) + if self.port and port != self.port: + return False + return host == self.host + +class MatchAuthorized(RequestMatch): + """match if a user is authorized or not""" + def __init__(self, app, users=None): + RequestMatch.__init__(self, app) + self.authorized_users = users + def condition(self, environ): + raise NotImplementedError # TODO + +class MatchProtocol(RequestMatch): + """match a given protocol, i.e. http vs https://""" + def __init__(self, app, protocol): + self.protocol = protocol + RequestMatch.__init__(self, app) + def condition(self, environ): + raise NotImplementedError # TODO + +class MatchQueryString(RequestMatch): + """ + match a request based on if the right query string parameters are given + """ + def __init__(self, app, *tags, **kw): + self.app = app + self.tags = tags + self.kw = kw + def condition(self, environ): + raise NotImplementedError # TODO + +### logical checkers (currently unused) + +class AND(RequestMatch): + def __init__(self, app, condition1, condition2, *conditions): + RequestMatch.__init__(self, app) + self.conditions = [condition1, condition2] + self.conditions.extend(conditions) + def condition(self, environ): + for condition in self.conditions: + if isinstance(condition, RequestMatch): + if not condition.condition(environ): + return False + else: + if not condition(): + return False + return True + +class OR(RequestMatch): + def __init__(self, app, condition1, condition2, *conditions): + RequestMatch.__init__(self, app) + self.conditions = [condition1, condition2] + self.conditions.extend(conditions) + def condition(self, environ): + for condition in self.conditions: + if isinstance(condition, RequestMatch): + if condition.condition(environ): + return True + else: + if condition(): + return + return False + +# string accessible list of conditions +conditions = {'host': MatchHost, + 'method': MatchMethod, + 'path': MatchPath } + +class WrapApp(object): + """match string-based conditions""" + + def __init__(self, conditions=None): + self.conditions = conditions or globals()['conditions'] + + def __call__(self, app, *conditions, **kwargs): + """ + wrap an app in conditions + conditions should be a key, value 2-tuple of string, args; + kwargs should be a dictionary of unordered conditions, + likewise of the form string, args. + use *conditions if order is important, otherwise kwargs + """ + + # determine the condition + conditions = list(conditions) + if kwargs: + conditions.extend(kwargs.items()) + + # wrap the application + for condition, args in conditions: + assert condition in self.conditions, 'Condition "%s" unknown' % condition + app = self.conditions[condition](app, args) + return app + +# convenience invocation +wrap = WrapApp(conditions) + diff -r 000000000000 -r ec815b7cb142 wsgintegrate/server.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/server.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,13 @@ +""" +front-ends for various WSGI servers +""" + +from factory import IniFactory + +def wsgiref(app, host='0.0.0.0', port=80): + from wsgiref import simple_server + server = simple_server.make_server(host=host, port=int(port), app=app) + server.serve_forever() + +def paster(global_conf, **kw): + return IniFactory(**kw) diff -r 000000000000 -r ec815b7cb142 wsgintegrate/web.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wsgintegrate/web.py Tue Jun 07 08:03:09 2011 -0700 @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# XXX legacy WSGIblob code; to be ported [TODO] + +""" +web handlers for wsgiblob +""" + +import sys +from webob import exc +from webob import Request +from webob import Response + +class JSONhandler(object): + """handles JSON requests""" + + def __init__(self, factory): + self.factory = factory + + def __call__(self, environ, start_response): + request = Request(environ) + if request.method == 'GET': + response = Response(content_type='application/json', + body=self.factory.json_config()) + return response(environ, start_response) + elif request.method == 'POST': + raise NotImplementedError + else: + raise NotImplementedError + +def main(args=sys.argv[1:]): + from factory import StringFactory + raise NotImplementedError + f = StringFactory(config={'':dict(type='app')}) + +if __name__ == '__main__': + main()