python-poetry / poetry

Python packaging and dependency management made easy
https://python-poetry.org
MIT License
31.51k stars 2.26k forks source link

Tilde requirements vs PEP 440 compatible release #4044

Closed ryanhiebert closed 1 year ago

ryanhiebert commented 3 years ago

https://python-poetry.org/docs/versions/#tilde-requirements https://www.python.org/dev/peps/pep-0440/#compatible-release

PEP 440 describes the use of ~ in version specifiers. Imprecisely, it treats the version given as the minimum version, and treats the final segment of the version specifier as a *.

This is notably different than ^ in Poetry and other languages' package managers (caret requirements are not in PEP440), which treats the version given as the minimum version, and treats all segments after the first as a *.

I expect that for many people, if they are wishing to use only PEP440 version specifiers for some reason, that compatible releases might be just the thing to use.

Unfortunately, it appears that Poetry either has a documentation bug, or has a behavior with this version specifier that is incompatible with PEP 440. The Poetry documentation for tilde requirements gives three examples, two of which are not compatible with PEP 440 compatible release. Compare what PEP 440 says to what the Poetry docs say, to what the docs should say if using the same format to match the meaning of PEP 440.

requirement PEP 440 says Poetry docs say Poetry docs should say
~1.2.3 >=1.2.3,1.2.* >=1.2.3 <1.3.0 >=1.2.3 <1.3.0
~1.2 >=1.2,1.* >=1.2.0 <1.3.0 >=1.2.3 <2
~1 invalid >=1.0.0 <2.0.0 ?

For many cases, including my own, I expect that people will want a version specifier that is like PEP 440's ~1.2 meaning, but to get (almost) that with Poetry they have to use ~1, which is an explicitly invalid specifier under PEP 440. I think that Poetry should match PEP 440's version specifier's meaning.

Poetry might prefer to assign a valid meaning to the ~1 version, although I think I'd prefer to follow PEP 440 there as well and throw an error, but having valid PEP 440 specifiers do (or document) something other than what PEP 440 instructs seems very unwise.

sir-sigurd commented 3 years ago

@ryanhiebert It looks like > should be replaced with >= in PEP 440 says column.

sir-sigurd commented 3 years ago

I can confirm the issue with poetry 1.1.6 and 1.2.0a1, both versions work the same. I used pyjwt for testing, pyjwt~=1.6 resolves to 1.7.1 and pyjwt~=1 resolves to 1.0.1.

ryanhiebert commented 3 years ago

@sir-sigurd: Good catch. I fixed that in the OP, thank you!

andriisoldatenko commented 3 years ago

I see maybe same/or other issue with poetry==1.2.0a2:

poetry add pyjwt
Using version ^2.1.0 for PyJWT

  InvalidVersion

  Invalid PEP 440 version: '3.6.'

  at ~/.pyenv/versions/3.9.5/envs/internal-transaction-tagger-lib-python/lib/python3.9/site-packages/poetry/core/version/pep440/parser.py:67 in parse
       63│     @classmethod
       64│     def parse(cls, value: str, version_class: Optional[Type["PEP440Version"]] = None):
       65│         match = cls._regex.search(value) if value else None
       66│         if not match:
    →  67│             raise InvalidVersion(f"Invalid PEP 440 version: '{value}'")
       68│
       69│         if version_class is None:
       70│             from poetry.core.version.pep440.version import PEP440Version
       71│

The following error occurred when trying to handle this error:

  ValueError

  Could not parse version constraint: >=3.6.*

  at ~/.pyenv/versions/3.9.5/envs/internal-transaction-tagger-lib-python/lib/python3.9/site-packages/poetry/core/semver/helpers.py:139 in parse_single_constraint
      135│
      136│         try:
      137│             version = Version.parse(version)
      138│         except ValueError:
    → 139│             raise ValueError(
      140│                 "Could not parse version constraint: {}".format(constraint)
      141│             )
      142│
      143│         if op == "<":
pbhuss commented 3 years ago

The documentation is internally inconsistent. On https://python-poetry.org/docs/faq/#why-are-unbound-version-constraints-a-bad-idea , the meaning of tilde does appear to follow PEP 440's meaning of ~=:

For example instead of using >=3.4 you should use ~3.4 which allows all versions <4.0.

But this is in complete disagreement with the information presented on https://python-poetry.org/docs/dependency-specification/ as @ryanhiebert mentions.


Edit: now that I look closer, it seems like ~= (CEP 440 Compatible Release) and ~ (Tilde requirements) are both supported by Poetry and have different behavior, which explains this confusion. See this test case. Perhaps the Dependency specification documentation should mention this.

For example instead of using >=3.4 you should use ~3.4 which allows all versions <4.0.

This line should either read ~=3.4 or ^3.4, because it's currently incorrect.

In [11]: from semver import parse_constraint

In [12]: parse_constraint('~3.4')
Out[12]: <VersionRange (>=3.4,<3.5)>

In [13]: parse_constraint('~=3.4')
Out[13]: <VersionRange (>=3.4,<4.0)>

In [14]: parse_constraint('^3.4')
Out[14]: <VersionRange (>=3.4,<4.0)>
dimbleby commented 1 year ago

this issue seems to be based entirely on not spotting the difference between the poetry-special ~ and the PEP-440 ~=

ryanhiebert commented 1 year ago

@dimbleby: You are right, although there was a documentation issue brought up that appears to be rectified now.

@pbhuss: Thank you for your work on that comment, very effectively demonstrating my confusion. The documentation issue you pointed out in your comment appears to be fixed up.

I could appreciate some additional language clarifying that is is not the same as PEP 440's ~= operator, and that ~= does work. Still, my OP was ultimately invalid and confused.

github-actions[bot] commented 7 months ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.