-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ability to specify "default" extras_require #1139
Comments
This work with pip: from setuptools import setup
setup(name='foo', version='1.0',
install_requires='''
requests; extra == ""
''',
extras_require={
'aiohttp': 'aiohttp',
'tornado': 'tornado',
},
) But not when setuptools' easy_install tries to install the necessary requirements ( from setuptools import setup
setup(name='foo', version='1.0',
extras_require={
'aiohttp': 'aiohttp',
'tornado': 'tornado',
':extra == ""': 'requests',
},
) IMHO, both should be allowed. @jaraco: what do you think? |
PEP 508 makes no mention of such behavior, so implementing this would cause setuptools to fall out of compliance, IMHO. |
Perhaps environment should provide @agronholm PEP 508 specifies the "extra" marker but it doesn't seem to specify its value when it's not set. |
I've just been experimenting with this problem... This has broken for me since 36.2. Since then, if you use
However,
Seems to do the trick for me! Would you like me to submit a PR? |
I don't think this is right. A better alternative would be to always define the extra environment marker, setting it to |
Yeah - that would work too. I'm not sure if that aligns with pep 508, though.
|
Yeah... I don't know why it was specified as such. It makes some things really hard, like converting metadata from wheel to egg: https://github.com/pypa/setuptools/blob/master/setuptools/wheel.py#L105 |
Lovely! :-) I'm still new to the setuptools code, so don't know where the |
I think this is what I had: diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 6f1071fb..db241642 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -1456,7 +1456,7 @@ def evaluate_marker(text, extra=None):
"""
try:
marker = packaging.markers.Marker(text)
- return marker.evaluate()
+ return marker.evaluate({'extra': extra})
except packaging.markers.InvalidMarker as e:
raise SyntaxError(e)
@@ -2678,7 +2678,7 @@ class Distribution(object):
if invalid_marker(marker):
# XXX warn
reqs = []
- elif not evaluate_marker(marker):
+ elif not evaluate_marker(marker, extra=extra):
reqs = []
extra = safe_extra(extra) or None
dm.setdefault(extra, []).extend(parse_requirements(reqs)) |
Looks like that's not quite enough... I get this error when I run my test input (from the previous diff) against it.
|
That's normal, no? Your test is trying to install a requirement that cannot be met, change it to: setuptools/tests/test_egg_info.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git i/setuptools/tests/test_egg_info.py w/setuptools/tests/test_egg_info.py
index 66ca9164..b270ba25 100644
--- i/setuptools/tests/test_egg_info.py
+++ w/setuptools/tests/test_egg_info.py
@@ -373,6 +373,20 @@ class TestEggInfo(object):
[empty]
''',
+
+ '''
+ install_requires_with_extra_marker
+
+ install_requires=["pytz; extra != 'foo'"],
+
+ [options]
+ install_requires =
+ pytz; extra != 'foo'
+
+ [:extra != "foo"]
+ pytz
+ ''',
+
# Format arguments.
invalid_marker=invalid_marker,
mismatch_marker=mismatch_marker, |
Sorry - I was being dense there. I'm clearly trying to do too much in too little time and making careless mistakes as a result. Is it worth packaging this fix up as a PR? |
No, this does not work... Because of the way the dependency map is built when using a dist-info distribution (wheel install), |
@benoit-pierre Still finding my way around this code... I think you're talking about invoking the
And the metedata.json also has conditional content:
That all looks plausible to me... What have I missed? |
Bump @benoit-pierre! Would love to see this pushed forward in some way if you have time. |
Would using the empty-string extra as a default be a reasonable approach here? from setuptools import setup
setup(name='foo', version='1.0',
extras_require={
'aiohttp': 'aiohttp',
'tornado': 'tornado',
'': 'requests',
},
) I was able to get this to work via diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 3ae2c5cd..2226b8ae 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -2628,6 +2628,8 @@ class Distribution:
raise UnknownExtra(
"%s has no such extra feature %r" % (self, ext)
)
+ if not extras:
+ deps.extend(dm.get('', ()))
return deps
def _get_metadata(self, name): Presumably the empty-string-extra is not used elsewhere, and running |
|
No, not a single package will break. Just to introduce a new syntax: you can define if you install any extra, it takes the This makes the default less of a special case, in fact it makes everything more consistent. |
…al dependencies Summary: The main changes are to `requirements.txt` and `setup.py`. After much discussion, we prefer that `pip install kats` installs everything, but that power users can have a way to opt out of some of dependencies required in only some parts of Kats (and then manually opt into them as desired). `pip` doesn't directly support this use case (see [setuptools issue](pypa/setuptools#1139), [packaging-problems issue](pypa/packaging-problems#214)), so we had to roll our own solution: ``` MINIMAL=1 pip install kats ``` Optional depedencies moved to `test_requirements.txt`. Two imports (`LunarCalendar`, `convertdate`) were not used anywhere anymore in Kats and removed. The rest of the changes wrap these now-optional imports in `try/except` blocks. In two cases, the changes are made to `__init__.py` files and log a warning that that the affected functionality is not available at all if the required depedencies aren't there. Otherwise, the modules can be imported but some methods cannot work without the optional dependencies (for example, some plotting methods require `plotly`). Reviewed By: rohanfb Differential Revision: D30172812 fbshipit-source-id: 2c7d8072e72cbdd2ac9960fe953cabe15db63cc9
Allow me to add another +1 to the already long list of +1 posts. I would like to use this in ImageIO (if anyone happens to know it), to default to a backend to install, if no backends are selected explicitly. Currently we always install pillow, but that doesn't work on all platforms. @dholth What is currently blocking this issue from progressing into a PR? A agreed-upon solution, or the manpower to implement it? I like the idea of a |
+1 I have a project which is capable of using either of two packages and selects between them at runtime. |
As the developer of "C", I just bumped into a scenario that requires dependencies that can be turned off. Scenario:
It looks like C cannot dependy on both A and a newer version of B. But hold on, B isn't really necessary for A! So I suggest to the developers of A to make B an optional dependency. This is turned down, because users of A would then by default miss the functionality of B. A solution would be an optional dependency B that in C can be turned off. |
Here is my hack to quickly implement such feature within the from setuptools import setup
import sys
# Hack to implement a default extra require with `lxml` and `bs4` packages that can be replaced with `[minidom]`
is_minidom_extra_enabled = any('minidom' in a for a in sys.argv)
def get_requirements():
requirements = [ 'requests',
'aiohttp',
'aioresponses',
'cached_property', ]
if not is_minidom_extra_enabled:
requirements.extend(['beautifulsoup4', 'lxml'])
return requirements
setup(
...
install_requires = get_requirements(),
extras_require = {
'minidom': ['dom_query'],
},
) EDIT: Actually, this does not seem to work :/ |
Any progress? |
I know I'll be shot down for this but please no. We do not need to make dependency trees more ambiguous. This concept of recommended dependencies has already plagued |
My use case is to have a default implementation, but offer alternatives backends. think requests vs httpx. |
... so your users get a less consistent experience, any asymmetries between requests and httpx could cause a |
Sounds like you’re assuming a poor implementation of what @luckydonald described, which, to be fair, it’s easy to get this wrong, but it doesn’t have to be poorly implemented: make configuration of a non-default backing implementation explicit, rather than relying on detecting what happens to be installed(*) in a “try/import…/except ImportError” cascade, and ensure the fascade library provides the same functional behavior regardless of which backing implementation is in use. (*) Consumers aren’t in control of what transitive dependencies they pick up after all. So they could end up with requests as a transitive dep via some other direct dependency, but still want to use httpx as the backing implementation for this example library. |
As this topic afects the semantics of the whole Python packaging, I suppose the best place to discuss it is in https://discuss.python.org/c/packaging/14. It is likely it will require someone to put forward a PEP and a proof-of-concept in terms of installer and build backend. |
Libraries that implement a client to an HTTP backend often provide multiple implementations such as for
tornado
,aiohttp
orrequests
. Each implementation comes with its own dependencies. User usually needs only one implementation.With
extras_require
developer of the library can specifyaiohttp
andtorndao
as optional and installrequests
for everyone viainstall_requires
.However, users of
aiohttp
andtornado
do not needrequests
, hence the request to be able to specify a "default" extra that should be picked when no other extras are specified.So that users calling
pip install mylib
orpip install mylib[requests]
will getrequests
installed, but users callingpip install mylib[aiohttp]
won't.The text was updated successfully, but these errors were encountered: