Rye: Unified Python Project Management from the Creator of Flask
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.

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:
- pyenv manages Python interpreter installations
- venv/virtualenv creates isolated environments
- pip installs packages
- pip-tools generates locked dependency files
- setuptools/flit/hatchling build packages
- twine uploads to PyPI
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:
- Resolves all dependencies and writes
requirements.lockandrequirements-dev.lock - Creates or updates the
.venv/virtual environment - 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:
- Keep your
pyproject.toml-- Rye reads the[project]table (PEP 621 format) - If you have
[tool.poetry.dependencies], convert them to the standard[project.dependencies]format - Run
rye syncto generate Rye's lockfiles - Remove
poetry.lockafter verifying everything works - Update CI scripts to use
ryecommands
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:
- Large teams already standardized on Poetry -- migration costs may outweigh benefits. Wait for natural project turnover.
- Conda-based data science workflows -- Rye manages PyPI packages, not conda channels. If you depend on conda-forge for scientific libraries with complex C/Fortran dependencies, stick with conda or mamba.
- Minimal scripts that don't need a project -- for a single-file Python script,
uv run script.pywith inline dependencies is lighter than creating a full Rye project. - Environments where curl-installing tools is prohibited -- some enterprise environments lock down tool installation. Check if Rye is available through your organization's package channels.
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.