packaging.python.org icon indicating copy to clipboard operation
packaging.python.org copied to clipboard

Section on entrypoint extras could use clarification

Open funkyfuture opened this issue 4 years ago • 3 comments

the extras field related part of the entrypoints data model description currently states:

Using extras for an entry point is no longer recommended. Consumers should support parsing them from existing distributions, but may then ignore them. New publishing tools need not support specifying extras. The functionality of handling extras was tied to setuptools’ model of managing ‘egg’ packages, but newer tools such as pip and virtualenv use a different model.

a text-related question would thus be: what is that 'different model'.

my practical question is: how should i implement modules that contain entrypoints that are supposed to be only loaded when a certain extra (dependency) has bee installed upon installation? afaict, pkg_resources.Distribution.extras contains all defined extras from the metadata, not just the installed ones. so for now i can only think of designing a module that contains entrypoints like this:

# the example assumes a console_script was defined w/o tying it to an extra as recommended
# the requests module was only installed when a certain extra (for dependencies) was selected

try:
    import requests 
except ImportError:
    def my_console_script():
        raise RuntimeError("Though I'm available, I can't help you due to a missing dependency.")
else:
    def my_console_script():
        pass  # functional code here

please note that i'm not a native tongue english speaker and might miss some subtleties.

funkyfuture avatar Jul 19 '20 12:07 funkyfuture

I'd do something like this:

try:
    import requests 
except ImportError:
    REQUESTS_AVAILABLE = False
else:
    REQUESTS_AVAILABLE = True


__requires__ = ('requests>=1.0', )


def my_console_script():
    if not REQUESTS_AVAILABLE:
        raise RuntimeError(
            'Using `my_console_script` requires the following '
            f'pre-requisites to be installed first: {", ".join(__requires__)}.',
        )

    # functional code here

N.B. This will explode early when loaded via setuptools/pkg_resources, or later (when installed from wheels by modern pip that uses imports for loading)

webknjaz avatar Jul 19 '20 17:07 webknjaz

Another way to do this that uses extra data from the installed distribution metadata (as opposed to having to specify the requirements again using __requires__) and that works even for non-console scripts would be to add setuptools to the package dependencies and use pkg_resources.require():

from pkg_resources import require
require("YOUR_PACKAGE_NAME[EXTRA_NAME]")

This will raise pkg_resources.DistributionNotFound or pkg_resources.VersionConflict if a package required by the extra isn't installed or if its version doesn't match the requirements, respectively. Of course it will only work if the package specifying the extra has been installed, otherwise it raises DistributionNotFound as well.

Also, I asked myself the same question as @funkyfuture when the I read the paragraph about the "different model" that deprecates entry-point-specific extras. Would be great if this could be explained a bit more in the docs, and even better if they also gave hints on alternative ways of achieving the same thing, such as @webknjaz's solution above or specifying a regular, non-entry-point-specific extra and then using something like my solution.

smheidrich avatar Aug 23 '21 13:08 smheidrich

thanks for your responses.

while @webknjaz proposal improves readability by flattening the code, i agree that duplicated dependency declarations are a maintenance burden and error prone.

i haven't tried out @smheidrich's proposal, but as far as i understand it, the module where this is implemented would have to be shielded in a try-except block as well.

however, i'm not really convinced by the general data model design. it seems like a regression to me that now all entrypoints are loaded and a plugin implementation would have to tell the loading code that dependencies are missing in a certain way. as opposed to the previous behaviour where an entrypoint would only be available when the required extras are installed.

funkyfuture avatar Sep 14 '21 11:09 funkyfuture