← All articles
PYTHON Rye: Unified Python Project Management from the Crea... 2026-02-15 · 10 min read · rye · python · package-management

Rye: Unified Python Project Management from the Creator of Flask

Python 2026-02-15 · 10 min read rye python package-management virtual-environments developer-tools

Rye: Unified Python Project Management from the Creator of Flask

Python's packaging story has been notoriously fragmented. You need pyenv to manage Python versions, pip to install packages, virtualenv to isolate environments, and pip-tools to lock dependencies. Every project starts with twenty minutes of scaffolding before you write a single line of code. Rye, created by Armin Ronacher (the developer behind Flask, Jinja2, and Click), takes a different approach: one tool that handles all of it. Install Python, create a project, manage dependencies, lock versions, run scripts -- all from a single binary with a coherent workflow.

Rye Python project management logo

Why Rye Exists

The Python packaging ecosystem evolved organically over two decades, producing a stack of loosely coupled tools that each solve one piece of the puzzle:

These tools don't share configuration, don't coordinate on lockfile formats, and often conflict with each other. A developer starting a new Python project has to make a dozen decisions about tooling before writing any code. Ronacher built Rye to eliminate that friction -- a single tool that owns the entire workflow from Python installation through packaging and deployment.

Rye's design philosophy is opinionated but practical. It manages its own Python installations (you never touch system Python), enforces virtual environments by default, uses pyproject.toml as the single source of truth, and generates a requirements.lock file for reproducible installs. It doesn't try to replace every tool in the ecosystem -- it provides a coherent default workflow that works out of the box.

Rye and uv

An important note about the current landscape: Rye now uses uv (from Astral, the team behind Ruff) as its underlying package installer and resolver. In late 2024, Ronacher endorsed uv as the future of Python packaging and began integrating it into Rye. The two projects have complementary goals -- uv is a fast, low-level package resolver and installer, while Rye provides the higher-level project management workflow on top. If you're choosing between them, Rye gives you the full project lifecycle; uv gives you speed and pip-compatible commands. Many developers use both.

Key Features

Managed Python Installations

Rye downloads and manages Python interpreters independently from your system. No more conflicts with OS-packaged Python, no Homebrew upgrades breaking your environment.

# List available Python versions
rye toolchain list --include-downloadable

# Install a specific version
rye toolchain fetch 3.12.4

# See what's installed
rye toolchain list

Rye uses portable builds from the python-build-standalone project (the same builds uv uses), so installations are fast and don't require compiling from source.

Automatic Virtual Environments

Every Rye project gets an isolated virtual environment in .venv/. Rye creates it automatically during rye sync and keeps it in sync with your declared dependencies. You never manually create, activate, or manage virtual environments.

Lockfile-Based Dependency Management

Rye distinguishes between your declared dependencies (in pyproject.toml) and the resolved, pinned versions (in requirements.lock and requirements-dev.lock). This is the same model Cargo, npm, and Bundler use -- and the model Python has lacked for years.

Built-in Linting and Formatting

Rye bundles Ruff for linting and formatting, available via rye lint and rye fmt. No separate installation or configuration needed.

Global Tool Installation

Install Python CLI tools globally without polluting project environments, similar to pipx:

rye install black
rye install httpie
rye install ruff

Installation and Setup

Installing Rye

On Linux and macOS:

curl -sSf https://rye.astral.sh/get | bash

On Windows (PowerShell):

irm https://rye.astral.sh/get | iex

The installer adds Rye to your PATH and sets up shell completions. After installation, restart your shell or source your profile:

source ~/.bashrc   # or ~/.zshrc

Initial Configuration

Rye stores its configuration in ~/.rye/. You can configure default behavior:

# Set the default Python version for new projects
rye config --set [email protected]

# Enable uv as the package installer (default in recent versions)
rye config --set [email protected]

# Set your preferred build system
rye config --set default.build-system=hatchling

Shell Completions

# Bash
rye self completion -s bash > ~/.local/share/bash-completion/completions/rye

# Zsh
rye self completion -s zsh > ~/.zfunc/_rye

# Fish
rye self completion -s fish > ~/.config/fish/completions/rye.fish

Project Creation and Management

Creating a New Project

rye init my-project
cd my-project

This generates a clean project structure:

my-project/
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
└── src/
    └── my_project/
        └── __init__.py

The generated pyproject.toml is minimal and ready to extend:

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

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Creating a Library vs. Application

# Application (default) -- no package building, focused on running
rye init my-app

# Library -- includes packaging configuration for PyPI
rye init --lib my-library

The --lib flag sets up the project with a proper src/ layout and build system configuration suitable for distribution.

Pinning a Python Version

# Pin the project to a specific Python version
rye pin 3.12

# Pin to a specific patch version
rye pin 3.12.4

This writes a .python-version file and ensures rye sync uses the correct interpreter.

Dependency Management

Adding Dependencies

# Add a runtime dependency
rye add flask

# Add with version constraints
rye add "sqlalchemy>=2.0,<3.0"
rye add "requests~=2.31"

# Add a development dependency
rye add --dev pytest
rye add --dev ruff mypy

# Add an optional dependency group
rye add --optional web gunicorn uvicorn

Syncing the Environment

After adding or removing dependencies, sync to update the lockfile and virtual environment:

rye sync

This does three things:

  1. Resolves all dependencies and writes requirements.lock and requirements-dev.lock
  2. Creates or updates the .venv/ virtual environment
  3. Installs all resolved packages into the environment

Locking Without Installing

If you only need to update the lockfile (useful in CI):

rye lock

Removing Dependencies

rye remove flask
rye sync

Viewing the Dependency Tree

# Show all installed packages
rye list

# Show dependency tree
rye list --include-dep

Working with requirements.lock

The lockfile pins every transitive dependency to an exact version with hashes:

flask==3.1.0 \
    --hash=sha256:...
werkzeug==3.1.3 \
    --hash=sha256:...
jinja2==3.1.5 \
    --hash=sha256:...

Commit both requirements.lock and requirements-dev.lock to version control. These files ensure that everyone on the team -- and CI -- installs identical versions.

Running Scripts and Tools

Running Python Scripts

# Run a script in the project's virtual environment
rye run python src/my_project/main.py

# Run a module
rye run python -m pytest

# Run any command in the venv context
rye run flask run --debug

Defining Project Scripts

Add script shortcuts in pyproject.toml:

[tool.rye.scripts]
dev = "flask run --debug"
test = "pytest tests/ -v"
lint = "ruff check src/"
format = "ruff format src/"
typecheck = "mypy src/"
serve = { cmd = "gunicorn my_project:app", env = { WORKERS = "4" } }
migrate = { chain = ["alembic upgrade head", "python scripts/seed.py"] }

Then run them:

rye run dev
rye run test
rye run lint

The chain type runs multiple commands in sequence. The cmd type with env sets environment variables for that specific command. This replaces Makefiles for most Python projects.

Built-in Linting and Formatting

Rye ships with Ruff integration:

# Lint the project
rye lint

# Lint with auto-fix
rye lint --fix

# Format the project
rye fmt

# Check formatting without modifying files
rye fmt --check

Global Tool Management

Install standalone Python CLI tools that are available system-wide:

# Install a global tool
rye install httpie
rye install black
rye install cookiecutter

# List installed global tools
rye tools list

# Uninstall
rye tools uninstall httpie

Each global tool gets its own isolated environment, so they never conflict with project dependencies. This replaces pipx entirely.

Comparison: Rye vs. uv vs. Poetry vs. PDM vs. pip-tools

Feature Rye uv Poetry PDM pip-tools
Python version management Yes Yes No No No
Virtual env management Automatic Automatic Automatic Automatic Manual
Dependency resolution Fast (uv) Very fast Moderate Fast Slow
Lockfile Yes Yes Yes Yes Yes
pyproject.toml Yes Yes Yes (custom) Yes No
Global tool install Yes Yes (uv tool) No No No
Script runner Yes Yes Yes Yes No
Build/publish Yes Yes (uv publish) Yes Yes No
Speed Fast Fastest Moderate Fast Slow
Maturity Growing Growing Mature Mature Mature
Config format Standard PEP 621 Standard PEP 621 Custom [tool.poetry] Standard PEP 621 requirements.in

Rye gives you the most complete project lifecycle management with a clean, opinionated workflow. It's the closest thing Python has to Cargo.

uv is the fastest and most flexible, especially for developers who want pip-compatible commands with modern resolver speed. It now covers most of Rye's surface area.

Poetry was the first tool to unify dependency management and packaging for Python. It has the largest user base and the most battle-tested workflow, but uses a non-standard configuration format and is slower than Rye and uv.

PDM follows PEP standards closely and was an early adopter of PEP 621 ([project] table). Good middle ground, but less well-known.

pip-tools is the minimal option -- it just generates pinned requirements.txt files from requirements.in. No project management, no virtual environment handling. Good for legacy projects that need lockfiles without a full tool migration.

