Python Rules
These rules come from app/rules/python/ in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in Python. Apply them when writing or
reviewing Python code.
Python Coding Style
Type Hints
- Type all public function signatures (parameters + return).
- Use
str | None(PEP 604) overOptional[str]on Python 3.10+. - Use
from __future__ import annotationsfor forward references. - Use
TypeAliasortype(3.12+) for complex type aliases. - Use
Protocolfor structural subtyping instead of ABCs where possible.
Naming
- snake_case: variables, functions, methods, modules.
- PascalCase: classes, type aliases, Protocols.
- UPPER_SNAKE: module-level constants.
- Prefix private:
_internal_helper. No double underscore unless name mangling needed. - Prefix unused:
_for intentionally unused variables.
Functions
- Prefer keyword arguments for functions with >2 params.
- Use
*to force keyword-only:def fetch(*, limit: int, offset: int). - Return early to reduce nesting. Avoid deep if/else chains.
- Use
@staticmethodonly for pure utility. Prefer module-level functions.
Imports
- Group: stdlib, third-party, local. Separated by blank lines.
- Use absolute imports. Relative imports only within packages.
- Never
from module import *. Be explicit. - Use
if TYPE_CHECKING:for import-only-for-types to avoid circular imports.
Data Structures
- Use
dataclassesfor plain data containers. - Use Pydantic
BaseModelfor validated data / API schemas. - Use
NamedTuplefor lightweight immutable records. - Use
Enumfor fixed sets of values. PreferStrEnumon 3.11+. - Prefer
dict/listliterals overdict()/list()constructors.
Modern Python
- Use f-strings for formatting. Never
.format()or%for new code. - Use
pathlib.Pathoveros.pathfor file operations. - Use
contextlib.suppress(KeyError)over bare try/except for simple cases. - Use walrus operator
:=when it genuinely improves readability. - Use
match/case(3.10+) for complex conditionals on structured data.
Tooling
- Formatter:
ruff formatorblack. No manual formatting. - Linter:
ruff check. Fix all errors before committing. - Type checker:
mypy --strictorpyrightin CI.
Python Frameworks
FastAPI
- Use Pydantic v2 models for request/response schemas.
- Use dependency injection (
Depends()) for shared logic (auth, DB sessions). - Use
APIRouterto organize routes by domain. - Return Pydantic models directly -- FastAPI handles serialization.
- Use
BackgroundTasksfor non-critical async work (emails, logging). - Use
lifespancontext manager for startup/shutdown (noton_event).
Django
- Use class-based views for CRUD, function-based for custom logic.
- Use
select_relatedandprefetch_relatedto prevent N+1 queries. - Use Django REST Framework serializers for API validation.
- Use Django ORM migrations. Never modify database schema manually.
- Use
transaction.atomic()for multi-model operations. - Use signals sparingly: prefer explicit service calls.
SQLAlchemy 2.0
- Use the 2.0-style with
select()statements, not legacyquery(). - Use
Mapped[type]annotations for typed column definitions. - Use
sessionmakerwithexpire_on_commit=Falsefor API responses. - Use
async_sessionmakerwithasyncpgfor async applications. - Always use
session.begin()context manager for transaction scope.
Pydantic v2
- Use
model_validator(mode="before")for cross-field validation. - Use
field_validatorfor single-field validation. - Use
model_config = ConfigDict(strict=True)for strict type coercion. - Use
Annotated[str, Field(min_length=1)]for reusable constrained types. - Use
model_dump(exclude_unset=True)for PATCH operations.
CLI (click / typer)
- Use Typer for new CLI tools (type-hint-driven, less boilerplate).
- Use
click.group()for multi-command CLIs. - Use
richfor formatted terminal output (tables, progress bars).
Task Queues
- Use Celery with Redis/RabbitMQ for background job processing.
- Use
arqfor lightweight async job queues. - Always set task timeouts. Never let tasks run indefinitely.
- Use idempotent tasks: safe to retry on failure.
Package Management
- Use
uvfor fast dependency resolution and virtual environments. - Use
pyproject.tomlfor all project configuration (no setup.py/setup.cfg). - Pin dependencies with lockfile (
uv.lock,poetry.lock).
Python Patterns
Error Handling
- Catch specific exceptions, never bare
except:orexcept Exception. - Use custom exception hierarchies:
class AppError(Exception)as base. - Add context when re-raising:
raise AppError("context") from original. - Use
contextlib.suppress()for expected, ignorable exceptions. - Log exceptions with
logger.exception("msg")to include traceback.
Context Managers
- Use
withfor any resource that needs cleanup (files, connections, locks). - Create custom context managers with
@contextmanagerdecorator. - Use
contextlib.AsyncExitStackfor dynamic async resource management. - Use
atexit.register()for process-level cleanup only.
Async
- Use
asynciofor I/O-bound concurrency. Usemultiprocessingfor CPU-bound. - Use
asyncio.gather()for concurrent independent operations. - Use
asyncio.TaskGroup(3.11+) for structured concurrency. - Never mix
asyncio.run()inside already-running event loops. - Use
async forandasync withfor streaming and resource patterns.
Dataclass Patterns
- Use
frozen=Truefor immutable value objects. - Use
field(default_factory=list)for mutable defaults, neverfield(default=[]). - Use
__post_init__for validation, not complex logic. - Use
slots=True(3.10+) for memory efficiency in high-volume objects.
Functional Patterns
- Use
functools.lru_cachefor pure function memoization. - Use
itertoolsfor efficient iteration (chain, islice, groupby). - Use generators (
yield) for lazy sequences and large data processing. - Prefer comprehensions over
map/filterwith lambdas. - Use
functools.partialto create specialized versions of functions.
Dependency Injection
- Use constructor injection: pass dependencies as
__init__params. - Use
Protocolclasses to define dependency interfaces. - Use factory functions to wire dependencies at application startup.
- Avoid global state and singletons. Use module-level instances if needed.
Anti-Patterns
- Mutable default arguments: use
Noneand create inside function. - Catching
Exceptionbroadly: masks bugs and interrupts. - Using
type()for type checking: useisinstance(). - Nested try/except: flatten with early returns or separate functions.
- Using
globalkeyword: pass state through parameters or classes.
Python Security
Input Validation
- Validate all input with Pydantic models at API boundaries.
- Use
constr,conint,conlistfor constrained types. - Never use
eval(),exec(), orcompile()with user input. - Never use
pickle.loads()on untrusted data (arbitrary code execution).
SQL Injection
- Use ORM query builders (SQLAlchemy, Django ORM) for all queries.
- For raw SQL, always use parameterized queries:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)). - Never use f-strings or
.format()to build SQL queries. - Use
text()with:paramsyntax in SQLAlchemy raw queries.
SSTI (Server-Side Template Injection)
- Use Jinja2 with autoescaping enabled:
Environment(autoescape=True). - Never render user input as a template string.
- Use
markupsafe.Markuponly for trusted HTML content.
Command Injection
- Never use
os.system()orsubprocess.run(shell=True)with user input. - Use
subprocess.run()with list arguments:subprocess.run(["ls", "-la", path]). - Use
shlex.quote()if shell=True is absolutely necessary.
Path Traversal
- Use
pathlib.Path.resolve()and verify the result is within allowed directory. - Never concatenate user input into file paths without validation.
- Use
os.path.commonpath()to verify path containment.
Secrets
- Use
secretsmodule for tokens:secrets.token_urlsafe(32). - Use
hashlib.scryptorbcryptfor password hashing. - Use
hmac.compare_digest()for constant-time secret comparison. - Load secrets from environment:
os.environ["SECRET_KEY"], never hardcode.
Dependencies
- Run
pip-auditorsafety checkin CI. - Use
uvorpip-compilefor reproducible dependency resolution. - Avoid installing packages with native extensions from untrusted sources.
- Pin all dependency versions. Review dependency updates carefully.
Deserialization
- Never deserialize untrusted data with
pickle,yaml.load(), ormarshal. - Use
yaml.safe_load()instead ofyaml.load(). - Use
json.loads()for untrusted data (safe by default). - Validate deserialized data with Pydantic before use.
Django-Specific
- Set
DEBUG = Falsein production. Never expose debug pages. - Use
django.utils.html.escape()for manual HTML escaping. - Use
CSRF_COOKIE_HTTPONLY = TrueandSESSION_COOKIE_SECURE = True. - Keep
SECRET_KEYunique per environment and out of version control.
Python Testing
Framework
- Use pytest as the default test framework. No unittest for new code.
- Use pytest-asyncio for async test functions.
- Use pytest-cov for coverage measurement.
- Use hypothesis for property-based testing on parsing/validation logic.
File Naming
- Test files:
test_*.pyintests/directory. - Conftest:
conftest.pyat each test directory level for shared fixtures. - Mirror source:
src/auth/service.py->tests/auth/test_service.py.
Fixtures
- Use
@pytest.fixturefor setup. Prefer fixtures over setup/teardown methods. - Scope fixtures appropriately:
function(default),module,session. - Use
yieldfixtures for setup + teardown:yield resource; cleanup(). - Use
tmp_pathfixture for temporary files, not manualtempfile. - Use
monkeypatchfor patching env vars, attributes, and dict items.
Parametrize
- Use
@pytest.mark.parametrizefor testing multiple inputs/outputs. - Use
pytest.param(..., id="descriptive_name")for readable test IDs. - Combine parametrize decorators for cross-product testing.
Mocking
- Use
unittest.mock.patchormonkeypatchfor dependency replacement. - Mock at the import location:
patch("myapp.service.http_client"). - Use
MagicMock(spec=ClassName)to get attribute checking. - Use
AsyncMockfor async functions. - Prefer dependency injection over patching when possible.
Markers
- Use
@pytest.mark.slowfor tests >1s. Exclude from default runs. - Use
@pytest.mark.integrationfor tests requiring external services. - Register all custom markers in
pyproject.tomlto avoid warnings.
Async Testing
- Use
@pytest.mark.anyioor@pytest.mark.asynciofor async tests. - Use
httpx.AsyncClientfor testing FastAPI/Starlette apps. - Use
aiosqliteor test containers for async database tests.
Configuration
- Configure pytest in
pyproject.tomlunder[tool.pytest.ini_options]. - Set
addopts = "--strict-markers -ra"for strict mode. - Set
testpaths = ["tests"]to avoid scanning the entire repo.