changeset 0:ec815b7cb142

initial commit of wsgintegrate
author Jeff Hammel <jhammel@mozilla.com>
date Tue, 07 Jun 2011 08:03:09 -0700
parents
children fb4a692e2571
files entry-points.txt flowerbed.txt setup.py wsgintegrate/__init__.py wsgintegrate/dispatcher.py wsgintegrate/main.py wsgintegrate/match.py wsgintegrate/server.py wsgintegrate/web.py
diffstat 9 files changed, 384 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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.
--- /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
--- /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="""
+      """,
+      )
--- /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 @@
+#
--- /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)
--- /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()
--- /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)
+
--- /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)
--- /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()