packaging.python.org
packaging.python.org copied to clipboard
Section on entrypoint extras could use clarification
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.
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)
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.
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.