Modern Python Development (Pyhd) #
This guide establishes the standard development workflow, project structure, and code quality gates for Python projects using uv for fast environment and dependency management, ruff for linting and formatting, and pytest for test verification.
1. Project Scaffolding & Architecture #
Prefer the standard src/ layout for Python packages to avoid accidental imports of uninstalled local code and ensure test parity with installed packages.
Standard Project Layout #
my-project/
โโโ .venv/ # Managed isolated virtual environment (gitignored)
โโโ pyproject.toml # Unified project metadata, dependencies, and tool config
โโโ README.md # Project documentation
โโโ src/
โ โโโ my_package/
โ โโโ __init__.py # Package export root
โ โโโ core.py # Domain logic
โ โโโ py.typed # PEP 561 marker for type checkers
โโโ tests/
โโโ conftest.py # Shared test fixtures and pytest hooks
โโโ unit/ # Fast, isolated unit tests
โโโ integration/ # Multi-module integration tests
Dependency Management with uv #
Manage dependencies deterministically using uv commands:
# Initialize a new application or library project
uv init --app my-project # For standalone applications
uv init --lib my-library # For reusable packages with src/ layout
# Add production dependencies
uv add requests pydantic
# Add development / testing dependencies
uv add --dev ruff pytest pytest-cov
# Synchronize local .venv with pyproject.toml and lockfile
uv sync
# Update lockfile without modifying dependencies
uv lock
Self-Contained Scripts (PEP 723) #
For standalone automation or utility scripts, declare dependencies directly inline using PEP 723 script metadata:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "httpx",
# "rich",
# ]
# ///
import httpx
from rich import print
response = httpx.get("https://httpbin.org/get")
print(response.json())
Execute single-file scripts with automated dependency isolation:
uv run script.py
2. Plan-Validate-Execute (Refactoring) #
When performing complex or multi-file Python refactoring:
- Plan: Scan the workspace with
grep_searchto identify all call-sites, import statements, and references to target symbols. - Validate: Verify compatibility of planned changes against dependent modules and type signatures.
- Execute: Modify files incrementally, running the Code Verification Loop after each change.
3. Code Verification Loop #
After modifying any Python file, execute this verification loop before completing tasks:
graph TD
A[Start: Modify Code] --> B[Lint: uv run ruff check --fix]
B --> C{Lint Clean?}
C -->|No: Manual fixes needed| D[Fix Violations]
D --> B
C -->|Yes| E[Format: uv run ruff format]
E --> F[Test: uv run pytest]
F --> G{Tests Pass?}
G -->|No| D
G -->|Yes| H[Loop Complete]
Step-by-Step Verification Commands #
- Lint & Auto-Fix: Resolve syntax, style, and import sorting issues:
uv run ruff check --fix
- Format Code: Ensure consistent code style:
uv run ruff format
- Run Unit & Integration Tests: Verify zero regressions:
# Run all tests
uv run pytest
# Run targeted test file or function
uv run pytest tests/unit/test_core.py -k "test_process_data"
4. Virtual Environment Isolation #
uv run (e.g. uv run python script.py, uv run ruff check, uv run pytest). This guarantees execution within the local .venv/ interpreter, preventing system package contamination and missing import errors.
5. Gotchas & Edge Cases #
- Circular Imports in Type Annotations: When module
Aimports typeBpurely for type annotations, prevent runtime import cycles by usingtyping.TYPE_CHECKING:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from my_package.service import DatabaseService
- Mutable Default Arguments: Never use mutable objects (
list,dict,set) as default parameter values. UseNoneas a sentinel:
# โ Bug: Shared list across calls
def append_item(item: str, target: list[str] = []) -> list[str]:
target.append(item)
return target
# โ
Correct: Fresh container per invocation
def append_item(item: str, target: list[str] | None = None) -> list[str]:
if target is None:
target = []
target.append(item)
return target
- Explicit Exception Chaining: When catching and re-raising errors as domain-specific exceptions, preserve root-cause tracebacks using
from err:
try:
data = parse_payload(raw)
except json.JSONDecodeError as err:
raise ValidationError("Malformed JSON payload") from err
- Modern Python 3.10+ Typing: Use built-in generics (
list[str],dict[str, Any]) and union syntax (str | None,int | float). Avoid importing legacy types fromtyping(List,Dict,Union,Optional).
6. ๐ Progressive Disclosure & References #
- Python Best Practices Guide:
references/best_practices.mdโ Idiomatic Python patterns, type annotations, Ruff configuration, and pytest fixture guidelines.