[Web-SIG] Standardized configuration
Chris McDonough
chrism at plope.com
Fri Jul 22 22:38:07 CEST 2005
I've had a stab at creating a simple WSGI deployment implementation.
I use the term "WSGI component" in here as shorthand to indicate all
types of WSGI implementations (server, application, gateway).
The primary deployment concern is to create a way to specify the
configuration of an instance of a WSGI component, preferably within a
declarative configuration file. A secondary deployment concern is to
create a way to "wire up" components together into a specific
deployable "pipeline".
A strawman implementation that solves both issues via the
"configurator", which would be presumed to live in "wsgiref". Currently
it lives in a package named "wsgiconfig" on my laptop. This module
follows.
""" Configurator for establishing a WSGI pipeline """
from ConfigParser import ConfigParser
import types
def configure(path):
config = ConfigParser()
if isinstance(path, types.StringTypes):
config.readfp(open(path))
else:
config.readfp(path)
appsections = []
for name in config.sections():
if name.startswith('application:'):
appsections.append(name)
elif name == 'pipeline':
pass
else:
raise ValueError, '%s is not a valid section name'
app_defs = {}
for appsection in appsections:
app_config_file = config.get(appsection, 'config')
app_factory_name = config.get(appsection, 'factory')
app_name = appsection.split('application:')[1]
if app_config_file is None:
raise ValueError, ('application section %s requires a
"config" '
'option' % app_config_file)
if app_factory_name is None:
raise ValueError, ('application %s requires a "factory"'
' option' % app_factory_name)
app_defs[app_name] = {'config':app_config_file,
'factory':app_factory_name}
if not config.has_section('pipeline'):
raise ValueError, 'must have a "pipeline" section in config'
pipeline_str = config.get('pipeline', 'apps')
if pipeline_str is None:
raise ValueError, ('must have an "apps" definition in the '
'pipeline section')
pipeline_def = pipeline_str.split()
next = None
while pipeline_def:
app_name = pipeline_def.pop()
app_def = app_defs.get(app_name)
if app_def is None:
raise ValueError, ('appname %s os defined in pipeline '
'%s butno application is defined '
'with that name')
factory_name = app_def['factory']
factory = import_by_name(factory_name)
config_file = app_def['config']
app_factory = factory(config_file)
app = app_factory(next)
next = app
if not next:
raise ValueError, 'no apps defined in pipeline'
return next
def import_by_name(name):
if not "." in name:
raise ValueError("unloadable name: " + `name`)
components = name.split('.')
start = components[0]
g = globals()
package = __import__(start, g, g)
modulenames = [start]
for component in components[1:]:
modulenames.append(component)
try:
package = getattr(package, component)
except AttributeError:
n = '.'.join(modulenames)
package = __import__(n, g, g, component)
return package
We configure a pipeline based on a config file, which
creates and chains two "sample" WSGI applications together.
To do this, we use a ConfigParser-format config file named
'myapplication.conf' that looks like this::
[application:sample1]
config = sample1.conf
factory = wsgiconfig.tests.sample_components.factory1
[application:sample2]
config = sample2.conf
factory = wsgiconfig.tests.sample_components.factory2
[pipeline]
apps = sample1 sample2
The configurator exposes a function that accepts a single argument,
"configure".
>>> from wsgiconfig.configurator import configure
>>> appchain = configure('myapplication.conf')
The "sample_components" module referred to in the
'myapplication.conf' file application definitions might look like
this::
class sample1:
""" middleware """
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['sample1'] = True
return self.app(environ, start_response)
class sample2:
""" end-point app """
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['sample2'] = True
return ['return value 2']
def factory1(filename):
# this app requires no configuration, but if it did, we would
# parse the file represented by filename and do some config
return sample1
def factory2(filename):
# this app requires no configuration, but if it did, we would
# parse the file represented by filename and do some config
return sample2
The appchain represents an automatically constructed pipeline of
WSGI components. Each application in the chain is constructed from
a factory.
>>> appchain.__class__.__name__ # sample1 (middleware)
'sample1'
>>> appchain.app.__class__.__name__ # sample2 (application)
'sample2'
Calling the "appchain" in this example results in the keys "sample1"
and "sample2" being available in the environment, and what is
returned is the result of the application, which is the list
['return value 2'].
Potential points of contention
- The WSGI configurator assumes that you are willing to write WSGI
component factories which accept a filename as a config file. This
factory returns *another* factory (typically a class) that accepts
"the next" application in the pipeline chain and returns a WSGI
application instance. This pattern is necessary to support
argument currying across a declaratively configured pipeline,
because the WSGI spec doesn't allow for it. This is more contract
than currently exists in the WSGI specification but it would be
trivial to change existing WSGI components to adapt to this
pattern. Or we could adopt a pattern/convention that removed one
of the factories, passing both the "next" application and the
config file into a single factory function. Whatever. In any
case, in order to do declarative pipeline configuration, some
convention will need to be adopted. The convention I'm advocating
above seems to already have been for the current crop of middleware
components (using a factory which accepts the application as the
first argument).
- Pipeline deployment configuration should be used only to configure
essential information about pipeline and individual pipeline
components. Where complex service data configuration is necessary,
the component which implements a service should provide its own
external configuration mechanism. For example, if an XSL service
is implemented as a WSGI component, and it needs configuration
knobs of some kind, these knobs should not live within the WSGI
pipeline deployment file. Instead, each component should have its
own configuration file. This is the purpose (undemonstrated above)
of allowing an [application] section to specify a config filename.
- Some people have seem to be arguing that there should be a single
configuration format across all WSGI applications and gateways to
configure everything about those components. I don't think this is
workable. I think the only thing that is workable is to recommend
to WSGI component authors that they make their components
configurable using some configuration file or other type of path
(URL, perhaps). The composition, storage, and format of all other
configuration data for the component should be chosen by the
author.
- Threads which discussed this earlier on the web-sig list included
the idea that a server or gateway should be able to "find" an
end-point application based on a lookup of source file/module +
attrname specified in the server's configuration. I'm suggesting
instead that the mapping between servers, gateways, and
applications be a pipeline and that the pipeline itself have a
configuration definition that may live outside of any particular
server, gateway, or application. The pipeline definition(s) would
wire up the servers, gateways, and applications itself. The
pipeline definition *could* be kept amongs the files representing a
particular server instance on the filesystem (and this might be the
default), but it wouldn't necessarily have to be. This might just
be semantics.
- There were a few mentions of being able to configure/create a WSGI
application at request time by passing name/value string pairs
"through the pipeline" that would ostensibly be used to create a
new application instance (thereby dynamically extending or
modifying the pipeline). I think it's fine if a particular
component does this, but I'm suggesting that a canonization of the
mechanism used to do this is not necessary and that it's useful to
have the ability to define static pipelines for deployment.
- If elements in the pipeline depend on "services" (ala
Paste-as-not-a-chain-of-middleware-components), it may be
advantageous to create a "service manager" instead of deploying
each service as middleware. The "service manager" idea is not a
part of the deployment spec. The service manager would itself
likely be implemented as a piece of middleware or perhaps just a
library.
On Wed, 2005-07-20 at 02:08 +0800, ChunWei Ho wrote:
> Hi, I have been looking at WSGI for only a few weeks, but had some
> ideas similar (I hope) to what is being discussed that I'll put down
> here. I'm new to this so I beg your indulgence if this is heading down
> the wrong track or wildly offtopic :)
>
> It seems to me that a major drawback of WSGI middleware that is
> preventing flexible configuration/chain paths is that the application
> to be run has to be determined at init time. It is much flexible if we
> were able to specify what application to run and configuration
> information at call time - the middleware would be able to approximate
> a service of sorts.
....
More information about the Web-SIG
mailing list