春江暮客

春江暮客的个人学习分享网站

2026 Practical Python Workflow: Replace pip, venv, and pipx with uv

2026-05-30 Technology
2026 Practical Python Workflow: Replace pip, venv, and pipx with uv

Many Python projects start with a simple flow: create a virtual environment, install dependencies, run a script. Once you have more projects, the same flow often becomes messy:

  1. Virtual environments are created in different places
  2. pip install changes are not reflected in requirements.txt
  3. Your local Python version differs from the server
  4. CLI tools such as ruff, pytest, and httpie get mixed into runtime dependencies
  5. New teammates do not know which commands to run after cloning the repo

This is where uv is useful. It puts Python versions, virtual environments, dependency resolution, lockfiles, script execution, and CLI tool management behind one command set. This article keeps the theory light and gives you a workflow you can copy directly.

By the end, you will know:

  1. How to install and verify uv
  2. How to create a Python project with a lockfile
  3. How to use uv run instead of manually activating virtual environments
  4. How to run single-file scripts
  5. How to use uvx for temporary CLI tools
  6. How to migrate an old requirements.txt project

When uv is worth using first

uv is a good first choice for these projects:

  1. Python script tools: crawlers, data processing, log analysis, automation
  2. Web projects: FastAPI, Django, Flask
  3. AI projects: LangChain, MCP Servers, model-calling scripts
  4. CLI-heavy projects that use ruff, pytest, mypy, or httpie
  5. Team projects where dependency versions need to be reproducible

If you have an old server script that almost never changes, you do not need to migrate immediately. A practical rule is simple: use uv for new projects, and migrate old projects the next time you touch their dependencies.

Method 1: Install uv and verify the environment

1. Install on macOS and Linux

The official installer is the most direct option:

curl -LsSf https://astral.sh/uv/install.sh | sh

After installation, reopen your terminal or reload your shell configuration:

source ~/.zshrc

If you use Bash, it is usually:

source ~/.bashrc

2. Install on Windows

Run this in PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

3. Verify the installation

uv --version
uv help

If you see the version and subcommand help, the installation is ready.

These are the commands worth remembering first:

uv init
uv add
uv remove
uv sync
uv run
uv lock
uvx

Method 2: Create a new Python project

The example below builds a small weather CLI. It calls a public API and prints a short city weather summary.

1. Initialize the project

uv init weather-cli
cd weather-cli

Check the generated files:

ls -la

You should see a structure similar to this:

.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

.python-version records the expected Python version for the project. pyproject.toml records project metadata and dependencies.

2. Pin a Python version

If you want the project to use Python 3.12, run:

uv python install 3.12
uv python pin 3.12

Then check:

cat .python-version

Expected output:

3.12

3. Add dependencies

This example uses httpx for HTTP requests and rich for better terminal output:

uv add httpx rich

The command updates two important files:

  1. pyproject.toml: direct dependencies
  2. uv.lock: the full locked dependency tree

Commit both files to version control.

Method 3: Run the project with uv run

When people first switch to uv, the biggest change is that they no longer need to manually run:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

In a uv project, run:

uv run python main.py

uv run checks whether the environment exists, syncs dependencies if needed, and then executes the command inside the project environment.

1. Write the example code

Replace main.py with this:

from __future__ import annotations

import sys

import httpx
from rich.console import Console
from rich.table import Table


console = Console()


def fetch_weather(city: str) -> dict:
    response = httpx.get(
        "https://geocoding-api.open-meteo.com/v1/search",
        params={"name": city, "count": 1, "language": "en", "format": "json"},
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()
    results = data.get("results", [])
    if not results:
        raise ValueError(f"city not found: {city}")

    location = results[0]
    weather = httpx.get(
        "https://api.open-meteo.com/v1/forecast",
        params={
            "latitude": location["latitude"],
            "longitude": location["longitude"],
            "current": "temperature_2m,wind_speed_10m",
        },
        timeout=10,
    )
    weather.raise_for_status()
    current = weather.json()["current"]
    return {
        "name": location["name"],
        "country": location.get("country", ""),
        "temperature": current["temperature_2m"],
        "wind": current["wind_speed_10m"],
    }


def main() -> None:
    city = sys.argv[1] if len(sys.argv) > 1 else "Shanghai"
    result = fetch_weather(city)

    table = Table(title=f"Weather: {result['name']}, {result['country']}")
    table.add_column("Metric")
    table.add_column("Value")
    table.add_row("Temperature", f"{result['temperature']} C")
    table.add_row("Wind", f"{result['wind']} km/h")
    console.print(table)


if __name__ == "__main__":
    main()

Run it:

uv run python main.py Shanghai

You should see a table with temperature and wind speed. The exact values will change with the API response.

2. Debug without activating the virtual environment

You can still inspect .venv:

ls -la .venv

But day-to-day development does not require manual activation. Use:

uv run python main.py Beijing
uv run python -m pip list

This reduces the common confusion of not knowing which virtual environment is active.

Method 4: Put development tools in dev dependencies

Runtime dependencies and development tools should be separate. For example, httpx is a runtime dependency, while ruff and pytest are development dependencies.

Add development dependencies:

uv add --dev ruff pytest

Run formatting and lint checks:

uv run ruff format .
uv run ruff check .

Create a small test:

mkdir -p tests
cat > tests/test_smoke.py <<'EOF'
from main import fetch_weather


def test_fetch_weather_shanghai():
    result = fetch_weather("Shanghai")
    assert result["name"]
    assert "temperature" in result
EOF

Run the tests:

uv run pytest

If you do not want tests to depend on a real network request, split fetch_weather into smaller functions and mock the HTTP layer. This example keeps the real request to stay short.

Method 5: Run temporary CLI tools with uvx

Some tools are only needed once and should not be added to project dependencies. For example, to temporarily format or check a directory:

uvx ruff check .

You can also run third-party CLIs temporarily:

uvx httpie --version
uvx pycowsay "hello uv"

If you use a tool every day, install it as a global tool:

uv tool install ruff
ruff --version

Use this simple rule:

  1. Required for project reproducibility: uv add --dev
  2. Needed once or rarely: uvx
  3. Used daily across many projects: uv tool install

Method 6: Migrate an existing requirements.txt project

Assume an old project looks like this:

old-project
├── app.py
└── requirements.txt

The requirements.txt file contains:

fastapi==0.115.0
uvicorn[standard]==0.30.6

1. Initialize inside the existing directory

Enter the project directory and run:

uv init --bare

--bare works well for existing projects because it does not generate a sample main.py.

2. Import dependencies

uv add -r requirements.txt

Then sync the environment:

uv sync

3. Update the start command

The old command might be:

source .venv/bin/activate
uvicorn app:app --reload

After migration, use:

uv run uvicorn app:app --reload

For deployment scripts, prefer uv sync --frozen so production uses the committed lockfile exactly:

uv sync --frozen
uv run uvicorn app:app --host 0.0.0.0 --port 8000

Validation: Confirm the project is reproducible

After the steps above, use this command set to check the project:

uv lock --check
uv sync --frozen
uv run python --version
uv run python main.py Shanghai

If these commands pass, the dependency declaration, lockfile, and run entry are in good shape.

Commit these files:

git add pyproject.toml uv.lock .python-version main.py README.md
git commit -m "Use uv for Python project workflow"

For team collaboration, a new teammate only needs:

git clone <repo-url>
cd weather-cli
uv sync
uv run python main.py Shanghai

Troubleshooting

1. uv command not found

The error may look like this:

zsh: command not found: uv

First reopen the terminal. If it still fails, check whether the installation directory is in PATH:

echo "$PATH"

On macOS and Linux, you can also rerun the installer and follow the shell configuration prompt.

2. uv add cannot find pyproject.toml

This usually means you are not inside the project directory.

Fix it:

cd weather-cli
uv add requests

If this is an existing project, initialize it first:

uv init --bare

3. Python version mismatch

Check the project requirement:

cat .python-version

Install and sync:

uv python install
uv sync

To explicitly switch to 3.12:

uv python install 3.12
uv python pin 3.12
uv sync

4. Lockfile and dependency declaration are out of sync

If uv lock --check fails, pyproject.toml and uv.lock do not match.

In development, update the lockfile:

uv lock

In production, do not regenerate the lockfile automatically. Let the deployment fail, fix it on a development machine, and commit the updated lockfile.

5. Not sure whether to use uv add or uv pip install

For normal project development, prefer:

uv add package-name

This updates both pyproject.toml and uv.lock.

uv pip install is better for compatibility with traditional pip workflows, such as maintaining an old environment temporarily. For new projects, build your workflow around uv init, uv add, uv sync, and uv run.

Summary

The most practical value of uv is not just fewer commands. It unifies the important parts of a Python project: creating the project, locking dependencies, syncing the environment, running commands, and managing tools. For personal projects, it reduces virtual environment and dependency-file confusion; for team projects, it makes a fresh machine much easier to reproduce.

For new projects, start with uv init. For existing projects, migrate in small steps with uv init --bare and uv add -r requirements.txt. Once pyproject.toml, uv.lock, and .python-version are committed, most daily work can flow through one command: uv run.

友情链接

其它