Integration with CI/CD

GitHub Actions

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rye
        uses: evilmartians/setup-rye@v1
        with:
          version: 'latest'

      - name: Install dependencies
        run: rye sync --no-lock

      - name: Lint
        run: rye lint

      - name: Format check
        run: rye fmt --check

      - name: Type check
        run: rye run mypy src/

      - name: Test
        run: rye run pytest --tb=short

The --no-lock flag on rye sync skips lockfile regeneration and installs from the committed lockfile. This ensures CI uses exactly the same versions as development.

GitLab CI

test:
  image: python:3.12-slim
  before_script:
    - curl -sSf https://rye.astral.sh/get | RYE_INSTALL_OPTION="--yes" bash
    - export PATH="$HOME/.rye/shims:$PATH"
    - rye sync --no-lock
  script:
    - rye lint
    - rye fmt --check
    - rye run pytest

Docker

FROM python:3.12-slim AS builder

# Install rye
RUN curl -sSf https://rye.astral.sh/get | RYE_INSTALL_OPTION="--yes" bash
ENV PATH="/root/.rye/shims:$PATH"

WORKDIR /app
COPY pyproject.toml requirements.lock ./
RUN rye sync --no-dev --no-lock

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app/.venv .venv
COPY src/ src/
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "my_project"]

The multi-stage build keeps Rye out of the final image. Only the virtual environment and application code are copied forward.

Tips and Best Practices

Commit Your Lockfiles

Always commit requirements.lock and requirements-dev.lock. These are the files that ensure reproducible builds. Without them, rye sync resolves fresh versions every time, which can introduce subtle breakage.

Use rye sync as Your Single Command

Train your workflow around rye sync. After cloning a project, after pulling changes, after editing pyproject.toml -- just run rye sync. It handles everything: Python installation, virtual environment creation, dependency resolution, and package installation.

Pin Python Versions Explicitly

Don't rely on requires-python = ">= 3.12" alone. Use rye pin 3.12.4 to write a .python-version file that tells Rye exactly which interpreter to use. This prevents surprises when a new Python patch release changes behavior.

Structure Your Scripts

Use [tool.rye.scripts] instead of a Makefile or shell scripts for common tasks. Keep script definitions in pyproject.toml so new contributors discover them immediately. Prefix related scripts consistently:

[tool.rye.scripts]
dev = "flask run --debug"
test = "pytest tests/"
test-cov = "pytest tests/ --cov=src/"
lint = "ruff check src/ tests/"
lint-fix = "ruff check --fix src/ tests/"
format = "ruff format src/ tests/"

Workspace Support for Monorepos

Rye supports workspaces for managing multiple related packages in a single repository:

# pyproject.toml (workspace root)
[tool.rye.workspace]
members = ["packages/*"]

Each member package has its own pyproject.toml but shares a single lockfile and virtual environment. This is useful for monorepos where packages depend on each other.

Migrating from Poetry

If you're moving an existing Poetry project to Rye:

  1. Keep your pyproject.toml -- Rye reads the [project] table (PEP 621 format)
  2. If you have [tool.poetry.dependencies], convert them to the standard [project.dependencies] format
  3. Run rye sync to generate Rye's lockfiles
  4. Remove poetry.lock after verifying everything works
  5. Update CI scripts to use rye commands

The main change is configuration format. Poetry uses a custom [tool.poetry] section; Rye uses the standard [project] table defined in PEP 621.

Migrating from pip + requirements.txt

For legacy projects using requirements.txt:

# Initialize Rye in an existing directory
rye init --name my-project .

# Import existing requirements
rye add $(cat requirements.txt | grep -v '^#' | grep -v '^$' | tr '\n' ' ')

# Generate lockfiles
rye sync

After this, you can delete requirements.txt and manage everything through pyproject.toml.

When Rye Might Not Be the Right Choice

Rye isn't always the answer:

Conclusion

Rye represents what Python packaging should have been from the start: a single tool that manages the full lifecycle of a Python project without requiring you to understand the archaeological layers of Python's packaging history. It handles Python installations, virtual environments, dependency resolution, lockfiles, script running, and tool management through one coherent interface.

The Python packaging landscape is converging. Rye and uv are built on the same foundations (python-build-standalone, PEP 621, fast Rust-based resolvers) and share a vision of what modern Python development should look like. Whether you choose Rye for its project management workflow or uv for its speed and pip compatibility, you're moving toward the same future: fast, reproducible, and sane Python packaging.

For new projects, rye init and start building. The twenty-minute setup ritual is over.