[Web-SIG] Entry points and import maps (was Re: Scarecrow deployment config

James Gardner james at pythonweb.org
Mon Jul 25 23:54:08 CEST 2005


Hi All,

I'm a bit late coming to all this and didn't really see the benefits of 
the new format over what we already do so I set out to contrast new and 
old to demonstrate why it wasn't *that* useful. I've since changed my 
mind and think it is great but here is the contrasting I did anyway. I'd 
be pleased to hear all the glaring errors :-)

Here is a new example: we want to have an application that returns a 
GZip encoded "hello world" string after it has been made lowercase by 
case changer middleware taking a parameter newCase. The GZip middleware 
is an optional feature of the modules in wsgiFilters.egg and the 
CaseChanger middleware and HelloWorld application are in the helloworld.egg.

The classes look like this:

class HelloWorld:
    def __call__(self, environ, start_response):
        start_response('200 OK', [('Content-type','text/plain')])
        return ['Hello World']

class CaseChanger:
    def __init__(self, app, newCase):
        self.app = app
        self.newCase = newCase

    def __call__(self, environ, start_response):
        for chunk in self.app(environ, start_response):
            if self.newCase == 'lower':
                yield chunk.lower()
            else: 
                yield chunk

Class GZip:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        # Do clever things with headers here (omitted)
	for chunk in self.app(environ, start_response):
            yeild gzip(chunk)

The way we would write our application at the moment is as follows:

from pkg_resources import require

require('helloworld >= 0.2')
from helloworld import Helloworld

require('wsgiFilters[GZip] == 1.4.3')
from wsgiFilters import GZip

pipeline =  GZip(
                app = CaseChanger(
                    app = HelloWorld(),
                    newCase = 'lowercase',
                )
            )

With pipeline itself somehow being executed as a WSGI application.

The new way is like this (correct me if I'm wrong)

The modules have egg_info files like this respectively defining the 
"entry points":

wsgiFilters.egg:

[wsgi.middleware]
gzipper = GZip:GZip

helloworld.egg:

[wsgi.middleware]
cs = helloworld:CaseChanger

[wsgi.app]
myApp = helloworld:HelloWorld


We would then write an "import map" (below) based on the "deployment 
descriptors" in the .eggs used to describe the "entry points" into the 
eggs. The order the "pipeline" would be built is the same as in the 
Python example eg middleware first then application.

[gzipper from wsgiFilters[GZip] == 1.4.3]
[cs from helloworld  >= 0.2 ]
newCase = 'lower'
[myApp from helloworld >= 0.2]


It is loaded using an as yet unwritten modules which uses a factory 
returning a middleware pipeline equivalent to what would be produced in 
the Python example (is this very last bit correct?)

Doing things this new way has the following advantages:
* We have specified explicitly in the setup.py of the eggs that the 
middleware and applications we are importing are actually middleware and 
an application
* It is simpler for a non-technical user.
* There are lots of other applications for the ideas being discussed

It has the following disadvantages:
* We are limited as to what we can use as variable names. Existing 
middleware would need customising to only accept basic parameters.
* We require all WSGI coders to use the egg format.
* Users can't customise the middleware in the configuration file (eg by 
creating a derived class etc and you lose flexibility).
* If we use a Python file we can directly import and manipulate the 
pipeline (I guess you can do this anyway once your factory has returned 
the pipeline)

Both methods are the same in that
* We have specified the order of the pipeline and the middleware and 
applications involved
* Auto-downloading and installation of middleware and applications based 
on version requirements is possible (thanks to PJE's eggs)
* We have specified which versions of modules we require.
* Both could call a script such as wsgi_CGI.py wsgi_mod_python.py etc to 
execute the WSGI pipeline so both method's files could be distributed as 
a single file and would auto download their own dependencies.

Other ideas:

Is it really necessary to be able to give an entry point a name? If not 
because we know what we want to import anyway, we can combine the 
deployment descriptor into the import map:

[GZip:GZip from wsgiFilters[GZip] == 1.4.3]


We can then simplify the deployment descriptor like this:

[wsgi.middleware]
GZip:GZip

And then remove the colons and give a fully qualified Python-style path:

[GZip.GZip from wsgiFilters[GZip] == 1.4.3]

and

[wsgi.middleware]
GZip.GZip

Is this not better? Why do you need to assign names to entry points?

Although writing a middleware chain is dead easy for a Python 
programmer, it isn't for the end user and if you compare the end user 
files from this example I know which one I'd rather explain to someone. 
So although this deployment format seemed at first like overkill, I'm 
now very much in favour. I was personally considering YAML for doing my 
own configuration using a factory but frankly the new format is much 
cleaner and you don't need all the power of YAML anyway! Count me in!

James



More information about the Web-SIG mailing list