Skip to content
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

Possible bug with wildcard version matching #425

Closed
calebzulawski opened this issue May 1, 2021 · 9 comments · Fixed by #563
Closed

Possible bug with wildcard version matching #425

calebzulawski opened this issue May 1, 2021 · 9 comments · Fixed by #563

Comments

@calebzulawski
Copy link

Hi all,

I'm implementing a PEP 440 parser in another language and have been using packaging to validate, and came across this possible corner case.

The specifier ==1.1.0.post0.* matches the version 1.1.post0 because the version is zero-padded (as expected).

The specifier ==1.1.post0.* does not match the version 1.1.0.post0, even though I'd expect it to. My understanding is that the version in the specifier should also get zero-padded and result in a match (for the same reason that ==1.1.post0 is zero-padded to match 1.1.0.post0).

@pradyunsg
Copy link
Member

Hi! Thanks for filing this issue.

I'm finding it a little difficult to understand what edge case you're describing. Could you provide a reproducer for this with the following structure?

(what you ran + what you got)

>>> req = Requirement("A <= 2.0")
>>> req.name
'something-unexpected'

(vs what you ran + what you expected)

>>> req = Requirement("A <= 2.0")
>>> req.name
'A'

@uranusjr
Copy link
Member

uranusjr commented May 1, 2021

I think it’s

>>> Version('1.0.post1') == Version('1.post1')  # These are considered equivalent.
True
>>> '1.0.post1' in SpecifierSet('==1.0.post1.*')  # As expected.
True
>>> '1.0.post1' in SpecifierSet('==1.post1.*')  # Why not?
False

Although I don’t quite get what exactly 1.0.post1.* is supposed to mean. What should the wildcard match (except an empty string)?

@pradyunsg
Copy link
Member

Based on a re-read of the relevant sections of the PEP, .* based matches are called prefix matches, and that should better explain why this behaviour is what it is. It's matching the prefix against the version. That said, this is all super weird.

AFAICT, the prefix matching stuff is only meant to match prefixes for public versions? Notably, there's the line:

It is invalid to have a prefix match containing a development or local release such as 1.0.dev1.* or 1.0+foo1.*.

That makes me think that .post.* is invalid as well, and the fact that they're allowed is an edge case no one has thought of yet? I feel like updating the standard's language to make this clearer is a good idea.

@uranusjr
Copy link
Member

uranusjr commented May 1, 2021

But postreleases are neither development nor local releases, so technically 1.0.post1.* is not violating the line. The PEP specifically does not provide examples combining the wildcard with versions containing a release segment, but has this line:

Prefix matching may be requested […]. If the specified version includes only a release segment, than trailing components (or the lack thereof) in the release segment are also ignored.

Where a “release segment” is the N(.N)* part. Since this is specifically called out, I’m inclined to think the PEP does intend to allow a prefix match to specify a version containing other segments, for example the postrelease segment (as raised by this issue).

So I would assume the usage raised by OP is valid, at least according to the intension of PEP 440. But as I previously mentioned, I cannot see how this syntax can be practically useful, so it would not be unreasonable to change the specification to explicitly disallow it either.

@calebzulawski
Copy link
Author

@uranusjr thanks, yes that is what I am describing. The .* could match an empty string, but it could also match .devN correct? So not useless, just not common either.

@calebzulawski
Copy link
Author

Looking back at the spec, this line references that:

If present, the development release segment is always the final segment in the public version, and the local version is ignored for comparison purposes, so using either in a prefix match wouldn't make any sense.

.devN is the final component, not .postN

@brettcannon brettcannon added the bug label May 4, 2021
@mayeut
Copy link
Member

mayeut commented Jun 12, 2022

Another (different situation) reproducer for wildcard matching issue on main:

>>> from packaging import __version__
>>> print(__version__)
21.4.dev0
>>> from packaging.specifiers import Specifier
>>> from packaging.version import Version
>>> spec = Specifier("==1.0.*")
>>> version1 = Version("1a1")
>>> version2 = Version("1.0a1")
>>> version1 == version2
True
>>> spec.contains(version1, prereleases=True) == spec.contains(version2, prereleases=True)
False

The 2 versions are semantically identical but prefix matching yields 2 different results.

EDIT: I have a feeling I should open another issue for this one. If maintainers feel so too, I'll delete the comment and open another issue.

@brettcannon
Copy link
Member

brettcannon commented Jun 15, 2022

@mayeut up to you if you want to open a separate issue. Without investigating where in the code the bug is your case could be the same or could be entirely different.

@brettcannon
Copy link
Member

#563 fixes this to prevent the wildcard and I'm up for merging it. Any objections?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants