git clone https://github.com/jieni777/opencode-config-backup
T=$(mktemp -d) && git clone --depth=1 https://github.com/jieni777/opencode-config-backup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/python-engineer" ~/.claude/skills/jieni777-opencode-config-backup-python-engineer && rm -rf "$T"
skills/python-engineer/SKILL.mdPython Engineer Skill
Comprehensive Python development guidance for modern, production-ready applications. Covers best practices, tooling, frameworks, and productivity patterns.
Metadata
- Name: python-engineer
- Description: Expert Python development with modern best practices, type hints, async/await, testing, packaging, and framework selection guidance
- License: MIT
Design Philosophy
Purpose
To provide authoritative guidance for Python development that balances:
- Code quality (PEP 8, type safety, testing)
- Developer productivity (tooling, automation, modern patterns)
- Performance (async, profiling, optimization)
- Maintainability (clear structure, documentation, modularity)
Tone
- Modern: Python 3.9+ features, contemporary tooling (uv, ruff, setuptools_scm)
- Practical: Real-world patterns over theoretical purity
- Opinionated: Clear defaults with justification for alternatives
Constraints
ALWAYS:
- Use type hints for public APIs
- Follow PEP 8 (enforced by black/ruff)
- Write tests with pytest
- Use
for new projects (10-100x faster than pip)uv - Prefer
layout for packagessrc - Use
(PEP 621 standard)pyproject.toml
NEVER:
- Suppress type errors with
(fix the code)# type: ignore - Use mutable default arguments (use
and set default)None - Ignore test coverage (aim for >80%)
- Hardcode secrets (use environment variables/Pydantic Settings)
- Mix
with logging (use proper logging)print()
Differentiation
| Aspect | This Skill | Others |
|---|---|---|
| Python Version | 3.9+ (current best practices) | Legacy 2.7/3.6 support |
| Dependency Management | uv (fast, modern) | poetry/pip/venv (slower) |
| Linting | ruff (Python-written, 10-100x faster) | black+flake8 (separate tools) |
| Testing | pytest with modern plugins | unittest/legacy approaches |
| Packaging | PEP 621 pyproject.toml + setuptools_scm | setup.py/setup.cfg |
| Type Checking | mypy strict mode for libraries | Optional/relaxed |
Python Best Practices
Code Style & Formatting
Primary Tools:
- ruff: Linting and formatting (replaces black, flake8, isort)
- 10-100x faster than alternatives
- Compatible with black 99% of the time
- Write in Rust for performance
- Configuration in
:pyproject.toml
[tool.ruff] line-length = 88 target-version = "py39" [tool.ruff.lint] select = ["E", "F", "I", "N", "W"] ignore = ["E501"] # Let black handle line length [tool.ruff.format] quote-style = "double" indent-style = "space"
Pre-commit hooks (
.pre-commit-config.yaml):
repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.0 hooks: - id: ruff args: [--fix] - id: ruff-format
Type Hints
Always use type hints for:
- Function parameters and return values
- Class attributes
- Public API surfaces
- Complex data structures
from typing import Optional, List, Dict, Any from dataclasses import dataclass from collections.abc import Sequence # Preferred: Use dataclasses for structured data @dataclass class User: id: int name: str email: str roles: Sequence[str] # Function with comprehensive type hints async def fetch_users( *, limit: Optional[int] = None, filters: Dict[str, Any] | None = None, ) -> List[User]: """Fetch users from database. Args: limit: Maximum number of users to return filters: Key-value filters for query Returns: List of User objects """ filters = filters or {} # Implementation return []
Mypy configuration (
pyproject.toml):
[tool.mypy] python_version = "3.9" strict = true # Libraries: strict mode warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true
For applications, use
warn_return_any = true without full strict mode.
Testing with pytest
Project structure:
myproject/ ├── src/ │ └── myproject/ │ ├── __init__.py │ ├── main.py │ └── utils.py ├── tests/ │ ├── conftest.py │ ├── test_main.py │ └── test_utils.py
Example test (
tests/test_utils.py):
import pytest from myproject.utils import calculate_sum def test_calculate_sum_basic(): assert calculate_sum([1, 2, 3]) == 6 @pytest.mark.parametrize("input,expected", [ ([], 0), ([1], 1), ([1, 2, 3], 6), ]) def test_calculate_sum_various(input, expected): assert calculate_sum(input) == expected @pytest.mark.asyncio async def test_async_function(): result = await async_operation() assert result is not None
pytest configuration (
pyproject.toml):
[tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = [ "--strict-markers", "--strict-config", "--cov=src/myproject", "--cov-report=term-missing", "--cov-report=html", ]
Async/await Patterns
When to use async:
- I/O-bound operations (HTTP requests, database queries)
- Network operations (websockets, streams)
- Concurrent tasks without CPU bottlenecks
Example patterns:
import asyncio from typing import AsyncIterable # Parallel async operations async def fetch_multiple(urls: Sequence[str]) -> List[Response]: """Fetch multiple URLs concurrently.""" async with httpx.AsyncClient() as client: tasks = [client.get(url) for url in urls] responses = await asyncio.gather(*tasks, return_exceptions=True) return responses # Async context manager class DatabaseConnection: async def __aenter__(self): self.conn = await asyncpg.connect(...) return self.conn async def __aexit__(self, exc_type, exc, tb): await self.conn.close() # Async generator async def stream_results(query: str) -> AsyncIterable[Dict]: """Stream database results row by row.""" async with DatabaseConnection() as conn: async for row in conn.cursor(query): yield dict(row)
Avoid async for:
- CPU-intensive operations (use multiprocessing)
- Simple synchronous code that doesn't benefit from concurrency
Project Structure
Modern src
Layout (Recommended for Packages)
srcmyproject/ ├── pyproject.toml # PEP 621 configuration ├── README.md ├── LICENSE ├── .gitignore ├── .pre-commit-config.yaml ├── src/ │ └── myproject/ │ ├── __init__.py │ ├── main.py │ └── utils.py ├── tests/ │ ├── conftest.py │ ├── __init__.py │ └── test_main.py ├── docs/ │ └── index.md └── .github/ └── workflows/ └── ci.yml
Why
layout:src
- Prevents import confusion during development
- Ensures tests import from installed package, not source tree
- Better packaging behavior with tools like
setuptools_scm
pyproject.toml
(PEP 621 Standard)
pyproject.tomlComplete example:
[build-system] requires = ["hatchling", "setuptools-scm"] build-backend = "hatchling.build" [project] name = "myproject" dynamic = ["version"] description = "A modern Python project" readme = "README.md" requires-python = ">=3.9" license = {text = "MIT"} authors = [ {name = "Author Name", email = "author@example.com"}, ] keywords = ["python", "example"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = [ "httpx>=0.25.0", "pydantic>=2.0.0", ] [project.optional-dependencies] dev = [ "pytest>=7.0.0", "pytest-cov>=4.0.0", "pytest-asyncio>=0.21.0", "mypy>=1.0.0", "ruff>=0.1.0", "pre-commit>=3.0.0", ] docs = [ "mkdocs>=1.5.0", "mkdocs-material>=9.0.0", ] [project.scripts] myproject-cli = "myproject.main:cli" [project.urls] Homepage = "https://github.com/user/myproject" Documentation = "https://myproject.readthedocs.io" Repository = "https://github.com/user/myproject" Issues = "https://github.com/user/myproject/issues" [tool.setuptools_scm] write_to = "src/myproject/_version.py" [tool.ruff] line-length = 88 target-version = "py39" [tool.ruff.lint] select = ["E", "F", "I", "N", "W"] ignore = ["E501"] [tool.mypy] python_version = "3.9" strict = true warn_return_any = true warn_unused_configs = true [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = [ "--strict-markers", "--cov=src/myproject", "--cov-report=term-missing", ]
Dependency Management with uv
uvInstallation:
# Using pip (one-time) pip install uv # Or using the official installer curl -LsSf https://astral.sh/uv/install.sh | sh
Commands:
# Create new project uv init myproject # Add dependencies uv add httpx pydantic # Add dev dependencies uv add --dev pytest pytest-cov mypy ruff # Run commands in virtual environment uv run pytest uv run myproject-cli # Sync dependencies (create/update venv) uv sync # Update dependencies uv lock --upgrade
Why
:uv
- Written in Rust (10-100x faster than pip)
- Drop-in replacement for pip/pip-tools
- Built-in dependency resolution (like poetry)
- Works with existing
requirements.txt
Framework Selection
Web Development
| Framework | Use Case | Key Features |
|---|---|---|
| FastAPI | APIs, microservices | Async, auto docs, Pydantic validation, fast |
| Django | Full-stack apps | ORM, admin, authentication, batteries included |
| Flask | Simple microservices | Minimal, flexible, easy to learn |
Choose FastAPI when:
- Building REST/GraphQL APIs
- Need async I/O performance
- Want automatic OpenAPI docs
- Rapid development with type safety
Choose Django when:
- Building full-stack web applications
- Need built-in admin interface
- Require robust authentication/permissions
- Want ORM and database migrations out of the box
Choose Flask when:
- Building simple microservices
- Need maximum flexibility
- Learning web frameworks
FastAPI best practices (
main.py):
from fastapi import FastAPI, Depends, HTTPException, status from pydantic import BaseModel, Field from typing import List, Optional app = FastAPI( title="My API", description="API documentation", version="1.0.0", ) # Pydantic models for request/response class UserCreate(BaseModel): name: str = Field(..., min_length=1, max_length=100) email: str = Field(..., regex=r"^[^@]+@[^@]+\.[^@]+$") class User(BaseModel): id: int name: str email: str # Dependency injection async def get_db(): """Database session dependency.""" async with async_session() as session: yield session # Routes with proper status codes @app.post("/users", response_model=User, status_code=status.HTTP_201_CREATED) async def create_user( user: UserCreate, db: AsyncSession = Depends(get_db), ) -> User: """Create a new user.""" # Implementation return user @app.get("/users/{user_id}", response_model=User) async def get_user( user_id: int, db: AsyncSession = Depends(get_db), ) -> User: """Get user by ID.""" user = await db.get(User, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) return user @app.get("/users", response_model=List[User]) async def list_users( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), ) -> List[User]: """List users with pagination.""" return await db.execute(select(User).offset(skip).limit(limit))
Database
SQLAlchemy 2.0 with async support:
from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import declarative_base, sessionmaker # Async engine DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/db" engine = create_async_engine(DATABASE_URL, echo=True) async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) name = Column(String, nullable=False) email = Column(String, unique=True, nullable=False) # Async CRUD operations async def create_user(user_data: dict) -> User: async with async_session() as session: user = User(**user_data) session.add(user) await session.commit() await session.refresh(user) return user async def get_user(user_id: int) -> Optional[User]: async with async_session() as session: return await session.get(User, user_id)
Alembic migrations:
# Initialize migrations alembic init alembic # Create migration alembic revision --autogenerate -m "Add users table" # Apply migrations alembic upgrade head
Configuration Management
Pydantic Settings (
config.py):
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, ) # Application settings app_name: str = "My App" debug: bool = False # Database database_url: str = Field(..., env="DATABASE_URL") # Security secret_key: str = Field(..., env="SECRET_KEY") jwt_algorithm: str = "HS256" # External services api_key: Optional[str] = None settings = Settings()
Usage (
.env file):
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/db SECRET_KEY=your-secret-key-here DEBUG=false
Logging
Structured logging with structlog (
logging.py):
import structlog import logging def configure_logging(debug: bool = False) -> None: """Configure structured logging.""" level = logging.DEBUG if debug else logging.INFO # Standard library logging logging.basicConfig( format="%(message)s", level=level, ) # Structlog configuration structlog.configure( processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.StackInfoRenderer(), structlog.dev.set_exc_info, structlog.processors.TimeStamper(fmt="iso"), structlog.processors.JSONRenderer() if not debug else structlog.dev.ConsoleRenderer(), ], wrapper_class=structlog.make_filtering_bound_logger(level), context_class=dict, logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=True, ) # Usage logger = structlog.get_logger(__name__) logger.info("User created", user_id=123, email="user@example.com")
CLI Development
Typer for Modern CLIs
Example CLI (
cli.py):
import typer from typing import Optional app = typer.Typer() @app.command() def hello( name: str = typer.Argument(..., help="Name to greet"), uppercase: bool = typer.Option(False, "--upper", "-u", help="Uppercase output"), count: int = typer.Option(1, "--count", "-c", help="Number of times to greet"), ) -> None: """Greet someone.""" greeting = f"Hello, {name}!" if uppercase: greeting = greeting.upper() for _ in range(count): typer.echo(greeting) if __name__ == "__main__": app()
Usage:
python cli.py World --upper --count 3 # Output: HELLO, WORLD! # HELLO, WORLD! # HELLO, WORLD!
Entry Points
Define in
:pyproject.toml
[project.scripts] myproject-cli = "myproject.cli:app"
After installation:
myproject-cli World --upper
Package Development & Distribution
Publishing to PyPI
Build with hatch/uv:
# Install build tools uv pip install build twine # Build package uv run build # Check distribution twine check dist/* # Upload to PyPI twine upload dist/*
Versioning with
:setuptools_scm
[build-system] requires = ["hatchling", "setuptools_scm"] build-backend = "hatchling.build" [tool.setuptools_scm] write_to = "src/myproject/_version.py"
Version automatically determined from:
- Latest git tag (e.g.,
)v1.0.0 - Number of commits since tag
- Commit hash for dev versions
CI/CD with GitHub Actions
Complete workflow (
.github/workflows/ci.yml):
name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up uv uses: astral-sh/setup-uv@v1 with: version: "latest" - name: Set up Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} - name: Install dependencies run: uv sync --dev - name: Run linters run: | uv run ruff check . uv run ruff format --check . - name: Run type checks run: uv run mypy src - name: Run tests run: uv run pytest --cov=src/myproject --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Build wheels uses: pypa/cibuildwheel@v2 env: CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* - uses: actions/upload-artifact@v4 with: name: wheels path: wheelhouse/*.whl
Documentation
Docstring Conventions
Google style (recommended):
def calculate_discount( price: float, discount_rate: float, quantity: int = 1, ) -> float: """Calculate the discounted price for an item. Args: price: The original price of the item. discount_rate: The discount rate as a decimal (e.g., 0.2 for 20%). quantity: The number of items. Defaults to 1. Returns: The discounted total price. Raises: ValueError: If price is negative or discount_rate is not between 0 and 1. Examples: >>> calculate_discount(100.0, 0.2, 2) 160.0 """ if price < 0: raise ValueError("Price must be non-negative") if not 0 <= discount_rate <= 1: raise ValueError("Discount rate must be between 0 and 1") return price * (1 - discount_rate) * quantity
MkDocs Documentation
Configuration (
mkdocs.yml):
site_name: My Project site_description: Modern Python project documentation site_url: https://myproject.readthedocs.io theme: name: material palette: - scheme: default primary: indigo accent: indigo toggle: icon: material/brightness-7 name: Switch to dark mode - scheme: slate primary: indigo accent: indigo toggle: icon: material/brightness-4 name: Switch to light mode plugins: - search - mkdocstrings: handlers: python: options: show_source: true show_root_heading: true show_root_full_path: false markdown_extensions: - pymdownx.highlight: anchor_linenums: true - pymdownx.superfences nav: - Home: index.md - Installation: installation.md - Usage: usage.md - API Reference: api/
Anti-Patterns & Common Mistakes
Code Quality Anti-Patterns
❌ Bad: Mutable default arguments
def process_items(items: List = []): # Mutates across calls! items.append("processed") return items
✅ Good: Use
None as default
from typing import Optional, List def process_items(items: Optional[List] = None) -> List: if items is None: items = [] items.append("processed") return items
❌ Bad: Empty except blocks
try: risky_operation() except: # Swallows ALL errors including KeyboardInterrupt! pass
✅ Good: Specific exception handling
try: risky_operation() except ValueError as e: logger.error("Invalid value", error=str(e)) raise except Exception as e: logger.exception("Unexpected error") raise
❌ Bad: Using
print() for logging
def process_data(data): print(f"Processing: {data}") # No timestamps, no levels, can't disable # ... process print(f"Done: {data}")
✅ Good: Proper logging
import logging logger = logging.getLogger(__name__) def process_data(data): logger.info("Processing data", data=data) # ... process logger.info("Processing complete", data=data)
❌ Bad: Suppressing type errors
def process(item: Any) -> str: # Avoid Any! return item.do_something() # mypy error: Item has no attribute
✅ Good: Fix type errors or use proper typing
from typing import Protocol class Processable(Protocol): def do_something(self) -> str: ... def process(item: Processable) -> str: return item.do_something() # Type-safe!
Performance Anti-Patterns
❌ Bad: String concatenation in loops
def build_string(items: List[str]) -> str: result = "" for item in items: # O(n²) - creates new string each iteration result += item return result
✅ Good: Use
join()
def build_string(items: List[str]) -> str: return "".join(items) # O(n)
❌ Bad: Synchronous HTTP calls in async function
async def fetch_data(urls: List[str]) -> List[Dict]: results = [] for url in urls: # Blocks the event loop! response = requests.get(url) results.append(response.json()) return results
✅ Good: Async HTTP client
import httpx async def fetch_data(urls: List[str]) -> List[Dict]: async with httpx.AsyncClient() as client: tasks = [client.get(url) for url in urls] responses = await asyncio.gather(*tasks) return [r.json() for r in responses]
Security Anti-Patterns
❌ Bad: Hardcoded secrets
API_KEY = "sk-1234567890abcdef" # Never commit secrets! def fetch_data(): requests.get("https://api.example.com/data", params={"key": API_KEY})
✅ Good: Environment variables + Pydantic Settings
from pydantic_settings import BaseSettings class Settings(BaseSettings): api_key: str # Loaded from API_KEY env var class Config: env_file = ".env" settings = Settings() def fetch_data(): requests.get( "https://api.example.com/data", params={"key": settings.api_key}, )
❌ Bad: SQL injection with f-strings
def get_user(username: str): cursor.execute(f"SELECT * FROM users WHERE name = '{username}'") # Injection!
✅ Good: Parameterized queries
def get_user(username: str): cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
Reference Resources
Official Documentation
- Python Language: https://docs.python.org/3/
- PEP 8 Style Guide: https://peps.python.org/pep-0008/
- PEP 621 pyproject.toml: https://peps.python.org/pep-0621/
- Type Hints: https://docs.python.org/3/library/typing.html
Modern Tooling
- uv: https://github.com/astral-sh/uv (Fast Python package manager)
- ruff: https://docs.astral.sh/ruff/ (Fast linter/formatter)
- pytest: https://docs.pytest.org/
- mypy: https://mypy.readthedocs.io/
- FastAPI: https://fastapi.tiangolo.com/
- SQLAlchemy: https://docs.sqlalchemy.org/en/20/
Best Practices
- The Hitchhiker's Guide to Python: https://docs.python-guide.org/
- Real Python: https://realpython.com/
- Effective Python: https://effectivepython.com/
- Python Packaging Authority: https://www.pypa.io/
Example Projects
- FastAPI: https://github.com/tiangolo/fastapi
- pydantic: https://github.com/pydantic/pydantic
- SQLAlchemy: https://github.com/sqlalchemy/sqlalchemy
- Typer: https://github.com/fastapi/typer
Quick Start Example
Project Setup
# 1. Initialize project with uv uv init myproject cd myproject # 2. Add dependencies uv add httpx pydantic pydantic-settings # 3. Add dev dependencies uv add --dev pytest pytest-cov pytest-asyncio mypy ruff pre-commit # 4. Create project structure mkdir -p src/myproject tests # 5. Set up pre-commit hooks uv run pre-commit install
Main Application (src/myproject/main.py
)
src/myproject/main.pyfrom fastapi import FastAPI from pydantic import BaseModel import structlog from config import settings app = FastAPI(title="My API", version="1.0.0") logger = structlog.get_logger(__name__) class Item(BaseModel): name: str price: float @app.get("/") async def root() -> dict: """Root endpoint.""" logger.info("Root endpoint called") return {"message": "Hello World"} @app.post("/items", status_code=201) async def create_item(item: Item) -> Item: """Create a new item.""" logger.info("Creating item", item=item) return item
Configuration (src/myproject/config.py
)
src/myproject/config.pyfrom pydantic_settings import BaseSettings class Settings(BaseSettings): app_name: str = "My App" debug: bool = False class Config: env_file = ".env" settings = Settings()
Test (tests/test_main.py
)
tests/test_main.pyimport pytest from fastapi.testclient import TestClient from myproject.main import app client = TestClient(app) def test_root(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World"} def test_create_item(): item = {"name": "Test Item", "price": 9.99} response = client.post("/items", json=item) assert response.status_code == 201 assert response.json() == item
Run Project
# Run tests uv run pytest # Run API uv run uvicorn myproject.main:app --reload # Type check uv run mypy src # Format/lint uv run ruff check --fix . uv run ruff format .
Integration with Other Skills
This skill provides comprehensive Python development guidance and can be integrated with other skills in the OpenCode ecosystem:
- gtk-ui-ux-engineer: Use Python for backend APIs that serve GTK applications
- frontend-ui-ux-engineer: Create Python-based REST APIs for frontend consumption
- document-writer: Generate API documentation from Python docstrings
- rails-basecamp-engineer: Apply Python patterns for microservices alongside Rails monoliths
The skill is designed to be invoked automatically when:
- Working with
files.py - Managing
orpyproject.tomlrequirements.txt - Running Python-related commands (
,pytest
,uv
)ruff - Setting up Python development environments
When to Use This Skill
Use this skill when:
- Creating new Python projects or modules
- Setting up Python development environments
- Writing Python code with type hints and async patterns
- Choosing frameworks (FastAPI, Django, Flask)
- Packaging and distributing Python libraries
- Setting up testing, linting, and CI/CD
- Implementing configuration, logging, and error handling
Do NOT use this skill for:
- JavaScript/TypeScript development (use frontend-ui-ux-engineer)
- GTK UI development (use gtk-ui-ux-engineer)
- Ruby/Rails development (use rails-basecamp-engineer)
- Generic system administration (use appropriate domain skills)
Skill Compatibility
Minimum Python Version: 3.9 Recommended Python Version: 3.11+ (for latest features and performance) Compatible Tools: uv, ruff, pytest, mypy, FastAPI, Django 5+, SQLAlchemy 2.0+