10aday › tools
^ and ~ to [project] dependenciesSince Poetry 2, dependencies can live in the standard [project] table, which pip, uv, Hatch and PDM also read.
That table uses PEP 508 strings, and PEP 440 has no caret (^) and no tilde (~).
Paste your pyproject.toml (or just the [tool.poetry.dependencies] lines) and get the equivalent
[project] table: explicit bounds, python = … turned into markers, extras and groups carried over.
Everything runs in your browser; nothing is sent anywhere. Keep [build-system], scripts and other [tool.poetry] settings as they are.
| Poetry | Meaning | PEP 440 / [project] |
|---|---|---|
^1.2.3 | no change to the first non-zero number | >=1.2.3,<2.0.0 |
^0.2.3 | 0.x: the minor is the breaking number | >=0.2.3,<0.3.0 |
^0.0.3 | 0.0.x: only that patch line | >=0.0.3,<0.0.4 |
^1.2 / ^1 | same rule, shorter bound | >=1.2,<2.0 / >=1,<2 |
~1.2.3 | patch updates | >=1.2.3,<1.3.0 (same as ~=1.2.3) |
~1.2 | patch updates | >=1.2,<1.3 (not ~=1.2, which is <2.0) |
~1 | any 1.x | >=1,<2 |
~=1.4 | PEP 440 compatible release | ~=1.4 (unchanged) |
~=1 | accepted by Poetry only | >=1,<2 (pip rejects ~=1) |
1.2.*, 1.* | wildcard | ==1.2.*, ==1.* |
"1.2.3" (bare) | exact version | ==1.2.3 |
* | any version | no specifier: just the name |
>=1.2 <2 | space = AND | >=1.2,<2 |
^1.2 || ^3.0 | OR | no equivalent: rewrite by hand |
python = "^3.9" (project) | supported Pythons | requires-python = ">=3.9,<4.0" |
python = ">=3.10" (on a dependency) | install only on those Pythons | marker ; python_version >= "3.10" |
platform = "win32" | install only on that OS | marker ; sys_platform == "win32" |
optional = true + [tool.poetry.extras] | extra | [project.optional-dependencies] |
[tool.poetry.group.dev.dependencies] | dependency group | [dependency-groups] (PEP 735) |
Not sure what your new requires-python allows? Check it with the requires-python checker (<=3.12 excludes 3.12.1, ==3.10 means 3.10.0 only).
Every Poetry constraint with its PEP 440 form, and the common ^ → ~= mistake: Poetry ^ and ~ to PEP 440.
Need the same comparison for npm, Cargo, Composer or RubyGems? Use the Version Constraint Checker
or read pip ~= and PEP 440 specifiers and tilde vs caret.
~1.2 is not ~=1.2. The first stops before 1.3, the second allows every 1.x from 1.2. Replacing one with the other by hand silently widens the range.|| has no PEP 440 equivalent. The line is left as a comment for you to fix.path = "../lib") cannot go in [project] portably. Keep them in [tool.poetry.dependencies]: Poetry 2 uses that table to enrich [project]. uv users put them in [tool.uv.sources].source = … (a private index), develop = true and allow-prereleases have no PEP 621 field.python = "3": Poetry writes the marker python_version == "3", which matches no interpreter because python_version is always X.Y. Here it is read as "any 3.x".<>: Poetry parses <>1.2 as an exact match, not "not equal". The converter keeps Poetry's reading and warns you.Does Poetry 2 still read [tool.poetry.dependencies]? Yes. If both tables exist, [project.dependencies] is used for the package metadata, and the Poetry table only adds locking details such as sources.
Moving to [project] makes the file readable by other tools (uv, pip, Hatch, PDM, Dependabot).
Should requires-python keep the <4.0 upper bound? The converter keeps exactly what ^3.9 meant in Poetry. Many projects drop the cap (>=3.9), because an upper bound in requires-python can make resolvers pick old versions of your package. Edit it if you prefer.
Are the markers the same as Poetry's? Yes, apart from the python = "3" case above: for python = …, the converter writes the markers Poetry itself writes (python_version for X.Y bounds, python_full_version when a patch number matters).
uv reads the same standard tables, so the dependency part of a Poetry-to-uv migration is this conversion. The rest, step by step:
pyproject.toml, replacing [tool.poetry.dependencies], [tool.poetry.extras] and the [tool.poetry.group.*] tables.[project]: poetry-core>=2.0 works, or switch to uv_build or hatchling. Applications that are never built as a package can skip [build-system].[tool.uv.sources] (for example mylib = { path = "../mylib", editable = true }) and list the bare name in dependencies; private indexes go to [[tool.uv.index]].uv lock, then uv sync. uv installs the dev group by default; other groups need --group name.We checked that the default example above, once converted, locks with uv 0.11 (35 packages resolved). For a whole-repository migration (lock file, scripts, authors), the open-source CLI migrate-to-uv does more; this page is for when you want to see and check each line.