Skip to content

fix: Poetry tilde on a short version converts to the wrong upper bound - #3858

Merged
frostming merged 2 commits into
pdm-project:mainfrom
deepspace28:fix/poetry-tilde-constraint
Aug 27, 2026
Merged

fix: Poetry tilde on a short version converts to the wrong upper bound#3858
frostming merged 2 commits into
pdm-project:mainfrom
deepspace28:fix/poetry-tilde-constraint

Conversation

@deepspace28

Copy link
Copy Markdown
Contributor

What's broken

pdm import -f poetry converts every tilde constraint to PEP 440's ~=. That only means the same thing when all three components are present. With a short version it either widens the constraint or produces a specifier that isn't valid at all:

Poetry means ~= equivalent pdm imported
~1.2.3 >=1.2.3 <1.3.0 >=1.2.3, ==1.2.* correct
~1.2 >=1.2.0 <1.3.0 >=1.2, ==1.* <2.0, too wide
~1 >=1.0.0 <2.0.0 — not a valid specifier import aborts

~= requires at least two release segments, so the one-component case doesn't just import wrongly, it raises.

Repro

foo = "~1.2" and bar = "~1" in a Poetry project:

before:
dependencies = ["foo~=1.2"]                 # accepts 1.9.9, which Poetry refuses
bar          -> MetaConvertError: dependencies: Invalid specifier for bar: ~=1

after:
dependencies = ["foo<1.3,>=1.2", "bar<2,>=1"]

The same path handles requires-python, so python = "~3" aborts the whole import today:

before: InvalidPyVersion: Invalid specifier: ~=3
after:  requires-python = "<4,>=3"

The fix

A tilde allows patch-level changes when a minor version is given and minor-level changes otherwise, so the bumped position is the minor when there is one and the major when there isn't. _tilde_upper_bound computes it and the tilde gets an explicit upper bound, the way _caret_upper_bound already does for ^ (#3848).

Verification

test_convert_poetry_tilde_constraint is parametrised over Poetry's own tilde table, ~1.2.3 through ~0, checked against the table in their dependency-specification docs, plus test_convert_poetry_tilde_python_constraint for the requires-python path. Reverting only src/pdm/formats/poetry.py turns 8 of the 9 new cases red; the one that stays green is ~1.2.3, which ~= already handled correctly.

One assertion in test_convert_poetry had the old bound baked in. The fixture is Poetry's own pyproject.toml with python = "~2.7 || ^3.4", so requires-python was asserting the bug — ~2.7 stops below 2.8, and the expectation now carries the resulting !=2.8.*.

tests/test_formats.py passes, 68 tests. Full suite is 1378 passed / 7 failed on this box, and those 7 are identical on a clean checkout — they need symlink privileges, POSIX echo/cat on PATH, or a working python3.14. ruff check and ruff format --check clean. Windows 11, CPython 3.11.

deepspace28 and others added 2 commits August 22, 2026 22:10
A tilde requirement allows patch-level changes when a minor version is
given and minor-level changes otherwise, so `~1.2` means `<1.3.0` and `~1`
means `<2.0.0`. Mapping it onto PEP 440's `~=` only agrees when all three
components are present: `~=1.2` means `<2.0`, and `~=1` is not a valid
specifier at all, so a one-component tilde aborted the import.

Give the tilde an explicit upper bound the way the caret already has one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUJeBSssdWdgDj3AVsPqkA
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUJeBSssdWdgDj3AVsPqkA
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.52%. Comparing base (a7e9d00) to head (6a468d3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3858   +/-   ##
=======================================
  Coverage   88.51%   88.52%           
=======================================
  Files         121      121           
  Lines       13281    13288    +7     
  Branches     2257     2257           
=======================================
+ Hits        11756    11763    +7     
  Misses        960      960           
  Partials      565      565           
Flag Coverage Δ
unittests 88.41% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@frostming
frostming merged commit d08a1cc into pdm-project:main Aug 27, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants