Posted on :: 541 Words :: Tags: , ,

UV is a fast alternative to pip. It offers better speed and performance, making it a smooth replacement. To get started, just run pip install uv to download the binary.

All functions from pip are still there. Just use the command uv pip to access them.

Migrating a project

Let's explore how to migrate a project using requirements.txt and requirements-dev.txt files to UV.

Initialize with UV

Begin by creating a new UV-managed project using:

uv init
BASH

This command will create a pyproject.toml file. This file is a central configuration file in Python, introduced to standardize packaging and build processes.

Clean up the Environment

To avoid using an old virtual environment, replace it by running:

rm -r .venv/
uv venv && source .venv/bin/activate
BASH

Add your requirements

Now, add your requirements from existing files:

uv add -r requirements.txt
uv add --dev -r requirements-dev.txt
BASH

Your pyproject.toml file should now look something like this:

[project]
name = "myproject"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi==0.115.7",
]

[dependency-groups]
dev = [
    "mypy==1.14.1",
    "pre-commit==4.1.0",
]
TOML

With uv sync dependencies are installed (or uninstalled if no longer mentioned) in a virtual environment under the .venv directory.

Additional Configurations

If your project is a package, be sure to add this:

[tool.uv]
package = true
TOML

CLI Commands

To expose CLI commands from your project, include them in the pyproject.toml file:

[project.scripts]
my-cmd = "my_project.cli:app"
TOML

Publish to PyPI

To publish your package to PyPI, you can do it manually or use automation tools like GitHub Actions or GitLab CI/CD.

UV provides a packaging Guide to assist you with this: https://docs.astral.sh/uv/guides/package/

Locally

Locally is quite simple, you just need the UV tool and a PyPI token.

First, build your package and then publish it:

uv build

uv publish --token <token>
BASH

GitHub Workflow

You can use the following GitHub workflow to publish a package to PyPI. It triggers the push-pypi job when a tag is pushed. The version in the tag updates the pyproject.toml file before building and publishing.

---
on:
  push:
    branches:
      - "*"
    tags:
      - "v*.*.*"

jobs:
  push-pypi:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Install python dependencies
        run: uv sync --all-extras --dev

      - name: Set release env, replace 'v' in tag
        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" | sed "s/v//g" >> $GITHUB_ENV

      - name: Replace version in pyproject.toml
        run: |
          sed -i 's/^version = "[^"]*"/version = "${{env.RELEASE_VERSION}}"/' pyproject.toml

      - name: Build package
        run: uv build

      - name: Publish package
        run: uv publish
YAML

GitLab CI/CD

Here's a GitLab CI/CD job that runs when a tag is pushed. It updates the pyproject.toml version, builds, and publishes the package to PyPI.

---
stages:
  - release

push_pypi:
  stage: release
  rules:
    - if: "$CI_COMMIT_TAG"
    - if: $CI_COMMIT_BRANCH
      when: never
  image: "docker.io/python:3.13-slim"
  script:
    - pip install uv
    - projectversion=$(echo $CI_COMMIT_REF_NAME | sed 's/v//')
    - sed -i "s/^version = \".*\"/version = \"$projectversion\"/" "${CI_PROJECT_DIR}/pyproject.toml"
    - cat "${CI_PROJECT_DIR}/pyproject.toml" | grep version
    - uv build ${CI_PROJECT_DIR}
    - uv publish --token "$PYPI_TOKEN"
YAML