| name | python |
| description | Modern Python 3.13+ development standards and best practices. Use when writing, reviewing, or refactoring Python code. Triggers on Python file creation/editing, code reviews, architecture decisions, async patterns, type hints, testing strategies, or when user asks about Python best practices. |
Modern Python 3.13+ Standards
Tooling
- Package management:
uv only, never pip directly
- Linting/formatting:
ruff (replaces black, isort, flake8)
- Type checking:
ty from Astral (fast, integrated with ruff/uv ecosystem)
- Testing:
pytest with function-based tests only
Type System
def process(items: list[str], config: dict[str, int] | None = None) -> tuple[str, int]: ...
def fetch(url: str) -> bytes | None: ...
from typing import TypeVar, Generic, Protocol, ParamSpec
T = TypeVar("T")
P = ParamSpec("P")
class Repository(Protocol[T]):
def get(self, id: str) -> T | None: ...
def save(self, item: T) -> None: ...
from collections.abc import Callable
def retry(fn: Callable[P, T]) -> Callable[P, T]: ...
Prohibited: Any type, Optional[T], List/Dict/Tuple from typing module
Code Style
Naming
user_count = len(users)
is_authenticated = token.valid
max_retry_attempts = 3
def fetch_user_orders(user_id: str) -> list[Order]: ...
def validate_email_format(email: str) -> bool: ...
def send_welcome_notification(user: User) -> None: ...
class OrderProcessor: ...
class PaymentGateway: ...
is_active = user.status == "active"
has_permission = "admin" in user.roles
can_edit = document.owner_id == current_user.id
Self-Documenting Code
if u.s == 'p' or u.s == 'e' or (u.t and u.t > now):
def has_premium_access(user: User) -> bool:
return user.is_premium or user.is_enterprise or user.has_active_trial
if has_premium_access(user):
...
Function Design
def calculate_order_total(items: list[Item]) -> Decimal: ...
def apply_discount(total: Decimal, code: str) -> Decimal: ...
def format_price_display(amount: Decimal) -> str: ...
def process_order(order: Order) -> Result:
if not validate_inventory(order):
return Result(error="insufficient stock")
charge_result = charge_payment(order)
if not charge_result.success:
return Result(error=charge_result.message)
return fulfill_order(order)
Avoid Abbreviations
def calc_ttl_amt(tx_lst: list) -> float: ...
usr_mgr = UserMgr()
btn_clk_hndlr = lambda: ...
def calculate_total_amount(transactions: list[Transaction]) -> Decimal: ...
user_manager = UserManager()
handle_button_click = lambda: ...
No Global State
_db_connection = None
_config = {}
def get_user(user_id: str) -> User:
global _db_connection
return _db_connection.query(User).get(user_id)
def init():
global _db_connection, _config
_db_connection = create_connection()
_config = load_config()
@dataclass
class AppContext:
db: Database
config: Config
def get_user(ctx: AppContext, user_id: str) -> User:
return ctx.db.query(User).get(user_id)
def get_user(db: Database, user_id: str) -> User:
return db.query(User).get(user_id)
Why globals are harmful:
- Hidden dependencies make testing impossible without monkeypatching
- Execution order matters (init must run first)
- Concurrent code breaks with shared mutable state
- Refactoring becomes dangerous
Acceptable module-level constants:
DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3
API_VERSION = "v2"
Hard rules
Non-negotiable; the gate for marking any task complete. If a rule is violated the task is not done - refactor, re-test, then mark complete. This block is the source lifted into a plan's per-task Code-Quality gate.
Signatures:
- No function or method has 4+ parameters (
self/cls do not count). Past the budget, pass a @dataclass or TypedDict of options.
- A function returning 3+ values returns a
@dataclass/NamedTuple, not a bare tuple unpacked at call sites.
- Adjacent same-type parameters (
start: int, end: int) are a swap hazard - use a small dataclass or keyword-only params (*,).
- Public callables are fully type-annotated (params + return).
Visibility (private by default):
- Prefix internal names with a single underscore (
_helper, _State); the public API is the non-underscore names.
- Pin each module's public surface with
__all__; nothing outside it is API.
- Before making a new name public (no underscore), confirm an out-of-module caller; if none, underscore it.
Functions vs methods:
- Prefer free module-level functions for stateless logic. If a function only manipulates one class's internals (reaches into several of its attributes), make it a method on that class.
- Do not create a class just to hold one function - a module function is enough.
Comments / docstrings (default: none):
- No comments or docstrings unless the WHY is non-obvious; clear names over comments.
- Never restate WHAT self-evident code does.
Per-task gate (before marking a checkbox [x]):
ruff format clean, ruff check zero issues, type-check clean (mypy/pyright), pytest passes.
- Grep new code:
grep -nE 'def [^(]*\(([^)]*,){3,}' for 4+ params (drop self/cls); for each new public (non-underscore) name confirm a cross-module caller or a deliberate __all__ entry.
- Only after 1-2 pass: mark complete.
Code Patterns
Early Return (flat code)
def validate_user(user: User) -> ValidationResult:
if not user.email:
return ValidationResult(valid=False, error="missing email")
if not user.age or user.age < 18:
return ValidationResult(valid=False, error="invalid age")
return ValidationResult(valid=True)
Pattern Matching (3.10+)
def handle_event(event: Event) -> Response:
match event:
case ClickEvent(x=x, y=y):
return handle_click(x, y)
case KeyEvent(key=key) if key.isalpha():
return handle_alpha_key(key)
case KeyEvent(key="Enter"):
return handle_submit()
case _:
return Response(status="ignored")
Walrus Operator
valid_users = [u for u in users if (email := u.get("email")) and "@" in email]
if (match := pattern.search(text)) and match.group(1):
process(match.group(1))
Data Structures
from dataclasses import dataclass, field
@dataclass(slots=True, frozen=True, kw_only=True)
class Config:
host: str
port: int = 8080
tags: list[str] = field(default_factory=list)
Context Managers
from contextlib import contextmanager, asynccontextmanager
from collections.abc import Generator, AsyncGenerator
@contextmanager
def temp_directory() -> Generator[Path, None, None]:
path = Path(tempfile.mkdtemp())
try:
yield path
finally:
shutil.rmtree(path)
@asynccontextmanager
async def db_transaction() -> AsyncGenerator[Connection, None]:
conn = await pool.acquire()
try:
yield conn
await conn.commit()
except Exception:
await conn.rollback()
raise
finally:
await pool.release(conn)
Async Patterns
Fully Async Stack
import anyio
from httpx import AsyncClient
async def fetch_all(urls: list[str]) -> list[bytes]:
async with AsyncClient() as client:
async with anyio.create_task_group() as tg:
results: list[bytes] = []
async def fetch_one(url: str) -> None:
response = await client.get(url)
results.append(response.content)
for url in urls:
tg.start_soon(fetch_one, url)
return results
File I/O in Async Context
import anyio
async def read_config(path: Path) -> dict:
content = await anyio.Path(path).read_text()
return json.loads(content)
Prohibited: Synchronous I/O (open(), requests, time.sleep) inside async functions
Performance
O(1) Lookups Over Conditionals
def get_handler(event_type: str) -> Handler:
if event_type == "click":
return click_handler
elif event_type == "key":
return key_handler
elif event_type == "scroll":
return scroll_handler
return default_handler
HANDLERS: dict[str, Handler] = {
"click": click_handler,
"key": key_handler,
"scroll": scroll_handler,
}
def get_handler(event_type: str) -> Handler:
return HANDLERS.get(event_type, default_handler)
Generators for Large Data
from collections.abc import Iterator
def read_large_file(path: Path) -> Iterator[str]:
with path.open() as f:
for line in f:
yield line.strip()
Testing
import pytest
from collections.abc import AsyncGenerator
@pytest.fixture
async def db_conn() -> AsyncGenerator[Connection, None]:
conn = await create_test_connection()
yield conn
await conn.close()
async def test_user_creation(db_conn: Connection) -> None:
user = await create_user(db_conn, name="test")
assert user.id is not None
assert user.name == "test"
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
])
def test_uppercase(input: str, expected: str) -> None:
assert input.upper() == expected
Prohibited: Class-based tests, mocking internal services (use real objects/fixtures)
Error Handling
class ValidationError(Exception):
def __init__(self, field: str, message: str) -> None:
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
def parse_config(data: dict) -> Config:
try:
return Config(**data)
except KeyError as e:
raise ValidationError("config", f"missing field: {e}") from e
Rules:
- Catch specific exceptions, never bare
except:
- Chain exceptions with
from e
- No exception handling for impossible scenarios
Anti-Patterns to Avoid
- Inheritance for code reuse → Use composition
- Global state → Use dependency injection
- Magic numbers → Use named constants
- Getter/setter methods → Use
@property when needed
- f-strings in logging → Use structured logging:
logger.info("user created", extra={"user_id": user.id})
- Blocking I/O in async → Use anyio.Path, httpx AsyncClient
- Manual dependency management → Use lock files (uv.lock)
Imports
import json
from pathlib import Path
from collections.abc import Callable, Iterator
import httpx
from pydantic import BaseModel
from app.models import User
from app.services import UserService
Always at file top, never inside functions. Ruff auto-sorts.
Modern Features Summary (3.13+)
| Feature | Use |
|---|
list[T], dict[K,V] | Built-in generic syntax |
X | Y | Union types |
match/case | Pattern matching |
:= | Walrus operator |
@dataclass(slots=True) | Memory-efficient dataclasses |
typing.Self | Return type for methods |
type statement | Type aliases (3.12+) |
**kwargs: Unpack[TypedDict] | Typed kwargs (3.12+) |