5

Is it possible to have conditional entry_points defined in setup.py? I noticed that it is possible to tag an entry point using extras, but that entry point will be available even if the package is installed without that extra feature.

setup(name='my.package',
      ...
      extras_require={
          'special': [
              'dependency1',
              'dependency2',
          ],
      },
      ...
      entry_points="""
      [custom_entrypoint]
      handlername = my.package.special:feature [special]
      """,
  )

It appears as that custom_entrypoint is available even if the package is installed without that special feature (pip install my.package[special]). Is there a simple way of getting something like this working?

Torkel
  • 138
  • 10

2 Answers2

2

In your "plugin loader" (whatever happens to find the entry point, either by name or by enumerating the full set of available entry points for a given namespace) you need to do something like the following:

import pkg_resources

# Get a reference to an EntryPoint, somehow.
plug = pkg_resources.get_entry_info('pip', 'console_scripts', 'pip')

# This is sub-optimal because it raises on the first failure.
# Can't capture a full list of failed dependencies.
# plug.require()

# Instead, we find the individual dependencies.

failed = []

for extra in sorted(plug.extras):
    if extra not in plug.dist._dep_map:
        continue  # Not actually a valid extras_require dependency?

    for requirement in i.dist._dep_map[extra]:
        try:
            pkg_resources.require(str(requirement))
        except pkg_resources.DistributionNotFound:
            failed.append((plug.name, extra, str(requirement)))

And there we go; for a given plugin you'll get a list of failed dependencies (or not failed in the event of success) listing the entry_point plugin name, [foo] extra tag, and specific unsatisfied package requirement.

An example of this in action comes from the web.command package's web versions --namespace sub-command. Note how the waitress extras_require is satisfied, where the gevent one is explicitly specifying the gevent package is missing:

Sample usage.

Unfortunately I don't actually have a multiple-dependency entry_point example handy to show, and it's important to note that the package listed as "missing" might not always match up with the name of the extras_require.

amcgregor
  • 1,228
  • 12
  • 29
1

The entry points are written to package.dist-info/entry_points.txt. I was suggesting to see what packages are installed on the system in setup.py, but it probably wouldn't help here because the dist-info might be processed before the other packages are installed by pip; also later on, even if you installed the other packages, these entry points wouldn't magically show up unless you run setup.py for my.package with correct arguments.

I suggest you refactor so that there'd be one package named my.package and another installable package called my.package.special; the latter would have the my.package, dependency1 and dependency2 as dependencies, and the entry point. Now if you wanted to install my.package it would do so without the specials; pip install my.package.special to get the special feature on top of that.

  • I ended up with something similar. Instead of moving the functionality to a new package I kept it in the same. I didnt want to move the functionalities out of several packages into new special packages, because I use it as an `experimental` feature toggle. Instead I created a special package called `app.experimental` and included that as an extras_require within the other packages. When doing the iter_entry_points loops I checked that the entrypoint had fulfilled their requirements if not it was ignored (`entrypoint.require()` will raise `pkg_resources.DistributionNotFound`). – Torkel Mar 15 '16 at 10:15
  • 1
    @Torkel then please do post your solution as an alternative answer and [accept it](http://stackoverflow.com/help/self-answer) :D – Antti Haapala -- Слава Україні Mar 15 '16 at 10:18
  • 2
    Creating whole Python packages to suit the "meta dependency" need is silly, when that's exactly what `extras_require` is for. – amcgregor May 04 '16 at 14:36