Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ganzua constraints bump

Usage: ganzua constraints bump [OPTIONS] [PYPROJECT]

Update pyproject.toml dependency constraints to match the lockfile.

Of course, the lockfile should always be a valid solution for the constraints. But often, the constraints are somewhat relaxed. This tool will increment the constraints to match the currently locked versions. Specifically, the locked version becomes a lower bound for the constraint.

This tool will try to be as granular as the original constraint. For example, given the old constraint foo>=3.5 and the new version 4.7.2, the constraint would be updated to foo>=4.7.

The PYPROJECT argument should point to a pyproject.toml file, or to a directory containing such a file. If this argument is not specified, the one in the current working directory will be used.

Options:

  • --lockfile PATH Where to load versions from. Inferred if possible.
    • file: use the path as the lockfile
    • directory: use the lockfile in that directory
    • default: use the lockfile in the PYPROJECT directory
  • --backup PATH Store a backup in this file.
  • --name FILTER Include/exclude constraints to edit by package name. [default: edit all]
  • --help Show this help message and exit.

Examples

These examples demonstrate the command line interface. Further below are examples of how different version constraint operators are bumped.

Uv Example

Ganzua will edit standard/uv pyproject.toml files.

Given an example directory with a pyproject.toml file with outdated constraints:

$ cp $CORPUS/constraints-uv-pyproject.toml $EXAMPLE/pyproject.toml

And given an uv.lock file with the following contents:

nameversion
annotated-types0.7.0
example0.2.0
typing-extensions4.14.1

When we bump the constraints (and make a backup):

$ ganzua constraints bump $EXAMPLE --backup=$EXAMPLE/backup-pyproject.toml

Then the pyproject.toml file contains updated constraints, whereas the backup contains the old version.

  • $ cat $EXAMPLE/pyproject.toml
  • $ cat $EXAMPLE/backup-pyproject.toml
  • $ cat $CORPUS/constraints-uv-pyproject.toml
output for the above commands

Output for:

  • $ cat $EXAMPLE/pyproject.toml
[project]
name = "example"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "Typing.Extensions>=4,<5",  # moar type annotations
    "merrily-ignored",
    [42, "also ignored"],  # we ignore invalid junk
]

[project.optional-dependencies]
extra1 = [
    "annotated-types>=0.7.0,==0.7.*",
]
extra2 = false  # known invalid
extra3 = ["ndr"]

[dependency-groups]
group-a = ["typing-extensions~=4.14"]
group-b = [{include-group = "group-a"}, "annotated-types~=0.7.0"]

Output for:

  • $ cat $EXAMPLE/backup-pyproject.toml
  • $ cat $CORPUS/constraints-uv-pyproject.toml
[project]
name = "example"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "Typing.Extensions>=3,<4",  # moar type annotations
    "merrily-ignored",
    [42, "also ignored"],  # we ignore invalid junk
]

[project.optional-dependencies]
extra1 = [
    "annotated-types >=0.6.1, ==0.6.*",
]
extra2 = false  # known invalid
extra3 = ["ndr"]

[dependency-groups]
group-a = ["typing-extensions ~=3.4"]
group-b = [{include-group = "group-a"}, "annotated-types ~=0.6.1"]

Poetry example

We can also do the same with Poetry pyproject.toml files.

Given an example directory with a pyproject.toml file with outdated constraints:

$ cp corpus/constraints-poetry-pyproject.toml $EXAMPLE/pyproject.toml

And given a poetry.lock file with the following content:

nameversion
annotated-types0.7.0
example0.2.0
typing-extensions4.14.1

When we bump the constraints:

$ ganzua constraints bump $EXAMPLE

Then the pyproject.toml file contains updated constraints.

$ cat $EXAMPLE/pyproject.toml
[tool.poetry.dependencies]
Typing_Extensions = "^4.14"
ignored-garbage = { not-a-version = true }

[build-system]

[tool.poetry.group.poetry-a.dependencies]
typing-extensions = { version = "^4.14" }
already-unconstrained = "*"

No-op example

When the constraints are already up to date, Ganzua doesn't modify them.

Given a pyproject.toml file:

$ cp corpus/new-uv-project/pyproject.toml $EXAMPLE/pyproject.toml

When we bump constraints using an already-matching lockfile:

$ ganzua constraints bump --lockfile=corpus/new-uv-project/uv.lock $EXAMPLE/pyproject.toml

Then the old and new file are the same.

  • $ cat corpus/new-uv-project/pyproject.toml
  • $ cat $EXAMPLE/pyproject.toml
output for the above commands
[project]
name = "example"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "annotated-types>=0.7.0",
    "typing-extensions>=4",
]

Empty example

Since Ganzua does not validate that the pyproject.toml file is semantically valid, it's also possible to edit an empty file.

Given an empty pyproject.toml file:

$ touch $EXAMPLE/pyproject.toml

When we bump the constraints in the empty pyproject.toml file, then Ganzua succeeds, and the pyproject.toml file remains empty:

$ ganzua constraints bump --lockfile=corpus/new-uv-project/uv.lock $EXAMPLE/pyproject.toml
$ cat $EXAMPLE/pyproject.toml

Split versions

If a project has split versions for a package, matching requirements will be skipped. It is not possible to determine which requirement is supposed to be matched up with which locked version, so these instances will have to be fixed manually. Note that depending on workflow, this means the lockfile and the pyproject.toml might become inconsistent.

The exact behavior here is not considered stable. Future Ganzua versions may change the handling of split versions if solutions to these problems are found.

Let's look at a concrete example with a split version:

$ cp $CORPUS/split/old.pyproject.toml $EXAMPLE/pyproject.toml
$ cp $CORPUS/split/uv.lock $EXAMPLE/uv.lock

Current pyproject.toml file:

$ cat $EXAMPLE/pyproject.toml
[project]
name = "split"
version = "0.1.0"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
  "typing_extensions >=4.7.1 ; python_version >= '3.14'",
  "typing_extensions >=3.7.2,<4 ; python_version < '3.14'",
]

Currently locked versions:

packageversion
split0.1.0
typing-extensions3.10.0.2
typing-extensions4.15.0

Now we try to bump the old constraints:

$ ganzua constraints bump $EXAMPLE
ganzua: package `typing-extensions` has multiple candidate versions: 3.10.0.2, 4.15.0

But all we get is a warning. The constraints remain unchanged:

$ cat $EXAMPLE/pyproject.toml
[project]
name = "split"
version = "0.1.0"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
  "typing_extensions >=4.7.1 ; python_version >= '3.14'",
  "typing_extensions >=3.7.2,<4 ; python_version < '3.14'",
]

Default files example

Ganzua tries to infer the pyproject.toml and lockfile.

Empty directory: Running Ganzua against an empty directory will fail, as it cannot locate a pyproject.toml file in there.

$ env -C $EXAMPLE ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock
Usage: ganzua constraints bump [OPTIONS] [PYPROJECT]
Try 'ganzua constraints bump --help' for help.

Error: Did not find default `pyproject.toml`.
[command exited with status 2]

Infering pyproject.toml: If no pyproject.toml file is specified explicitly, Ganzua will look for it in the current working directory. If a path to a directory is given, the file in that directory will be used.

For this example, let's create a pyproject.toml file:

$ cp $CORPUS/old-uv-project/pyproject.toml $EXAMPLE/pyproject.toml
$ ls $EXAMPLE
pyproject.toml

Now, all of the following commands are equivalent:

$ env -C $EXAMPLE ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock
$ env -C $EXAMPLE ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock pyproject.toml
$ env -C $EXAMPLE ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock .
$ ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock $EXAMPLE/pyproject.toml
$ ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock $EXAMPLE

Lockfile is required: If we leave out the explicit --lockfile argument, then Ganzua will fail, because there's currently no lockfile in the example directory:

$ ls $EXAMPLE
pyproject.toml
$ env -C $EXAMPLE ganzua constraints bump
Usage: ganzua constraints bump [OPTIONS] [PYPROJECT]
Try 'ganzua constraints bump --help' for help.

Error: Could not infer `--lockfile` for `.`.
[command exited with status 2]

But an explicit lockfile succeeds:

$ env -C $EXAMPLE ganzua constraints bump --lockfile=$CORPUS/new-uv-project/uv.lock

Infering lockfiles: If no explicit --lockfile is given, then Ganzua will look for an uv.lock or poetry.lock file in the directory of the pyproject.toml file. The lockfile may also be given as a different directory, in which case Ganzua will look for the lockfile in that directory.

If we add a lockfile in the example directory, then we can leave out the lockfile.

$ cp $CORPUS/new-uv-project/uv.lock $EXAMPLE/uv.lock
$ ls $EXAMPLE
pyproject.toml
uv.lock

So now these commands are equivalent:

$ ganzua constraints bump $EXAMPLE
$ ganzua constraints bump --lockfile=$EXAMPLE $EXAMPLE
$ ganzua constraints bump --lockfile=$EXAMPLE/uv.lock $EXAMPLE
$ env -C $EXAMPLE ganzua constraints bump
$ env -C $EXAMPLE ganzua constraints bump --lockfile=./
$ env -C $EXAMPLE ganzua constraints bump --lockfile=uv.lock

Conflicting lockfiles: If there are multiple candidate lockfiles in the directory, then Ganzua will report a conflict, unless a full path to a specific lockfile is given:

$ touch $EXAMPLE/poetry.lock
$ ls $EXAMPLE
poetry.lock
pyproject.toml
uv.lock
$ ganzua constraints bump $EXAMPLE
Usage: ganzua constraints bump [OPTIONS] [PYPROJECT]
Try 'ganzua constraints bump --help' for help.

Error: Could not infer `--lockfile` for `${EXAMPLE}`.
Note: Candidate lockfile: ${EXAMPLE}/uv.lock
Note: Candidate lockfile: ${EXAMPLE}/poetry.lock
[command exited with status 2]
$ ganzua constraints bump $EXAMPLE --lockfile=$EXAMPLE/uv.lock

Filters

Can use filters to only bump certain constraints. Read the filter manual for further details on how filters work.

Added in Ganzua 0.4.0: --name filters.

Let's recall the above uv example.

$ cp $CORPUS/constraints-uv-pyproject.toml $EXAMPLE/pyproject.toml
nameversion
annotated-types0.7.0
example0.2.0
typing-extensions4.14.1

We can now decide that we only want to bump the typing-extensions constraints, not the annotated-types constraints:

$ ganzua constraints bump $EXAMPLE --name=typing-extensions --backup=$EXAMPLE/old.toml

Old constraints:

$ ganzua constraints inspect $EXAMPLE/old.toml --format=markdown
| package           | version         | group/extra                      |
|-------------------|-----------------|----------------------------------|
| annotated-types   | ==0.6.*,>=0.6.1 | extra `extra1`                   |
| annotated-types   | ~=0.6.1         | group `group-b`                  |
| merrily-ignored   |                 |                                  |
| ndr               |                 | extra `extra3`                   |
| typing-extensions | <4,>=3          |                                  |
| typing-extensions | ~=3.4           | group `group-a`, group `group-b` |

Updated constraints:

$ ganzua constraints inspect $EXAMPLE --format=markdown
| package           | version         | group/extra                      |
|-------------------|-----------------|----------------------------------|
| annotated-types   | ==0.6.*,>=0.6.1 | extra `extra1`                   |
| annotated-types   | ~=0.6.1         | group `group-b`                  |
| merrily-ignored   |                 |                                  |
| ndr               |                 | extra `extra3`                   |
| typing-extensions | <5,>=4          |                                  |
| typing-extensions | ~=4.14          | group `group-a`, group `group-b` |

Examples of different constraints

The following examples illustrate how version bumping works.

The different version specifier operators are defined in PEP-440, with the up-to-date specifications on packaging.python.org.

Certain combinations of operators are often used to describe SemVer constraints, e.g. >=1.2.3,<2 or >=1.2.3,==1.* (see SemVer examples).

Poetry supports certain additional operators:

Syntax for example tables:

  • The locked column shows the locked version in format <package> <version>, e.g. foo 1.2.3.
  • The old and new column show the constraint before and after bumping.
    • may use PEP-508/PEP-440 expressions, e.g. foo >=1.2.3
    • may describe Poetry version constraints using a <package> = <version> format but without quoting that would appear in a TOML file, e.g. foo = *

Basic examples

Will only bump packages that are present in the lockfile. Other constraints are not affected:

lockedoldnew
locked 1.2.3other >=4,<5other >=4,<5
locked 1.2.3other = ^4other = ^4

Bumping matches the granularity of the bounds that are currently present.

If there are no constraints for that package, bumping will not add any bounds:

lockedoldnew
package 1.2.3packagepackage
package 1.2.3package = *package = *

Lower version bound examples

When the major version changes, a >= bound is bumped with matching granularity:

lockedoldnew
major 7.1.2major>=4major>=7
minor 7.1.2minor>=4.3minor>=7.1
patch 7.1.2patch>=4.3.2patch>=7.1.2
minor-poetry 7.1.2minor-poetry = >=4.3minor-poetry = >=7.1

When the minor or patch version changes, a >= bound is bumped with matching granularity. Here, no changes to the major constraint are needed. Granularity is determined based on how many version elements have been explicitly provided. So whereas the versions 4 and 4.0 are semantically equivalent, they have different granularity.

lockedoldnew
major 4.5.6major>=4major>=4
minor0 4.5.6minor0>=4.0minor0>=4.5
minor 4.5.6minor>=4.3minor>=4.5
patch 4.5.6patch>=4.3.2patch>=4.5.6
minor-poetry 4.5.6minor-poetry = >=4.3minor-poetry = >=4.5
patch-poetry 4.5.6patch-poetry = >=4.3.2patch-poetry = >=4.5.6

Constraints may also be downgraded if this is necessary to match the locked version:

lockedoldnew
major 4.5.6major>=7major>=4
minor 4.5.6minor>=4.9minor>=4.5
patch 4.5.6patch>=4.5.9patch>=4.5.6

SemVer examples

Poetry has the ^ SemVer operator. It is bumped the same way as >= bounds:

lockedoldnew
major 7.1.2major = ^4major = ^7
minor 7.1.2minor = ^7.0minor = ^7.1
patch 7.1.2patch = ^7.1.1patch = ^7.1.2
downgrade 7.1.2downgrade = ^9.8downgrade = ^7.1

However, SemVer bounds are also frequently expressed via PEP-440 expressions. For example, the Poetry bound ^4.1 can also be expressed via the following idioms:

  • upper bounds >=4.1,<5
  • version matching prefixes >=4.1,==4.*

Upper bounds will be bumped when they look like a SemVer bound (>=N, <N+1) and would be outdated by the new version:

lockedoldnew
major 7.1.2major>=4,<5major>=7,<8
minor0 7.1.2minor0>=4.0,<5minor0>=7.1,<8
minor 7.1.2minor>=4.9,<5minor>=7.1,<8
patch 7.1.2patch>=4.0.1,<5patch>=7.1.2,<8

However, upper bounds are not changed if they remain valid for the locked version:

lockedoldnew
minor-upgrade 4.5.6minor-upgrade>=4.3,<5minor-upgrade>=4.5,<5
minor-downgrade 4.5.6minor-downgrade>=4.9,<5minor-downgrade>=4.5,<5
wide 7.1.2wide>=4.5,<9wide>=7.1,<9

The prefix version matching SemVer idiom behaves more intuitively because each bound in the constraint will be bumped independently to match the new version:

lockedoldnew
major 7.1.2major>=4,==4.*major>=7,==7.*
minor0 7.1.2minor0>=4.0,==4.*minor0>=7.1,==7.*
minor 7.1.2minor>=4.9,==4.*minor>=7.1,==7.*
minor-match 7.1.2minor-match>=4.9,==4.9.*minor-match>=7.1,==7.1.*
patch 7.1.2patch>=4.0.1,==4.*patch>=7.1.2,==7.*
downgrade 7.1.2downgrade >=9.8,==9.*downgrade>=7.1,==7.*
minor-upgrade 4.5.6minor-upgrade>=4.3,==4.*minor-upgrade>=4.5,==4.*
minor-downgrade 4.5.6minor-downgrade>=4.9,==4.*minor-downgrade>=4.5,==4.*

Strict version matching examples

The == operator can be used for exact version matching (without * wildcards for prefix matching). Exact bounds will be bumped to match the locked version exactly.

The == operator is also implied when a Poetry version constraint doesn't use any operators.

expand PEP-440 examples
lockedoldnew
foo 7.1.2foo==4.3.2foo==7.1.2
foo 7.1.2foo==4.3foo==7.1.2
foo 7.1.2foo==4foo==7.1.2
foo 7.1.2foo==7foo==7.1.2
foo 7.1.2foo==7.1foo==7.1.2
foo 7.1.2foo==7.1.2foo==7.1.2
expand Poetry examples
lockedoldnew
foo 7.1.2foo = 4.3.2foo = 7.1.2
foo 7.1.2foo = 4.3foo = 7.1.2
foo 7.1.2foo = 4foo = 7.1.2
foo 7.1.2foo = 7foo = 7.1.2
foo 7.1.2foo = 7.1foo = 7.1.2
foo 7.1.2foo = 7.1.2foo = 7.1.2

Prefix version matching examples

When the == operator is combined with * wildcards, it performs prefix matching. Such constraints are updated with matching granularity.

The == operator is implied when a Poetry version constraint doesn't use any operators.

expand PEP-440 examples
lockedoldnew
foo 7.1.2foo==4.3.*foo==7.1.*
foo 7.1.2foo==4.*foo==7.*
foo 7.1.2foo==7.*foo==7.*
foo 7.1.2foo==7.0.*foo==7.1.*
foo 7.1.2foo==7.1.*foo==7.1.*
expand Poetry examples
lockedoldnew
foo 7.1.2foo = 4.3.*foo = 7.1.*
foo 7.1.2foo = 4.*foo = 7.*
foo 7.1.2foo = 7.*foo = 7.*
foo 7.1.2foo = 7.0.*foo = 7.1.*
foo 7.1.2foo = 7.1.*foo = 7.1.*

Compatible release examples

The ~= compatible release operator (spec) is bumped the same way as >= lower bounds: it will be bumped so that it is a lower bound for the currently locked version, matching granularity.

Note that a ~= compatible release clause needs to consist at least of ~=major.minor. A single element is insufficient.

See also the Poetry ~ tilde operator.

expand PEP-440 examples
lockedoldnew
major 7.1.2major~=4.5major~=7.1
minor0 4.5.6minor0~=4.0minor0~=4.5
minor 4.5.6minor~=4.3minor~=4.5
patch 4.5.6patch~=4.3.2patch~=4.5.6
minor-poetry 4.5.6minor-poetry = ~=4.3minor-poetry = ~=4.5
patch-poetry 4.5.6patch-poetry = ~=4.3.2patch-poetry = ~=4.5.6
major-downgrade 4.5.6major-downgrade~=7.9major-downgrade~=4.5
minor-downgrade 4.5.6minor-downgrade~=4.9minor-downgrade~=4.5
patch-downgrade 4.5.6patch-downgrade~=4.5.9patch-downgrade~=4.5.6
noop 1.2.3noop~=1.2.3noop~=1.2.3

Poetry tilde examples

Tilde requirements (spec) can only occur within the [tool.poetry] table. They work similarly to compatible release constraints, but describe different version ranges. Notably, a single segment like ~1 is allowed.

Poetry bumps such constraints the same way as >= lower version bounds.

expand Poetry examples
lockedoldnew
major 7.1.2major = ~4major = ~7
major 7.1.2major = ~4.5major = ~7.1
minor0 4.5.6minor0 = ~4.0minor0 = ~4.5
minor 4.5.6minor = ~4.3minor = ~4.5
patch 4.5.6patch = ~4.3.2patch = ~4.5.6
major-downgrade 4.5.6major-downgrade = ~7major-downgrade = ~4
major-downgrade 4.5.6major-downgrade = ~7.9major-downgrade = ~4.5
minor-downgrade 4.5.6minor-downgrade = ~4.9minor-downgrade = ~4.5
patch-downgrade 4.5.6patch-downgrade = ~4.5.9patch-downgrade = ~4.5.6
noop 1.2.3noop = ~1.2.3noop = ~1.2.3
noop 1.2.3noop = ~1.2noop = ~1.2
noop 1.2.3noop = ~1noop = ~1

Version exclusion examples

The != version exclusion clause (spec) is the inverse of == version matching. It can be used to exclude specific versions like !=1.2.3 or prefixes like !=1.2.*.

When Ganzua bumps a version exclusion specifier, the specifier will be retained as-is, unless it would exclude the currently locked version.

exclusions are kept by default
lockedoldnew
foo 7.1.2foo!=4.3.2foo!=4.3.2
foo 7.1.2foo!=4.*foo!=4.*
foo 7.1.2foo!=7.9.*foo!=7.9.*
foo 7.1.2foo!=7.0.1foo!=7.0.1
exclusions are removed if they would exclude the current version
lockedoldnew
foo 7.1.2foo!=7.*foo
foo 7.1.2foo!=7.1.*foo
foo 7.1.2foo!=7.1.2foo

Arbitrary equality examples

The === arbitrary equality comparison (spec) matches only an exact version, without any semantic interpretation of the version number.

When Ganzua bumps arbitrary equality clauses, it always sets the currently locked version. Ganzua requires that the currently locked version is valid, but allows the old version constraint to contain an invalid version number.

expand examples
lockedoldnew
foo 7.1.2.post1+abcfoo===7.1.2foo===7.1.2.post1+abc
foo 7.1.2foo===7.1.2.post1+abcfoo===7.1.2
foo 7.1.2foo===whateverfoo===7.1.2
unmodified 7.1.2unmodified===7.1.2unmodified===7.1.2

Ordered comparison examples

The version constraint operators <=, <, and > (spec) cannot be bumped meaningfully. Ganzua will retain them as-is if they allow the locked version, else remove them.

expand examples
lockedoldnew
foo 7.1.2foo>4foo>4
foo 7.1.2foo>8foo
foo 7.1.2foo<5foo
foo 7.1.2foo<8foo<8
foo 7.1.2foo<=5foo
foo 7.1.2foo<=8foo<=8
foo 7.1.2foo<=7.1foo
foo 7.1.2foo<=7.2foo<=7.2