Python project workflow guidelines. Triggers: .py, pyproject.toml, uv, pip, pytest, Python.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Guidelines for working with Python projects across different package managers, code styles, and architectural patterns using modern tooling (uv, Python 3.9+).
| Task | Tool | Command |
|---|---|---|
| Lint | Ruff | uv run ruff check . --fix |
| Format | Ruff | uv run ruff format . |
| Type check | Mypy | uv run mypy src/ |
| Type check | Pyright | uv run pyright |
| Security | Bandit | uv run bandit -r src/ |
| Dead code | Vulture | uv run vulture src/ |
| Coverage | pytest-cov | uv run pytest --cov=src |
| Complexity | Radon | uv run radon cc src/ -a |
MUST NOT reference .venv paths manually (e.g., .venv/Scripts/python.exe or ../../../.venv/) - causes cross-platform issues and breaks on structure changes.
MUST use uv run python in uv-based projects (auto-finds venv, works cross-platform, no activation needed):
# BAD: ../../../.venv/Scripts/python.exe script.py
# GOOD: uv run python script.py
uv run python -m module.cli
Prefer shared root .venv unless isolation required (saves ~7GB per environment).
uv exclusively for modern Python projectsuv add <package>uv add --dev <package>uv add --group <group-name> <package> (e.g., notebook, docs)uv run python script.py or uv run pytestuv runuv run python in uv-based projectsuv sync before executing code in new projectsrequirements.txt and requirements-dev.txt.venv) and activate before operationspyproject.toml for project configurationUse -m flag when running modules as CLIs (tells Python to run module as script, not file):
# GOOD: uv run python -m module.cli
# BAD: uv run python module.cli # fails - treats as file path
uv run ruff check . --fixuv run ruff format .pyproject.toml under [tool.ruff]ruff.toml for standalone configurationpyproject.toml, .editorconfig).format() or % formattingCheck these files for style preferences:
pyproject.toml - Modern Python project configurationruff.toml - Ruff-specific configuration.editorconfig - Editor-agnostic style settingsfrom typing import Any
import pandas as pd
from pydantic import BaseModel
class DataModel(BaseModel):
"""Example data model with proper spacing."""
field_one: str
field_two: int
def process_data(input_data: list[dict[str, Any]]) -> pd.DataFrame:
"""
Process input data and return DataFrame.
Args:
input_data: List of dictionaries containing raw data
Returns:
Processed pandas DataFrame
"""
return pd.DataFrame(input_data)
list[str], dict[str, Any] (Python 3.9+)from typing import List, Dicttyping module for complex types: Union, Optional, Literal, Protocoldataclasses for simple data containers when Pydantic is overkillattrs for enhanced dataclasses if preferredfrom typing import Any, Protocol
from pydantic import BaseModel
class Repository(Protocol):
"""Protocol defining repository interface."""
def get(self, id: str) -> dict[str, Any] | None:
...
class User(BaseModel):
"""User model with validation."""
username: str
email: str
age: int | None = None
def fetch_user(repo: Repository, user_id: str) -> User | None:
"""Fetch user from repository with type safety."""
data = repo.get(user_id)
return User(**data) if data else None
UserService, DatabaseConnection)get_user_data, connection_pool)MAX_RETRIES, DEFAULT_TIMEOUT)_internal_method, _cache)MockComponent, HelperClass, UtilityFunction instead of TestComponentDNSRecordHandler → dns_record_handler.pyComponentFactory → component_factory.py"""Docstring text."""def calculate_compound_interest(
principal: float,
rate: float,
time: int,
compound_frequency: int = 1
) -> float:
"""
Calculate compound interest using the standard formula.
Args:
principal: Initial amount invested
rate: Annual interest rate as decimal (e.g., 0.05 for 5%)
time: Time period in years
compound_frequency: Times per year interest compounds (default: 1)
Returns:
Final amount after compound interest
Raises:
ValueError: If principal, rate, or time is negative
"""
if principal < 0 or rate < 0 or time < 0:
raise ValueError("Values must be non-negative")
# Using compound interest formula: A = P(1 + r/n)^(nt)
return principal * (1 + rate / compound_frequency) ** (compound_frequency * time)
logging module with structured loggingimport logging
logger = logging.getLogger(__name__)
def process_user_data(user_id: str) -> dict[str, Any]:
"""
Process user data with proper error handling.
Args:
user_id: Unique user identifier
Returns:
Processed user data dictionary
Raises:
ValueError: If user_id is empty or invalid format
UserNotFoundError: If user doesn't exist
"""
if not user_id or not user_id.strip():
raise ValueError("user_id cannot be empty")
try:
user = fetch_user(user_id)
if user is None:
raise UserNotFoundError(f"User {user_id} not found")
return process(user)
except DatabaseError as e:
logger.error(f"Database error processing user {user_id}: {e}")
raise
__init__.py in all packages__init__.py to control package exportsproject/
├── src/
│ └── app/
│ ├── __init__.py # Export main app components
│ ├── core/ # Core business logic
│ │ ├── __init__.py
│ │ ├── commands.py # Command DTOs
│ │ └── queries.py # Query DTOs
│ ├── services/ # Business services
│ │ ├── __init__.py
│ │ └── user_service.py
│ ├── repositories/ # Data access
│ │ ├── __init__.py
│ │ └── user_repository.py
│ ├── models/ # Data models
│ │ ├── __init__.py
│ │ └── user.py
│ └── handlers/ # Request handlers
│ ├── __init__.py
│ └── user_handler.py
├── tests/ # Test files
│ ├── __init__.py
│ ├── unit/
│ ├── integration/
│ └── fixtures/
├── pyproject.toml # Project configuration
└── README.md
from .models import Userfrom app.services import UserService.env filesos.getenv() with sensible defaults.env files to version controlfrom pydantic import BaseModel, Field
import os
class AppConfig(BaseModel):
"""Application configuration with validation."""
debug: bool = Field(default=False)
database_url: str = Field(...)
max_connections: int = Field(default=10, ge=1, le=100)
@classmethod
def from_env(cls) -> "AppConfig":
"""Load configuration from environment variables."""
return cls(
debug=os.getenv("DEBUG", "false").lower() == "true",
database_url=os.getenv("DATABASE_URL", ""),
max_connections=int(os.getenv("MAX_CONNECTIONS", "10"))
)
pathlib.Path for cross-platform path handlingos.path.expanduser('~/') for home directorieswith statement)from pathlib import Path
# Read file
config_path = Path.home() / '.config' / 'app.json'
if config_path.exists():
content = config_path.read_text(encoding='utf-8')
# Write file with context manager
output_path = Path('output.txt')
with output_path.open('w', encoding='utf-8') as f:
f.write('content')
tests/unit/, tests/integration/pytest.mark for test categorizationimport pytest
from app.services import UserService
@pytest.fixture
def user_service():
"""Provide UserService instance for tests."""
return UserService()
@pytest.fixture
def sample_user():
"""Provide sample user data."""
return {"id": "user123", "name": "John Doe"}
def test_get_user_success(user_service):
"""Test successful user retrieval."""
user = user_service.get_user("user123")
assert user is not None
assert user.id == "user123"
def test_get_user_not_found(user_service):
"""Test user not found raises appropriate exception."""
with pytest.raises(UserNotFoundError):
user_service.get_user("nonexistent")
def test_create_user(user_service, sample_user):
"""Test user creation with fixture data."""
user = user_service.create_user(sample_user)
assert user.name == "John Doe"
app/ package using __init__.py exports/health, /status)core/commands.py - Command DTOscore/queries.py - Query DTOshandlers/command_handler.py - Command processinghandlers/query_handler.py - Query processingasync def for I/O-bound operationsawait for async callsasyncio for concurrent operationsimport asyncio
async def fetch_data(url: str) -> dict:
"""Fetch data asynchronously."""
# Use aiohttp or similar for actual HTTP calls
await asyncio.sleep(1)
return {"status": "success"}
async def main():
"""Run multiple async operations concurrently."""
results = await asyncio.gather(
fetch_data("url1"),
fetch_data("url2")
)
return results
pyproject.toml - Modern Python project (PEP 518)requirements.txt - Pip dependenciessetup.py - Package definition (legacy or hybrid)Pipfile - Pipenv projectspoetry.lock - Poetry projectsuv.lock - UV projectspyproject.toml or pytest.iniuv run pytest, poetry run pytest, etc.django-workflowfastapi-workflowflask-workflowdatabase-workflowPackage managers:
uv run, uv sync, uv add, uv add --devpoetry run, poetry install, poetry addpip install, python -m pipKey rules:
uv run python (MUST NOT use manual .venv paths)-m flag for module CLIspyproject.toml for configNote: For project-specific Python patterns, check .claude/CLAUDE.md in the project directory.