-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Allow PEP508 url dependencies in install_requires #5571
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @bstrdsmkr!
I'm assuming that you opened a PR for CI and feedback so, hopefully you don't mind that. :)
PS: I'm super nit-picky. :P
src/pip/_internal/models/index.py
Outdated
|
||
# This is a temporary hack used to block installs of PyPI packages which depend | ||
# on external urls only necessary until PyPI can block such packages themselves | ||
PyPI.link_source = 'files.pythonhosted.org' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mypy won't be super happy about this.
news/4187.feature
Outdated
@@ -0,0 +1,5 @@ | |||
Allow PEP508 dependencies in install_requires. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Allow PEP 508 URL requirements to be used as dependencies.
tests/functional/test_install.py
Outdated
@pytest.mark.network | ||
def test_install_from_pypi_with_ext_url_in_install_requires_is_blocked(script): | ||
res = script.pip('install', '-vvv', 'pep-508-url-deps', expect_error=True) | ||
assert "Packages installed from PyPI cannot " in res.stderr, str(res) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Instead of checking the entire message over multiple statements, I'd rather that you compose an expected
string and then just do assert expected in result.stderr
.
We should also check the exit code here.
src/pip/_internal/req/req_install.py
Outdated
) | ||
|
||
if req.url and comes_from.link.netloc == PyPI.link_source: | ||
# Explicitly blacklist pypi packages that depend on external urls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indentation seems weird.
Could you make both the comment and the statement the same indent, which is a multiple of 4?
c004d31
to
6d3baab
Compare
@pradyunsg thanks for the feedback! I've incorporated your changes. If you think the approach is solid, I'll remove the wip and mark is as closing #4187 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with the approach here.
src/pip/_internal/models/index.py
Outdated
@@ -2,14 +2,19 @@ | |||
|
|||
|
|||
class Index(object): | |||
def __init__(self, url): | |||
def __init__(self, url, link_source=''): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can drop the default on this.
Making it mandatory to pass doesn't affect anything and prevents from accidentally missing it out. :)
src/pip/_internal/models/index.py
Outdated
# This part of a temporary hack used to block installs of PyPI packages | ||
# which depend on external urls only necessary until PyPI can block | ||
# such packages themselves | ||
self.link_source = link_source |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call this something else? link_source
seems very ambiguous and is completely out of context in this file.
Possibly file_storage_domain
?
src/pip/_internal/req/req_install.py
Outdated
"dependencies" % req | ||
"Packages installed from PyPI cannot depend on packages " | ||
"which are not also hosted on PyPI. " | ||
"%s depends on %s " % (comes_from, req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message shows up as:
Packages installed from PyPI cannot depend on packages which are not also hosted on PyPI. pep-508-url-deps from https://files.pythonhosted.org/packages/bc/69/b088a665f2cf87cb1f260376dce6895bf4b00336736b2082ef5af5a8bd20/pep-508-url-deps-1.0.0.post0.tar.gz#sha256=0fdbbb60d734d738d1bd25eeddfb4bc89f1c3cc5406f59c32b7eb4445439f1b6 depends on sampleproject@ https://github.com/pypa/sampleproject/archive/master.zip
I don't think that's very friendly or clear. It's rather have something like:
PEP 508 URL requirements are forbidden when installing from PyPI.
pep-508-url-deps depends on sampleproject@https://github.com/pypa/sampleproject/archive/master.zip
news/4187.feature
Outdated
@@ -0,0 +1,5 @@ | |||
Allow PEP 508 URL requirements to be used as dependencies. | |||
|
|||
As a security measure, all packages installed from PyPI are specifically blacklisted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor preference to not say "blacklisted" here. I'd rather just say:
As a security measure, this is not supported when installing from PyPI for dependencies not hosted on PyPI. In the future, PyPI will block uploading packages with such external URL dependencies directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to point attention to the fact that this is a conscious choice to make it not work for a specific reason, rather than just unsupported which felt more to me like "may or may not work."
Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. My reservation here is that this says "blacklist" here (and in the comment) but we don't actually have a "list" of packages that we disallow based on -- it's on a characteristic of how the installation is being done.
So, a phrasing that doesn't use "blacklist" is something I'd prefer but I won't block this PR for this. :P
6d3baab
to
92ce20d
Compare
It just occurred to me that we would also want to block pip from doing this on test.pypi.org (the domain is test-file.pythonhosted.org). |
92ce20d
to
b5fdebe
Compare
@pradyunsg changes integrated |
src/pip/_internal/models/index.py
Outdated
PyPI = Index('https://pypi.org/') | ||
PyPI = Index( | ||
'https://pypi.org/', | ||
['files.pythonhosted.org', 'test-file.pythonhosted.org'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/functional/test_install.py
Outdated
|
||
@pytest.mark.network | ||
def test_install_from_pypi_with_ext_url_in_install_requires_is_blocked(script): | ||
res = script.pip('install', '-vvv', 'pep-508-url-deps', expect_error=True) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
b5fdebe
to
47b0f4d
Compare
@pradyunsg once more, with feeling! :) |
47b0f4d
to
868c743
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized I'm suggesting a lot of small changes. Thanks for constantly updating the PR. :)
tests/functional/test_install.py
Outdated
|
||
|
||
@pytest.mark.network | ||
def test_install_from_test_pypi_with_ext_url_dependency_is_blocked(script): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test can simply be parameterized.
src/pip/_internal/models/index.py
Outdated
PyPI = Index('https://pypi.org/') | ||
PyPI = Index( | ||
'https://pypi.org/', | ||
['files.pythonhosted.org', 'test-files.pythonhosted.org'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we instead have a TestPyPI = Index(...)
, and use that in the checks?
868c743
to
4ae6756
Compare
@pradyunsg no worries, might as well get it right the first time =) I've addressed your changes, but the tests are going to fail since we're also now testing against test.pypi.org, which doesn't have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've addressed your changes, but the tests are going to fail since we're also now testing against test.pypi.org, which doesn't have peppercorn which sampleproject depends on, which is wanted by pep-508-urls =)
LOL. Nice way for the test to fail.
I don't think we need to change anything that on pep-508-urls, since pip shouldn't be trying to download sampleproject in the first place.
src/pip/_internal/req/req_install.py
Outdated
@@ -169,11 +170,15 @@ def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None): | |||
req = Requirement(req) | |||
except InvalidRequirement: | |||
raise InstallationError("Invalid requirement: '%s'" % req) | |||
if req.url: | |||
|
|||
if req.url and comes_from.link.netloc == PyPI.file_storage_domain: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also check for TestPyPI though.
Suggestion (you can use a different way if you like):
domains_not_allowed = [PyPI..., TestPyPI...]
comes_from_domain = comes_from...
if req.url and comes_from_domain in domains_not_allowed:
...
tests/functional/test_install.py
Outdated
"pep-508-url-deps depends on sampleproject@ " | ||
"https://github.com/pypa/sampleproject/archive/master.zip" | ||
) | ||
assert error_message in res.stderr, str(res) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a good idea to check the error code before checking the message.
4ae6756
to
9fa6f4d
Compare
9fa6f4d
to
c71ac73
Compare
@pradyunsg ready for review again |
@pradyunsg any more feedback? =) |
Thanks @bstrdsmkr! LGTM. @pypa/pip-committers Does anyone else want to review this, or want additional changes here? |
I don't have time to do a full review, but I quickly skimmed the code and it looks OK to me. Thanks @bstrdsmkr for picking up this change for us! |
Pushing this down the road to the next release since I don't think this'd happen in time for 18.0. This doesn't seem to be a release blocker -- it's new functionality that can be merged post 18.0. |
@pradyunsg We've had a couple of approvals, plus my OK. Why not just merge this? Nothing's going to happen between now and when we do merge, so I don't see much value in being cautious here. I'm OK to merge this unless you have a specific objection. |
Apologies, I see from #5516 (comment) that the 18.0 release is this weekend, so that's a perfectly good reason for deferring! I'll merge this after the release. |
I'm not seeing the reason this was pushed. It's holding up things at work for me so I can fix things today if there's any way to get this in the next release |
I'd prefer to be a little cautious here and not merge this PR right now. I understand that this change might be holding up some improvement/cleanup work for you (and other users) and I'd personally like to see this change made too. I do feel being a little cautious here doesn't hurt that much. Further, while I do think this PR is ready to merge, I think these changes should be released with some changes to |
Sounds reasonable to me |
I can understand that, I just wish I had known that upfront and I wouldn't have put so much effort into trying to get it ready so fast. Either way, thanks for your help in getting it this far. Any estimate on when it will be released? Ballpark obviously, I just have to give a report on the delay |
With pip 18, we've now switched to a regular release cadence, which you can find the full details at https://pip.pypa.io/en/stable/development/#release-cadence, but the tl;dr is the next release will be October, so if everything is ready and merged before October, it'll go out then. |
Indeed. I agree that this could have been communicated earlier; apologies for that. Will take care of this in the future. :) |
I think this can go in now. The |
So dependencies with install_requires sub-dependencies in private Git repos are broken until October? MyGitPackage A -> Depends on -> MyGitPackage B Both A and B are from our private Git repo, But B cannot be installed because:
when using setup.py in Package A
And there is no way to override this check. Is there a pre-release version available? We will have several hundred packages that depend on other packages in the same repo. |
@AdamLeyshon somewhat ironically, it seems like |
That has been the case since pip 10, which is when this functionality was introduced. While the blocking seems arbitrary, the intent is to allow the user to not be able to install packages from PyPI that reach out to random locations on the internet. This PR makes it a much more specific, allowing the use of URL dependencies in a package's install_requires when installing from anywhere except PyPI, enabling use cases such as yours. None the less, yes, the next release is in October. This functionality would be released then, barring any blockers. |
It seems to me it would be trivial to create a PyPI compliant package that downloads and executes code from elsewhere, so the precautions being taken here are unnecessary. For example if setup.py or the package at runtime calls |
PyPI.file_storage_domain, | ||
TestPyPI.file_storage_domain, | ||
] | ||
if req.url and comes_from.link.netloc in domains_not_allowed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If comes_from.link is None
, this crashes. This can happen if a package is already installed, and it was installed from a local wheel. I tested this on Pip 18.1 released today, but can't figure out how to make a minimum example.
Exception:
Traceback (most recent call last):
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/cli/base_command.py", line 143, in main
status = self.run(options, args)
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/commands/install.py", line 318, in run
resolver.resolve(requirement_set)
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/resolve.py", line 102, in resolve
self._resolve_one(requirement_set, req)
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/resolve.py", line 318, in _resolve_one
add_req(subreq, extras_requested=available_requested)
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/resolve.py", line 275, in add_req
wheel_cache=self.wheel_cache,
File "<local>/venv/lib/python3.7/site-packages/pip/_internal/req/constructors.py", line 288, in install_req_from_req
if req.url and comes_from.link.netloc in domains_not_allowed:
AttributeError: 'NoneType' object has no attribute 'netloc'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wkschwartz : can you check with #5788?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line can simply be changed to
if req.url and comes_from.link is not None and comes_from.link.netloc in domains_not_allowed:
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This is intended to be the minimum obvious approach to allow users to make user of PEP508 urls as dependencies. Per the discussion in #4187, packages originating from PyPI (by virtue of being hosted at
files.pythonhosted.org
) are explicitly excluded from this feature in an effort to provide some security and avoid the (possibly) unexpected side effect ofpip install
grabbing packages from arbitrary 3rd party urls. Once PyPI is able to block such packages themselves, this patch will be obviated and should be removed.Adding a property to the PyPI Index instance feels dirty, especially since it is only ever used once, but I decided on adding it there instead of hard coding it in the conditional logic. My reasoning is that if/when PyPI changes their hosting url, it'll need to be updated and the point of instance creation seemed the most logical location for someone to look when that time comes around.
Fixes #4187