-
Notifications
You must be signed in to change notification settings - Fork 252
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 __hash__
/__eq__
to requirements
#499
Conversation
01038fa
to
3005d18
Compare
Thank you very much @brettcannon for the review, I have submitted some commits addressing the proposed changes. Please note that in order to circumvent the Alternatively we could still make use of |
packaging/markers.py
Outdated
if not isinstance(other, Marker): | ||
return NotImplemented | ||
|
||
return _flatten_marker(self._markers) == _flatten_marker(other._markers) |
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.
An alternative to flattening _markers
here would be doing it directly on the __init__
method (that could potentially also simplify the _format_marker
function)
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.
As in pre-compute the string? Is the string used enough to warrant the forced cost of doing that?
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.
Not necessarily the string. The key point here seems to be flattening parenthesized groups with a single element (or the data structure equivalent to the parsing of those).
The "flattening" seems to be one of the central keys of the string conversion too.
We could do this flattening in the __init__
function without pre-computing the string, and that would facilitate both __eq__
and __str__
.
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.
My vote is to rely on the string representation for simplicity, else we are duplicating algorithms for walking the markers. If it turns out performance is a problem we can cache it, but we can wait on that until that actually becomes a problem.
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.
Yeah, I think that definitely is the simplest approach. I have updated the implementation accordingly.
(To be honest my first impulse was to do a string comparison, but I refrained from adopting that by overthinking about a previous comment, in a different context, about the cost of the string conversion).
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 not sure what anyone thinks about accepting this overall, but if this were to get accepted I think the hash tests should be a bit more clear as to what they are testing for.
packaging/markers.py
Outdated
if not isinstance(other, Marker): | ||
return NotImplemented | ||
|
||
return _flatten_marker(self._markers) == _flatten_marker(other._markers) |
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.
As in pre-compute the string? Is the string used enough to warrant the forced cost of doing that?
Hi @brettcannon, thank you very much for the review. Regarding the hashing test, I was really unfortunate with the naming since it does not reflect what I had in mind. Something important to say is that, the idea of the test was not to check if the objects are correctly being hashed... To be sincere I think all of that is just an implementation detail. The real objective of the tests were to check if the objects can be used as elements of sets and if comparisons would work fine in that context. Knowing that the output of the Please let me know if clarifying the names of the tests and splitting them up acordingly would work for you and I will do my best to come up with better names. Otherwise I also don't have a problem and just simplifying the tests to use |
I actually think the opposite of you. 😄 I view the ability to put an object in a container a side-effect of implementing Now if you want to consolidate testing both methods into testing if the objects get put into a container as appropriate, then that's fine, just make sure to name and document the tests appropriately. I don't have strong opinions either way, but I think it should be one or the other approaches. |
Thank you very much for the comment @brettcannon. I think it is important to test 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 think we should keep this simple and treat the string representation as the simple encoding for comparison.
Also some test simplification to save on some time and electricity.
packaging/markers.py
Outdated
assert isinstance(marker, (list, tuple)) | ||
|
||
if isinstance(marker, tuple): | ||
return marker | ||
|
||
if len(marker) == 1: | ||
return _flatten_marker(marker[0]) | ||
|
||
return [_flatten_marker(e) if isinstance(e, list) else e for e in marker] |
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.
assert isinstance(marker, (list, tuple)) | |
if isinstance(marker, tuple): | |
return marker | |
if len(marker) == 1: | |
return _flatten_marker(marker[0]) | |
return [_flatten_marker(e) if isinstance(e, list) else e for e in marker] | |
assert isinstance(marker, (list, tuple)) | |
if isinstance(marker, tuple): | |
return marker | |
elif len(marker) == 1: | |
return _flatten_marker(marker[0]) | |
else: | |
return [_flatten_marker(e) if isinstance(e, list) else e for e in marker] |
packaging/markers.py
Outdated
if not isinstance(other, Marker): | ||
return NotImplemented | ||
|
||
return _flatten_marker(self._markers) == _flatten_marker(other._markers) |
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.
My vote is to rely on the string representation for simplicity, else we are duplicating algorithms for walking the markers. If it turns out performance is a problem we can cache it, but we can wait on that until that actually becomes a problem.
tests/test_markers.py
Outdated
# Markers should not be comparable with other kinds of objects. | ||
assert marker1 != example1 |
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 can be its own test instead of having to do it repeatedly as part of a parameterized test.
tests/test_requirements.py
Outdated
# Requirement objects should not be comparable with other kinds of objects. | ||
assert req1 != dep1 | ||
assert req2 != dep2 |
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 be a separate test that isn't repeated multiple time needlessly.
Co-authored-by: Brett Cannon <brett@python.org>
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
Co-authored-by: Brett Cannon <brett@python.org>
a942422
to
ed0c623
Compare
Thank you very much for the updated reviews. I have adopted the suggested changes and also rebased the PR. |
Thanks for much for sticking with this, @abravalheri ! |
Hello, this PR is a feature request that started as a conversation in #498 (comment). I also believe it might close #453.
The idea here is to be able to compare requirement objects as well as be able to compare sets containing those objects, e.g.:
The approach I used was to rely on the normalization that happens when the requirement object is converted to string to implement both
__eq__
and__hash__
.