-
-
Notifications
You must be signed in to change notification settings - Fork 614
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
Fix allow unsafe false pinning bug #508
Fix allow unsafe false pinning bug #508
Conversation
ad7ff32
to
4dfe7f4
Compare
157f3b2
to
b0b60db
Compare
cc @jdufresne |
Apologies if I have introduced a regression.
Do you have additional details on this? I do not use the Here is the command I use:
|
CHANGELOG.md
Outdated
|
||
Bug Fixes: | ||
- Fixed bug where unsafe packages would get pinned in generated requirements files | ||
when `--allow-unsafe` was not set. ([#508](https://github.com/jazzband/pip-tools/pull/508)). |
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 you mean WAS set, right?
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.
Unsafe packages were being pinned in requirements files even when --allow-unsafe
wasn't set instead of being comments
If I run with
Are you saying this behavior is incorrect? setuptools should not have a pinned version? If that is the case, what is the expected output? |
I believe the initial implementation was correct in removing the unsafe packages at runtime. This PR introduces a similar kind of logic in the Output writer. I am on @jdufresne side here, and I am not sure there is a regression. Can you write a test that demonstrates the failure? |
tests/test_writer.py
Outdated
from piptools.writer import OutputWriter | ||
|
||
|
||
@fixture | ||
class Writer(object): |
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 don't understand this refactoring of correct code.
For what it's worth, pytest now doesn't discriminate between a yield and an ordinary fixture. @fixture is only needed.
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.
By yielding a class that I can invoke later I am allowing for keyword arguments to be passed in so that I can reuse this fixture while setting allow_unsafe
.
I'll update the fixture annotation
@jdufresne no worries! I wanted to tag you in case I was missing a use case that isn't tested. The command I'm running is
But if I run
@davidovich not trying to imply there are "sides" here. Just wanted to fix a bug ;) happy to fork and fix in that. |
Thanks for following up. Can you provide the |
|
@@ -124,9 +132,12 @@ def write(self, results, reverse_dependencies, primary_packages, markers, hashes | |||
f.write(unstyle(line).encode('utf-8')) | |||
f.write(os.linesep.encode('utf-8')) | |||
|
|||
def _format_requirement(self, ireq, reverse_dependencies, primary_packages, marker=None, hashes=None): | |||
line = format_requirement(ireq, marker=marker) | |||
def _format_requirement(self, ireq, reverse_dependencies, primary_packages, **kwargs): |
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.
Keep the named arguments here.
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 you provide a reason why?
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.
Because it makes the contract clear and not hidden in the function body.
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.
For the sake of argument it makes it harder for discoverability but it makes usage much clearer because you aren't allowing for usages to rely on position.
In terms of discoverability of arguments there should be documentation on the function with these values.
I'm having trouble understanding your use case. Can you expand on it? Why do you put What is the point to putting |
@jdufresne I do a first pass with pip pinned and then a second pass including the generated file from the first pass. Even if this seems odd the functionality is broken. If someone has a unsafe library in their requirements and |
@jdufresne I think the idea is that if an end-user puts pip into requirements.in, that we strip it unless Basically, this PR just makes piptools work as advertised, based on the arguments. |
The spirit of --allow-unsafe is to control if unsafe sub-dependencies are output to the requirements.txt, not to filter a user input which is the user desired state of wanted dependencies. requirements.in state what the user wants. The --allow-unsafe flag is to remove unwanted side effects of sub-dependencies which are Even unsafe here is a bit hard to define. But if the said unsafe requirements are in the input which YOU control in the requirements.in, pip-tools cannot decide to remove them for you. I used this setup to convince myself there was no bug here. The following creates a fake package with only setuptools as sub-dependency.
The result:
With --allow-unsafe:
|
@davidovich this is wrong. If you look at the PR to add the flag (#377) the description says,
This is exactly what we are doing and what this pull request fixes... |
marker=markers.get(ireq.req.name), | ||
hashes=hashes if self.allow_unsafe else None | ||
) | ||
if not self.allow_unsafe: |
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.
reverse the if logic here, use the positive first.
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's safer to fall back to the unmodified line at the catch all.
w, '_format_requirement', return_value='{}'.format(unsafe_package) | ||
) | ||
comment = mocker.patch( | ||
'piptools.writer.comment', side_effect=['foobar', '# {}'.format(unsafe_package)]) |
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.
Why so much mocking ? I think it is best to use the real implementation as much as possible in order to test the implementation and not the mocks.
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.
Unit tests should test functionality in isolation of other modules assuming that those modules are tested independently.
'piptools.writer.comment', side_effect=['foobar', '# {}'.format(unsafe_package)]) | ||
|
||
expected_results = ['header', 'flags', '', 'foobar', '# {}'.format(unsafe_package)] | ||
result = w._iter_lines([ireq], False, [], {unsafe_package: 'marker'}, 'hashes') |
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.
Is the 'marker' here a real package? deduced from the USAFE_PACKAGES dependencies? or is it a test marker, something that you make up for your test ?
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.
'marker'
is an arbitrary value used for testing purposes.
@@ -124,9 +132,12 @@ def write(self, results, reverse_dependencies, primary_packages, markers, hashes | |||
f.write(unstyle(line).encode('utf-8')) | |||
f.write(os.linesep.encode('utf-8')) | |||
|
|||
def _format_requirement(self, ireq, reverse_dependencies, primary_packages, marker=None, hashes=None): | |||
line = format_requirement(ireq, marker=marker) | |||
def _format_requirement(self, ireq, reverse_dependencies, primary_packages, **kwargs): |
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.
Because it makes the contract clear and not hidden in the function body.
Given that all the criteria in the description are satisfied, none of the feedback is critical, and most importantly this fixes a bug with intended functionality I propose that we merge this. |
@dschaller You are the only one saying the feedback is not critical. I feel the overall gist of this is that you want the change in, without regards to style or existing code. I have made comments and tried to understand the nature of what this PR trying to fix something that I am still not convinced is broken, but they seem to be ignored. The response I got from the example code was that I didn't read a previous PR. I understand what that PR brought to the table, but I believe we are now in a better position than at that time. The problem is that if you put something in the input, it is not for pip-tools to decide to remove that wanted state. If you want to remove pip from the output, don't put it in the input as a wanted requirement in the first place. I sill fail to understand your use case. I can perfectly see that I would like to put For example, with the same setup as above, imaging I would like to have
|
@davidovich happy to chat more about the changes. My apologies if that is the way my comment came off as it was not my intent. By critical I meant the inline comments left by you were mostly style ones and they seem like a personal preference. Please help me understand how I can I better help you understand how this is broken.
Pip-tools, up until the last release with the change in it, would remove unsafe dependencies from the generated requirements files even if they were part of the original requirements unless the As far as your example goes it seems counter-intuitive that you would want pip-tools, a tool whose entire purpose is to pin all underlying dependencies, to not pin the underlying dependencies of unsafe packages when they are allowed. Can you help me understand why you wouldn't want this? |
I went through the discussion on whether this is a bug or not, and I think I can summarize this: @dschaller, you're pointing out that prior to 1.9.0, @davidovich, you're saying that in retrospective, maybe thats a good change, because it allows us to say "Yes, I want IMHO, I can see use cases where both behaviour would be good. And I think I have an idea to keep the best of both worlds: having a way allow specific unsafe packages, in the same way we do with Whether we should do that in one or many PR is debatable (oh no... what have I done ;) ), but I feel like this is the way to go. What do you say about this? On another subject, @dschaller, I haven't fully analysed the code change (sorry, I'll try to!), but the feeling I got is that we went back at filtering the output rather than filtering on the input. Am I right? If so, do you think its possible to change that? I think its a better way to handle the case, and I also got a feeling that the unsafe dependencies of unsafe requirements directly in the As always, thanks everyone for your time! |
@davidovich @vphilippon @jdufresne opened #517 to hopefully fix this bug while preserving the additions in #441. Please take a look when you get a chance |
WIth 4e2f928 merged, should this now be closed? |
Fix a bug introduced in 441 that pinned unsafe dependencies in generated requirements files when
--allow-unsafe
wasFalse
.Contributor checklist