WikifitaGitHub live67e8de5
outro · infra/uv_ecosystem

uv Ecosystem — Package Manager Best Practices

Entrypoints, tool isolation, packaging hygiene, and anti-patterns for Python projects managed with uv. Distilled from Pokemon TCG MLX migration and other projects.

Baixar raw

uv Ecosystem — Best Practices

Dense reference for Python packaging and tooling via uv. Every rule here was learned the hard way.

The Golden Rule

If you are writing pip install, python script.py, or requirements.txtstop. Use uv.

If you are writing PYTHONPATH=.stop. Fix the project structure instead.

Derived from preferencias-tecnicas (the directive that mandates uv). Crystallized during the pokemon_tcg_mlx_migration project.

Core Principles

1. uv run Is the Universal Runner

Never invoke python or python3 directly. Always route through uv.

# Bad
python script.py
python3 -m scripts.bc.bc_train

# Good
uv run python script.py
uv run --env-file .env script.py
uv run my-command
uv run --with ruff ruff check .

uv resolves the venv, Python version, and dependencies from pyproject.toml automatically. Direct python bypasses all of that.

2. [project.scripts] Entry Points Over PYTHONPATH

PYTHONPATH=. is a code smell. It means the project structure is wrong. Fix it with proper packaging.

In pyproject.toml:

[project.scripts]
bc-train = "scripts.bc.bc_train:main"
evaluate = "scripts.evaluate:main"
build-dataset = "scripts.bc.build_bc_dataset:main"

Then:

uv run bc-train data/bc_data/bc_2026_07_21 --d-model 128
uv run evaluate -n 50

This works from any directory, no path hacking required. After editing pyproject.toml, always run uv sync to register the entry points.

3. uv tool install for Standalone CLI Tools

Global CLI tools (like kaggle, ruff, gh) should be installed via uv tool install, not pip install. This isolates them from the project venv.

uv tool install kaggle
uv tool install ruff

Each tool gets its own isolated environment. No venv pollution, no version conflicts.

4. uvx for One-Off Tool Runs

Run a tool without installing it permanently:

uvx ruff check .
uvx mypy src/
uvx pytest tests/

Equivalent to uv run --with <tool> <tool> but shorter. Use for tools you do not need continuously.

5. uv add vs uv pip install

OperationCommandEffect
Project dependencyuv add numpyModifies pyproject.toml + locks
Dev dependencyuv add --dev pytestAdds to [dependency-groups]
Temp installuv pip install numpyInstalled in venv only, no lock change
Install from lockuv syncInstalls everything from lockfile

Rule: for project dependencies, always uv add. uv pip install is for temporary experiments only.

6. uv sync After Changing pyproject.toml

Entry points, new deps, and version changes only take effect after:

uv sync

This is the step people forget. If uv run my-command says "command not found", you forgot to sync.

Anti-Patterns

PYTHONPATH Hacks

# BAD — this means the project is not a proper Python package
PYTHONPATH=. python scripts/bc/bc_train.py

Fix chain:

  1. Add __init__.py to every importable directory (scripts/, scripts/bc/, rl/, etc.)
  2. Add [project.scripts] entry point in pyproject.toml
  3. Use relative imports within the package
  4. Run via uv run <entrypoint>

Legitimate exception: Kaggle sandbox where __file__ is not defined. That is a runtime constraint, not a hack.

sys.path.insert(0, ...)

# BAD
import sys
sys.path.insert(0, os.path.dirname(__file__))

Same fix chain as PYTHONPATH. If you see this in code, the package structure is broken.

Direct python/python3 Invocation

# BAD
python3 -u scripts/bc/bc_train.py data/bc_data/bc_2026_07_21

# GOOD
uv run python -u scripts/bc/bc_train.py data/bc_data/bc_2026_07_21

Direct invocation bypasses the uv-managed venv and Python version.

Package Structure Checklist

Every Python directory that contains importable modules must have __init__.py:

project/
├── pyproject.toml
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── module_a.py
│       └── subpackage/
│           ├── __init__.py
│           └── module_b.py
└── scripts/
    ├── __init__.py      # if scripts are importable
    └── bc/
        ├── __init__.py
        └── bc_train.py

Directories without __init__.py are not packages. They cannot be imported. PYTHONPATH tricks do not make them proper packages; they just paper over the structural problem.

Subagent Instructions

When spawning subagents for Python tasks, explicitly instruct them to use uv run in every command. Example:

"Use uv run for every Python command in this project. Never invoke python or python3 directly."

This prevents subagents from falling back to system Python or activating the wrong venv.

pyproject.toml Template

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.0",
]

[project.optional-dependencies]
dev = ["pytest", "ruff"]

[project.scripts]
my-command = "mypackage.module:main"

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

Key points:

  • requires-python pins the minimum Python version
  • Entry points in [project.scripts]
  • Dev deps in [project.optional-dependencies] or [dependency-groups]
  • No setup.py, no setup.cfg, no requirements.txt

Checkpoint File Handling

For ML projects with large binary files (.npy, .pkl, .pt, .safetensors):

  • Do not track them in git (add to .gitignore)
  • Use uv for Python tooling only; binary data lives outside the package manager
  • For checkpoint loading/saving, prefer pickle (.pkl) over np.savez when working with MLX (avoids flatten/unflatten name mismatch issues)
  • Document checkpoint format in code comments or architecture docs

Related: pokemon_tcg_mlx_migration (checkpoint format decisions).

Quick Reference

TaskCommand
Run a scriptuv run script.py
Run a project commanduv run my-command
Run a one-off tooluvx ruff check .
Install a project depuv add numpy
Install a global CLIuv tool install kaggle
Sync after toml changeuv sync
Run with env fileuv run --env-file .env script.py
Run testsuv run pytest
Format codeuv run ruff format .
Lint codeuv run ruff check .

Related Pages