Quick Reference
uv Command Purpose
uv init my-project
Create new project
uv add <package>
Add dependency
uv add --dev <package>
Add dev dependency
uv sync
Install all dependencies
uv run <command>
Run in virtual env
uv lock --upgrade
Update all deps
uv python install 3.13
Install Python version
Tool Command Speed
uv uv add requests
10-100x faster
pip pip install requests
Baseline
ruff Command Purpose
ruff check .
Lint code
ruff check --fix .
Auto-fix issues
ruff format .
Format code
Project Layout Recommended
src layout src/my_package/
Tests tests/
Config pyproject.toml
When to Use This Skill
Use for project setup and dependencies:
-
Starting new Python projects
-
Setting up uv for fast package management
-
Configuring pyproject.toml
-
Setting up linting with ruff
-
Publishing packages to PyPI
Related skills:
-
For CI/CD: see python-github-actions
-
For testing: see python-testing
-
For type hints config: see python-type-hints
Python Package Management (2025)
Overview
Modern Python package management centers around uv (the fast Rust-based tool), pip , and pyproject.toml . This guide covers best practices for dependency management, virtual environments, and project configuration.
uv - The Modern Package Manager
Why uv?
-
10-100x faster than pip (written in Rust)
-
Replaces pip, pip-tools, pipx, poetry, pyenv, virtualenv
-
Automatic virtual environment management
-
Lockfile support for reproducibility
-
~200x faster venv creation
Installation
macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell)
irm https://astral.sh/uv/install.ps1 | iex
pip (works everywhere)
pip install uv
Homebrew
brew install uv
Quick Start
Create new project
uv init my-project cd my-project
Add dependencies
uv add requests fastapi pydantic
Add dev dependencies
uv add --dev pytest ruff mypy
Sync environment (install all dependencies)
uv sync
Run commands in the environment
uv run python main.py uv run pytest
Basic Commands
Package management
uv add <package> # Add dependency uv add <package>==1.0.0 # Specific version uv add --dev <package> # Dev dependency uv remove <package> # Remove dependency uv sync # Sync environment with lockfile
Virtual environments
uv venv # Create .venv uv venv --python 3.12 # Specific Python version uv venv my-env # Named environment
pip compatibility
uv pip install <package> # Install (faster pip) uv pip install -r requirements.txt uv pip freeze > requirements.txt uv pip compile pyproject.toml -o requirements.txt
Python management
uv python install 3.13 # Install Python version uv python list # List installed versions uv python pin 3.12 # Pin project Python version
Running
uv run <command> # Run in virtual env uv run --with httpx python # Run with temporary package
pyproject.toml with uv
[project] name = "my-project" version = "0.1.0" description = "My Python project" readme = "README.md" requires-python = ">=3.11" license = {text = "MIT"} authors = [ {name = "Your Name", email = "you@example.com"} ] dependencies = [ "fastapi>=0.100.0", "pydantic>=2.0.0", "httpx>=0.25.0", ]
[project.optional-dependencies] dev = [ "pytest>=8.0.0", "ruff>=0.1.0", "mypy>=1.8.0", ]
[project.scripts] my-cli = "my_project.cli:main"
[build-system] requires = ["hatchling"] build-backend = "hatchling.build"
[tool.uv] dev-dependencies = [ "pytest>=8.0.0", "ruff>=0.1.0", "mypy>=1.8.0", ]
uv.lock
Lockfile is auto-generated and should be committed
Contains exact versions for reproducibility
Update all dependencies
uv lock --upgrade
Update specific package
uv lock --upgrade-package requests
Install from lockfile only
uv sync --frozen
Project Structure
Recommended: src Layout
my-project/ ├── src/ │ └── my_package/ │ ├── init.py │ ├── main.py │ ├── models.py │ └── utils/ │ ├── init.py │ └── helpers.py ├── tests/ │ ├── init.py │ ├── conftest.py │ ├── test_main.py │ └── test_models.py ├── docs/ │ └── index.md ├── pyproject.toml ├── uv.lock ├── README.md ├── LICENSE └── .gitignore
Why src Layout?
-
Prevents import confusion - Can't accidentally import from project root
-
Forces installation - Must install package to test
-
Cleaner distributions - Only package code in wheels
-
Semantic clarity - Clear separation of code, tests, docs
Flat Layout (for simpler projects)
my-project/ ├── my_package/ │ ├── init.py │ └── main.py ├── tests/ │ └── test_main.py ├── pyproject.toml └── README.md
pyproject.toml Complete Reference
[project] name = "my-project" version = "1.0.0" description = "A comprehensive Python project" readme = "README.md" requires-python = ">=3.11" license = {text = "MIT"} keywords = ["python", "example", "package"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] authors = [ {name = "Your Name", email = "you@example.com"} ] maintainers = [ {name = "Maintainer", email = "maintainer@example.com"} ]
dependencies = [ "fastapi>=0.100.0", "pydantic>=2.0.0", "sqlalchemy>=2.0.0", ]
[project.optional-dependencies] dev = [ "pytest>=8.0.0", "pytest-cov>=4.0.0", "ruff>=0.1.0", "mypy>=1.8.0", "pre-commit>=3.0.0", ] docs = [ "mkdocs>=1.5.0", "mkdocs-material>=9.0.0", ] all = ["my-project[dev,docs]"]
[project.scripts] my-cli = "my_package.cli:main"
[project.entry-points."my_package.plugins"] plugin1 = "my_package.plugins:Plugin1"
[project.urls] Homepage = "https://github.com/user/my-project" Documentation = "https://my-project.readthedocs.io" Repository = "https://github.com/user/my-project" Changelog = "https://github.com/user/my-project/blob/main/CHANGELOG.md"
[build-system] requires = ["hatchling"] build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel] packages = ["src/my_package"]
Ruff configuration
[tool.ruff] target-version = "py311" line-length = 88 src = ["src", "tests"]
[tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # Pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade "ARG", # flake8-unused-arguments "SIM", # flake8-simplify ] ignore = [ "E501", # line too long (handled by formatter) ]
[tool.ruff.lint.isort] known-first-party = ["my_package"]
[tool.ruff.format] quote-style = "double" indent-style = "space"
Mypy configuration
[tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_ignores = true disallow_untyped_defs = true plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false
Pytest configuration
[tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_.py"] python_functions = ["test_"] addopts = [ "-ra", "-q", "--strict-markers", "--cov=src/my_package", "--cov-report=term-missing", ]
Coverage configuration
[tool.coverage.run] branch = true source = ["src/my_package"] omit = ["/tests/", "*/init.py"]
[tool.coverage.report] exclude_lines = [ "pragma: no cover", "def repr", "raise NotImplementedError", "if TYPE_CHECKING:", ]
Ruff - Linting and Formatting
Why Ruff?
-
10-100x faster than Flake8, Black combined
-
Single tool for linting AND formatting
-
800+ built-in rules
-
Auto-fix capabilities
-
Written in Rust (by Astral, same as uv)
Basic Usage
Linting
ruff check . # Check for issues ruff check --fix . # Auto-fix issues ruff check --watch . # Watch mode
Formatting
ruff format . # Format all files ruff format --check . # Check formatting
Both
ruff check --fix . && ruff format .
Configuration
pyproject.toml
[tool.ruff] target-version = "py311" line-length = 88 src = ["src", "tests"]
[tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # Pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade "ARG", # flake8-unused-arguments "SIM", # flake8-simplify "TCH", # flake8-type-checking "PTH", # flake8-use-pathlib "ERA", # eradicate (commented code) "PL", # Pylint "PERF", # Perflint "RUF", # Ruff-specific rules ] ignore = [ "E501", # line too long (handled by formatter) "PLR0913", # too many arguments ]
Per-file ignores
[tool.ruff.lint.per-file-ignores] "tests/**/*.py" = ["S101"] # Allow assert in tests "init.py" = ["F401"] # Allow unused imports
[tool.ruff.lint.isort] known-first-party = ["my_package"] force-single-line = true
[tool.ruff.format] quote-style = "double" indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto"
Pre-commit Integration
.pre-commit-config.yaml
repos:
-
repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.4.0 hooks:
- id: ruff args: [--fix]
- id: ruff-format
-
repo: https://github.com/pre-commit/mirrors-mypy rev: v1.9.0 hooks:
- id: mypy
additional_dependencies:
- pydantic
- types-requests
- id: mypy
additional_dependencies:
pip and requirements.txt
When to Use pip
-
Production environments requiring stability
-
Legacy projects
-
Simple scripts without complex dependencies
requirements.txt Best Practices
requirements.txt - Pinned versions for production
fastapi==0.109.0 pydantic==2.5.3 sqlalchemy==2.0.25 httpx==0.26.0
requirements-dev.txt
-r requirements.txt pytest==8.0.0 ruff==0.1.14 mypy==1.8.0
Generating requirements.txt
From uv
uv pip compile pyproject.toml -o requirements.txt uv pip compile pyproject.toml --extra dev -o requirements-dev.txt
From pip-tools
pip-compile pyproject.toml -o requirements.txt pip-compile pyproject.toml --extra dev -o requirements-dev.txt
Freeze current environment (not recommended for reproducibility)
pip freeze > requirements.txt
Virtual Environment Best Practices
Location
Recommended: Project-local .venv
project/ ├── .venv/ # Virtual environment here ├── src/ ├── tests/ └── pyproject.toml
uv creates .venv by default
uv venv
Explicit location
uv venv .venv python -m venv .venv
.gitignore
Virtual environments
.venv/ venv/ ENV/ env/
Python
pycache/ *.py[cod] *$py.class *.so .Python
Distribution
build/ dist/ *.egg-info/
IDE
.idea/ .vscode/ *.swp *.swo
Testing
.pytest_cache/ .coverage htmlcov/ .mypy_cache/
Ruff
.ruff_cache/
Activation (when needed)
Usually not needed with uv - just use uv run
Linux/macOS
source .venv/bin/activate
Windows (PowerShell)
.venv\Scripts\Activate.ps1
Windows (cmd)
.venv\Scripts\activate.bat
Deactivate
deactivate
Publishing Packages
Build and Publish
Build with uv
uv build
Publish to PyPI
uv publish
Or with twine
pip install twine twine upload dist/*
Test PyPI first
twine upload --repository testpypi dist/*
Version Management
pyproject.toml - dynamic version from init.py
[project] dynamic = ["version"]
[tool.hatch.version] path = "src/my_package/init.py"
src/my_package/init.py
version = "1.0.0"