Metadata-Version: 2.4
Name: uv-align
Version: 0.1.4
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Utilities
Requires-Dist: maturin>=1.14.1 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Align `pyproject.toml` dependency constraints with versions resolved by `uv`.
Keywords: uv,python,dependencies,pyproject,cli
Home-Page: https://pypi.org/project/uv-align
Author-email: Javid Ahmed <javidahmed@icloud.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: homepage, https://pypi.org/project/uv-align
Project-URL: repository, https://github.com/javidahmed64592/uv-align

[![PyPI](https://img.shields.io/pypi/v/uv-align?style=flat-square)](https://pypi.org/project/uv-align/)
[![Rust](https://img.shields.io/badge/Rust-1.95.0-blue?style=flat-square&logo=rust)](https://www.rust-lang.org/)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json&style=flat-square)](https://docs.astral.sh/uv/)
[![CI](https://img.shields.io/github/actions/workflow/status/javidahmed64592/uv-align/ci.yml?style=flat-square&label=CI&logo=github)](https://github.com/javidahmed64592/uv-align/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/github/actions/workflow/status/javidahmed64592/uv-align/docs.yml?style=flat-square&label=Docs&logo=github)](https://github.com/javidahmed64592/uv-align/actions/workflows/docs.yml)
[![Publish](https://img.shields.io/github/actions/workflow/status/javidahmed64592/uv-align/publish.yml?style=flat-square&label=Publish&logo=github)](https://github.com/javidahmed64592/uv-align/actions/workflows/publish.yml)
[![Release](https://img.shields.io/github/actions/workflow/status/javidahmed64592/uv-align/release.yml?style=flat-square&label=Release&logo=github)](https://github.com/javidahmed64592/uv-align/actions/workflows/release.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

<!-- omit from toc -->
# uv-align

`uv-align` is a Rust command-line tool that keeps your Python dependency constraints in sync between `pyproject.toml` and `uv.lock`.

When you run `uv lock --upgrade`, `uv` resolves and updates `uv.lock` with the latest compatible versions, but leaves the version constraints in `pyproject.toml` untouched.
This means your declared constraints can drift from what `uv` actually resolved - `uv-align` bridges that gap.

It reads the resolved versions from `uv.lock` and updates the version numbers in `pyproject.toml` accordingly, preserving your existing operators (`>=`, `==`, etc.), upper bounds, environment markers, and formatting.
It does not require a virtual environment as it does not install packages, and never modifies `uv.lock` directly (`uv` is responsible for that).

**Links:**

- [PyPI](https://pypi.org/project/uv-align/)
- [GitHub](https://github.com/javidahmed64592/uv-align)

<!-- omit from toc -->
## Table of Contents
- [Installation](#installation)
- [Quick Guide](#quick-guide)
- [Usage](#usage)
- [How It Works](#how-it-works)
- [Issues](#issues)
- [License](#license)

## Installation

**As a tool (recommended)**

Install `uv-align` as a global tool:

```sh
uv tool install uv-align
```

If you'd like to run it without installing:

```sh
uvx uv-align
```

**As a dev dependency**

To pin `uv-align` to a specific project, add it to your development dependencies in `pyproject.toml`:

```sh
uv add uv-align --optional dev
```

**As a pre-built binary**

Pre-built binaries for Linux, macOS, and Windows are available on the [GitHub Releases page](https://github.com/javidahmed64592/uv-align/releases).

## Quick Guide

```sh
uv-align -h      # Show help message
uv-align --check # Check for out-of-sync dependencies between `pyproject.toml` and `uv.lock`
uv-align -y      # Update any out-of-sync version constraints in `pyproject.toml`
uv-align -yu     # Upgrade dependencies and update version constraints in `pyproject.toml`
```

Example output:

![Example Usage](https://github.com/javidahmed64592/uv-align/blob/main/docs/usage.png)

## Usage

```
Align `pyproject.toml` dependency constraints with versions resolved by `uv`

Usage: uv-align [OPTIONS] [PATH]

Arguments:
  [PATH]  Path to folder containing `pyproject.toml` and `uv.lock` files [default: .]

Options:
      --check    Show a diff of dependency updates without applying them
  -y, --yes      Automatically apply all changes without prompting
  -u, --upgrade  Upgrade dependencies in `uv.lock` with `uv lock --upgrade`
  -v, --verbose  Show detailed information about dependency updates
  -h, --help     Print help
```

## How It Works

`uv-align` is a file transformer - in its default mode it reads two files and writes one, with no network access or environment inspection.
The optional `--upgrade` flag adds a `uv` invocation that does require network access to fetch updated package metadata.

**Step 1 - Parse `pyproject.toml`**

All dependency entries are read from three locations:

- `[project.dependencies]` - main dependencies
- `[project.optional-dependencies]` - extras groups (e.g. `dev`, `docs`)
- `[dependency-groups]` - PEP 735 groups supported by uv

Each entry is parsed as a PEP 508 string.
The package name, version operator (`>=`, `==`, `~=`, etc.), lower-bound version, extras, and any suffix constraints (e.g. `,<1.0`) are extracted separately.
Git/URL dependencies (e.g. `package @ git+https://...`) are included in the parsed output but have no version to compare, so they are naturally skipped during the diff step.
Package names are normalised per PEP 503 (lowercase, runs of `[-_.]` collapsed to `-`) to ensure consistent matching.

**Step 2 - Parse `uv.lock`**

Every `[[package]]` entry in the lockfile is read to build a list of resolved package names and versions.
Package names are normalised in the same way as step 1, so that `my_package` and `my-package` match correctly.
Packages without a `version` field (git/URL sources) are skipped naturally by the parser.

**Step 3 - Compute the diff**

Each dependency from `pyproject.toml` that has a resolved version is looked up in the lockfile by its normalised name.
If the resolved version differs from the declared version, a change is recorded.
Version strings are compared after normalising trailing `.0` components, so `0.25.0` and `0.25` are treated as equal and no spurious change is emitted.

**Step 4 - Report or apply**

In `--check` mode, the diff is printed and the tool exits with code 1 if any changes are needed (suitable for CI), or code 0 if everything is already in sync.
Without this flag, the user is prompted to confirm before changes are applied (or `-y` skips the prompt).
Changes are written back to `pyproject.toml` using `toml_edit` - a format-preserving TOML library that modifies only the version numbers, leaving all comments, whitespace, and key ordering intact.
The original operator and any suffix constraints are preserved verbatim.

**Step 5 - Upgrade mode (`-u`)**

With `--upgrade`, `uv lock --upgrade` runs first to fetch and resolve the latest compatible versions, updating `uv.lock` accordingly.
Steps 1–4 then run as normal to align `pyproject.toml` with the newly resolved versions.
The number of updated, added, and removed packages reported by `uv` is printed as a summary, with full package details available via `--verbose`.

## Issues

If you encounter any issues, please report them on the [GitHub Issues page](https://github.com/javidahmed64592/uv-align/issues).
Please provide a brief description of how you tried to run the tool, the command you ran, and the output in the terminal.
Also, provide the contents of your `pyproject.toml`, the expected updated `pyproject.toml`, and your `uv.lock` file if possible.

See the [integration tests](https://github.com/javidahmed64592/uv-align/tree/main/tests/fixtures) to understand how these three files will help reproduce the issue and aid in fixing it.

## License

This project is licensed under the MIT License - see the [LICENSE](https://github.com/javidahmed64592/uv-align/blob/main/LICENSE) file for details.

