[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