[Web-SIG] Entry points and import maps (was Re: Scarecrow deployment config
Phillip J. Eby
pje at telecommunity.com
Sun Jul 24 18:49:03 CEST 2005
[cc:ed to distutils-sig because much of the below is about a new egg
feature; follow-ups about the web stuff should stay on web-sig]
At 04:04 AM 7/24/2005 -0500, Ian Bicking wrote:
>So maybe here's a deployment spec we can start with. It looks like:
>
> [feature1]
> someapplication.somemodule.some_function
>
> [feature2]
> someapplication.somemodule.some_function2
>
>You can't get dumber than that! There should also be a "no-feature"
>section; maybe one without a section identifier, or some special section
>identifier.
>
>It goes in the .egg-info directory. This way elsewhere you can say:
>
> application = SomeApplication[feature1]
I like this a lot, although for a different purpose than the format Chris
and I were talking about. I see this fitting into that format as maybe:
[feature1 from SomeApplication]
# configuration here
>And it's quite unambiguous. Note that there is *no* "configuration" in
>the egg-info file, because you can't put any configuration related to a
>deployment in an .egg-info directory, because it's not specific to any
>deployment. Obviously we still need a way to get configuration in
>there, but lets say that's a different matter.
Easily fixed via what I've been thinking of as the "deployment descriptor";
I would call your proposal here the "import map". Basically, an import map
describes a mapping from some sort of feature name to qualified names in
the code.
I have an extension that I would make, though. Instead of using sections
for features, I would use name/value pairs inside of sections named for the
kind of import map. E.g.:
[wsgi.app_factories]
feature1 = somemodule:somefunction
feature2 = another.module:SomeClass
...
[mime.parsers]
application/atom+xml = something:atom_parser
...
In other words, feature maps could be a generic mechanism offered by
setuptools, with a 'Distribution.load_entry_point(kind,name)' API to
retrieve the desired object. That way, we don't end up reinventing this
idea for dozens of frameworks or pluggable applications that just need a
way to find a few simple entry points into the code.
In addition to specifying the entry point, each entry in the import map
could optionally list the "extras" that are required if that entry point is
used.
It could also issue a 'require()' for the corresponding feature if it has
any additional requirements listed in the extras_require dictionary.
So, I'm thinking that this would be implemented with an entry_points.txt
file in .egg-info, but supplied in setup.py like this:
setup(
...
entry_points = {
"wsgi.app_factories": dict(
feature1 = "somemodule:somefunction",
feature2 = "another.module:SomeClass [extra1,extra2]",
),
"mime.parsers": {
"application/atom+xml": "something:atom_parser [feedparser]"
}
},
extras_require = dict(
feedparser = [...],
extra1 = [...],
extra2 = [...],
)
)
Anyway, this would make the most common use case for eggs-as-plugins very
easy: an application or framework would simply define entry points, and
plugin projects would declare the ones they offer in their setup script.
I think this is a fantastic idea and I'm about to leap into implementing
it. :)
>This puts complex middleware construction into the function that is
>referenced. This function might be, in turn, an import from a
>framework. Or it might be some complex setup specific to the
>application. Whatever.
>
>The API would look like:
>
> wsgiapp = wsgiref.get_egg_application('SomeApplication[feature1]')
>
>Which ultimately resolves to:
>
> wsgiapp = some_function()
>
>get_egg_application could also take a pkg_resources.Distribution object.
Yeah, I'm thinking that this could be implemented as something like:
import pkg_resources
def get_wsgi_app(project_name, app_name, *args, **kw):
dist = pkg_resources.require(project_name)[0]
return dist.load_entry_point('wsgi.app_factories',
app_name)(*args,**kw)
with all the heavy lifting happening in the pkg_resources.Distribution
class, along with maybe a new EntryPoint class (to handle parsing entry
point specifiers and to do the loading of them.
>Open issues? Yep, there's a bunch. This requires the rest of the
>configuration to be done quite lazily.
Not sure I follow you; the deployment descriptor could contain all the
configuration; see the Web-SIG post I made just previous to this one.
> But I can fit this into source
>control; it is about *all* I can fit into source control (I can't have
>any filenames, I can't have any installation-specific pipelines, I can't
>have any other apps), but it is also enough that the deployment-specific
>parts can avoid many complexities of pipelining and factories and all
>that -- presumably the factory functions handle that.
+1.
> I don't think
>this is useful without the other pieces (both in front of this
>configuration file and behind it) but maybe we can think about what
>those other pieces could look like. I'm particularly open to
>suggestions that some_function() take some arguments, but I don't know
>what arguments.
At this point, I think this "entry points" concept weighs in favor of
having the deployment descriptor configuration values be Python
expressions, meaning that a WSGI application factory would accept keyword
arguments that can be whatever you like in order to configure it.
However, after more thought, I think that the "next application" argument
should be a keyword argument too, like 'wsgi_next' or some such. This
would allow a factory to have required arguments in its signature, e.g.:
def some_factory(required_arg_x, required_arg_y, optional_arg="foo",
....):
...
The problem with my original idea to have the "next app" be a positional
argument is that it would prevent non-middleware applications from having
any required arguments.
Anyway, I think we're now very close to being able to define a useful
deployment descriptor format for establishing pipelines and setting
options, that leaves open the possibility to do some very sophisticated
things.
Hm. Interesting thought... we could have a function to read a deployment
descriptor (from a string, stream, or filename) and then return the WSGI
application object. You could then wrap this in a simple WSGI app that
does filesystem-based URL routing to serve up *.wsgi files from a
directory. This would let you bootstrap a deployment capability into
existing WSGI servers, without them having to add their own support for
it! Web servers and frameworks that have some kind of file extension
mapping mechanism could do this directly, of course. I can envision
putting *.wsgi files in my web directories and then configuring Apache to
run them using either mod_python or FastCGI or even as a CGI, just by
tweaking local .htaccess files. However, once you have Apache tweaked the
way you want, .wsgi files can be just dropped in and edited.
Of course, there are still some open design issues, like caching of .wsgi
files (e.g. should the file be checked for changes on each hit? I guess
that could be a setting under "WSGI options", and would only work if the
descriptor parser was given an actual filename to load from.)
More information about the Web-SIG
mailing list