[Web-SIG] Entry points and import maps (was Re: Scarecrow deployment config
Phillip J. Eby
pje at telecommunity.com
Sun Jul 24 22:42:35 CEST 2005
At 02:12 PM 7/24/2005 -0500, Ian Bicking wrote:
>This kind of addresses the issue where the module structure of a package
>becomes an often unintentional part of its external interface. It feels a
>little crude in that respect... but maybe not. Is it worse to do:
>
> from package.module import name
>
>or:
>
> name = require('Package').load_entry_point('service_type', 'name')
>
>OK, well clearly the second is worse ;) But if that turned into a single
>function call:
>
> name = load_service('Package', 'service_type', 'name')
>
>It's not that bad. Maybe even:
>
> name = services['Package:service_type:name']
The actual API I have implemented in my CVS working copy is:
the_object = load_entry_point('Project', 'group', 'name')
which seems pretty clean to me. You can also use
dist.load_entry_point('group','name') if you already have a distribution
object for some reason. (For example, if you use an activation listener to
get callbacks when distributions are activated on sys.path.)
To introspect an entry point or check for its existence, you can use:
entry_point = get_entry_info('Project', 'group', 'name')
which returns either None or an EntryPoint object with various
attributes. To list the entry points of a group, or to list the groups,
you can use:
# dictionary of group names to entry map for each kind
group_names = get_entry_map('Project')
# dictionary of entry names to corresponding EntryPoint object
entry_names = get_entry_map('Project', 'group')
These are useful for dynamic entry points.
>Though service_type feels extraneous to me. I see the benefit of being
>explicit about what the factory provides, but I don't see the benefit of
>separating namespaces; the name should be unambiguous.
You're making the assumption that the package author defines the entry
point names, but that's not the case for application plugins; the
application will define entry point names and group names for the
application's use, and some applications will need multiple groups. Groups
might be keyed statically (i.e. a known set of entry point names) or
dynamically (the keys are used to put things in a table, e.g. a file
extension handler table).
>>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.
>
>I figured each entry point would just map to a feature, so the
>extra_require dictionary would already have entries.
The problem with that is that asking for a feature that's not in
extras_require is an InvalidOption error, so this would force you to define
entries in extras_require even if you have no extras involved. It would
also make for redundancies when entry points share an extra. I also don't
expect extras to be used as frequently as entry points.
>>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 = [...],
>> )
>> )
>
>I think I'd rather just put the canonical version in .egg-info instead of
>as an argument to setup(); this is one place where using Python
>expressions isn't a shining example of clarity. But I guess this is fine
>too; for clarity I'll probably start writing my setup.py files with
>variable assignments, then a setup() call that just refers to those variables.
The actual syntax I'm going to end up with is:
entry_points = {
"wsgi.app_factories": [
"feature1 = somemodule:somefunction",
"feature2 = another.module:SomeClass [extra1,extra2]",
]
}
Which is still not great, but it's a bit simpler. If you only have one
entry point, you can use:
entry_points = {
"wsgi.app_factories": "feature = somemodule:somefunction",
}
Or you can use a long string for each group:
entry_points = {
"wsgi.app_factories": """
# define features for blah blah
feature1 = somemodule:somefunction
feature2 = another.module:SomeClass [extra1,extra2]
"""
}
Or even list everything in one giant string:
entry_points = """
[wsgi.app_factories]
# define features for blah blah
feature1 = somemodule:somefunction
feature2 = another.module:SomeClass [extra1,extra2]
"""
This last format is more readable than the others, I think, but there are
likely to be setup scripts that will be generating some of this
dynamically, and I'd rather not force them to use strings when lists or
dictionaries would be more convenient for their use cases.
Anyway, I hope to check in a working implementation with tests later
today. Currently, the EntryPoint class works, but setuptools doesn't
generate the entry_points.txt file yet, and I don't have any tests yet for
the entry_points.txt parser or the API functions, although they're already
implemented.
More information about the Web-SIG
mailing list