Ganzua is a little utility for working with Python lockfiles – inspecting and diffing lockfiles, as well as editing pyproject.toml files with information from the lockfiles.

This post explains a bit of background, and offers a small tutorial.

You can install Ganzua via PyPI or view the source code at latk/ganzua on GitHub.

What makes Ganzua special

Ganzua is not a general-purpose tool. It's focused solely on working with two modern Python project managers, uv and Poetry, and their native lockfile formats. In particular, there's no support for requirements.txt.

Ganzua strives to be complete, compliant, and correct. Ganzua is 0% AI and 100% human expertise, informed by reading the relevant PEPs, docs, and the source code of relevant tools. The tool is thoroughly tested with 100% branch coverage, and has already seen months of successful use in large-scale real-world projects. Ganzua is intentionally stupid and avoids dangerous stuff like editing lockfiles or interacting with Git.

Ganzua is designed for scripting. All subcommands are designed for JSON output first, with output that conforms to a stable schema. Where appropriate, Ganzua offers an optional Markdown view on the same data, which lets scripts generate human-readable summaries. Ganzua does not offer GitHub Actions, but it's really easy to integrate Ganzua into your CI workflows.

Quickstart

Here's an example of a workflow where Ganzua helps us make dependency changes with confidence.

Which Pytest version is installed in the current project?

$ uvx ganzua inspect | jq .packages.pytest
{
  "version": "8.4.2",
  "source": "pypi"
}

That's out of date, let's upgrade: uv lock --upgrade.

That changed a lot of other things as well. Let's get a human-readable summary of all changes that we can paste into a commit message:

$ uvx ganzua diff <(git show HEAD:uv.lock) uv.lock --format=markdown
7 changed packages (7 updated)

| package | old       | new        | notes |
|---------|-----------|------------|-------|
| astroid | 4.0.1     | 4.0.2      |       |
| certifi | 2025.10.5 | 2025.11.12 |       |
| click   | 8.3.0     | 8.3.1      |       |
| psutil  | 7.1.2     | 7.1.3      |       |
| pylint  | 4.0.2     | 4.0.3      |       |
| pytest  | 8.4.2     | 9.0.1      | (M)   |
| ruff    | 0.14.3    | 0.14.6     |       |

* (M) major change

However, our pyproject.toml constraints are now out of sync with the locked versions:

$ uvx ganzua constraints inspect --format=markdown
| package        | version           |
|----------------|-------------------|
| click          | >=8.3.0           |
| mypy           | >=1.18.2          |
| psutil         | >=7.1.2           |
| pylint         | >=4.0.2           |
| pytest         | >=8.4.2           |
| requests       | >=2.32.5          |
| ruff           | >=0.14.3          |
| tabulate       | >=0.9.0           |
| types-requests | >=2.32.4.20250913 |
| types-tabulate | >=0.9.0.20241207  |

Let's update them to match the locked versions:

$ uvx ganzua constraints bump
$ uvx ganzua constraints inspect --format=markdown
| package        | version           |
|----------------|-------------------|
| click          | >=8.3.1           |
| mypy           | >=1.18.2          |
| psutil         | >=7.1.3           |
| pylint         | >=4.0.3           |
| pytest         | >=9.0.1           |
| requests       | >=2.32.5          |
| ruff           | >=0.14.6          |
| tabulate       | >=0.9.0           |
| types-requests | >=2.32.4.20250913 |
| types-tabulate | >=0.9.0.20241207  |

If we're in a monorepo with a shared lockfile (e.g. an uv workspace), we can easily use Ganzua to bump constraints across all packages:

for p in packages/*/pyproject.toml; do
  uvx ganzua constraints bump --lockfile=uv.lock "$p"
done

Background

I wrote Ganzua because I was dissatisfied with existing tooling for helping with dependency updates in modern Python projects. Please excuse the following rant. Click here to skip.

I'm an unhappy Dependabot user. Dependabot is an application that's integrated into GitHub and supports multiple different languages and dependency ecosystems. It also supports various Python package managers (pip, Poetry, uv), but not very well.

Dependabot is primarily focused on being used via the GitHub web interface. Running it locally is cumbersome. I wanted a tool that's really convenient to use locally, with good support for scripting (JSON output) and with human-friendly Markdown output.

Dependabot is very slow on large dependency graphs. I've had Dependabot updates time out after an hour. I wanted update tooling that just delegates to the package managers themselves, e.g. poetry update or uv lock --upgrade which both run very fast even on very large dependency graphs (seconds, not hours).

Part of the difficulty here is that for Poetry, Dependabot updates one package at a time, which isn't just slow, but can also lead to unexpected updates that aren't properly noted. For example, when using Dependabot to update indirect dependencies, and when the project uses Pydantic, I've seen the following pattern play out more than once:

  • There are no pydantic updates.
  • But there is a new pydantic-core version.
  • Therefore, Dependabot asks Poetry to update pydantic-core to the newest version.
  • But Pydantic and Pydantic-Core versions are tightly coupled using == constraints. The new Pydantic-Core version would be incompatible with the current Pydantic version.
  • Thus, I've seen the Poetry version solver satisfy the Pydantic-Core upgrade request by also updating Pydantic, but to an alpha pre-release!
  • Since Dependabot didn't intend this Pydantic update, it's not shown in the version change table when Dependabot opens a pull request. This issue can only be noticed by manually looking at the lockfile diff.

Historically, Dependabot hasn't fared much better for uv projects. Dependabot will not bump constraints in the pyproject.toml for uv projects. Dependabot used to edit the uv.lock file directly, which would corrupt it. A dependency update tool that corrupts lockfiles is worse than useless.

GitHub features for looking at lockfile changes are limited. Of course, we can always look at the textual diff of an uv.lock or poetry.lock file. But most of the changes relate to hashes for the locked Wheels, which tends to be meaningless to human reviewers.

For poetry.lock files, GitHub does offer a rich diff view. For example:

A screenshot of the GitHub web interface, showing its “dependency review” feature for a poetry.lock file. The known dependencies are click 8.3.0, colorama 0.4.6, idna 3.11, and propcache 0.4.1.

But that only shows PyPI dependencies! It excludes dependencies from other sources (e.g. private package registries, vendored packages, URL dependencies, …). A diff that hides relevant changes is worse than useless. I need tools that are complete and correct. For comparison, here's the corresponding ganzua inspect output:

packageversion
click8.3.0
click-example-repo1.0.0
colorama0.4.6
coverage7.10.7
idna3.11
multidict6.7.0
propcache0.4.1
yarl1.22.0

So what about Renovate? Generally, this does a lot of things right, but updates for uv/Poetry projects are not among them. Whereas Dependabot is driven by the lockfile contents, Renovate is driven by the constraints in the pyproject.toml. Renovate is structurally unable to update indirect dependencies. There's a “lockfile maintenance” concept, but it consists of deleting and recreating the lockfile, without summarizing what changed.

Rant over.

So to summarize, existing tools are very good, just not for safe and valid dependency updates (including indirect dependencies), or for accurately reporting what changed.

Ganzua is not a 1:1 replacement, but offers a scripting-friendly toolkit for doing things like summarizing lockfile differences or bumping dependency constraints.

Creating Ganzua has been a lot of fun – doing things right, being absolutely anal about test coverage, using (and contributing to) state of the art Python tooling. For example, Pydantic issue #12424 was found while changing the ganzua diff output schema, because Ganzua has tests that fail whenever the schema changes. It's also been fun to debug real-world bug reports like Ganzua issue #4 where my previous assumptions about how uv works hadn't been quite correct.

Future directions

I've found Ganzua to be quite helpful in my everyday work as a Python developer. But as I find the time, I intend to evolve Ganzua so that it can be an even better helper with typical dependency update workflows.

  • More flexible constraint edits. The ganzua constraints reset subcommand can edit the pyproject.toml file to unlock all constraints. For example, a foo >=1.2.3, <2 constraint might be edited to foo (no constraint) or foo >=1.3.1 (remove upper bounds and set minimum bound to currently locked version). Such edits are useful for enabling major version upgrades. However, it would be helpful to only edit specific dependencies, or exclude certain dependencies that we know shouldn't be upgraded yet.

  • Changelog summaries. One very useful Dependabot/Renovate feature is the ability to scrape package metadata and locate the changelogs corresponding to a version change. This makes it much easier to review the impact of a dependency change. In particular, SemVer major updates might be irrelevant for a given code base, or might require difficult migrations. The Ganzua 0.3.0 package source feature is a first step towards being able to locate changelogs.

  • Limiting operations to specific dependency groups. It is common for projects to have a dev dependency group which contains testing tools and linters. It would often be helpful to only operate on the dependencies of a specific group, or only on those dependencies that aren't part of a group.

Learn more about Ganzua

If you'd like to use Ganzua, you can install it or run it without installing:

Using uv:

  • run via uvx ganzua
  • install via uv tool install ganzua

Using pipx:

  • run via pipx run ganzua
  • install via pipx install ganzua

If you run Ganzua in CI, I recommend pinning a specific version, e.g. uvx ganzua==0.3.0. I do not recommend adding Ganzua as a dependency to your project, or installing Ganzua into an existing venv. Perhaps ironically, this is likely to cause dependency conflicts.

Ganzua has comprehensive reference docs for each subcommand:

Web links for Ganzua: