Python Standards
This document defines the internal development standards for building backend services in Python (FastAPI) within Defra. It adheres strictly to the UK Government Digital Service (GDS) Python style guide and PEP 8, ensuring code is clear, consistent, and maintainable across all AI services (using tools like LangChain, LangGraph, etc.). All developers must follow these conventions when writing Python code for backend services.
Note: Python should ONLY be used for creating backend services related to AI or data science. For frontend services, use Node.js using the following Node.js and Javascript Defra standards.
Package Management
These standards mandate using uv for all Python project management, including dependency resolution, version pinning, and virtual environment handling. All new Python projects must be created using uv init and follow the standard project structure it generates.
Virtual Environments
uv manages virtual environments automatically—they are created transparently when you run uv sync or uv run. No manual environment setup is required.
pyproject.toml
All projects must use pyproject.toml for declaring dependencies and project metadata. This file is automatically generated and managed by uv init.
Dependency Management
- Pin dependencies to exact versions using
==specifiers to prevent unexpected version drift:
dependencies = [
"fastapi==0.104.1",
"requests==2.31.0",
]
-
Commit
uv.lockto version control for reproducible builds. Always useuv sync --lockedin CI/automated builds to ensure the lock file matchespyproject.toml(equivalent tonpm ci). -
Separate dependencies and dev dependencies using
[project.optional-dependencies]or[dependency-groups]. -
Use an automated dependency checker such as Dependabot or
uv pip auditto monitor for security vulnerabilities and keep dependencies up to date. -
Before adding third-party packages, vet them according to the package vetting guide.
pyproject.toml security settings
Create the following configuration in pyproject.toml to implement supply-chain security controls:
[tool.uv]
exclude-newer = "1 week"
| Setting | Purpose |
|---|---|
exclude-newer = "1 week" |
Refuses to resolve packages published fewer than 7 days ago. Provides a window to detect package takeover or typosquatting attacks before they reach your codebase. |
exclude-newer-package |
Per-package override of the quarantine window. Set to the RFC 3339 timestamp of the specific release needed — use this when a critical vulnerability fix must be resolved before the quarantine expires. |
Example with a critical patch override:
[tool.uv]
exclude-newer = "1 week"
exclude-newer-package = { requests = "2026-05-10T00:00:00Z" }
When using exclude-newer-package to bypass the quarantine for a critical security patch, you must closely examine the package for signs of supply chain compromise before adding it to your project:
- Verify the package on the official registry (PyPI) and check the release notes for legitimacy
- Review the package source repository—look for unusual activity, unexpected commits, or maintainer changes
- Check package download statistics for anomalies
- If possible, compare the package hash against official sources
- Review package dependencies for suspicious additions
Remove the override once the package ages past the global exclude-newer window.
Linting
Ruff
These standards advises the use of the Ruff command line checker as an all in one formatter, linter, codestyle and complexity checker.
Function Annotations and Typing
- All public functions must include type annotations for parameters and return types.
- Use standard PEP 484 typing syntax.
- Function definitions should format parameters and return types as follows:
def get_item(item_id: int, detail: bool = False) -> dict[str, str]:
...
- Annotate variables where the type is not immediately clear:
items: list[str] = []
- Optional types should be annotated with | None or Optional from typing:
def get_user(user_id: int | None = None) -> dict[str, str] | None:
...
- There should be no spaces before the colon and exactly one space after.
def get_mapping() -> dict[str, int]:
return {'a': 1, 'b': 2}
Naming Conventions
- Variables and functions: Use snake_case.
- Classes: Use PascalCase.
- Constants: Use UPPER_CASE.
Example:
MAX_RETRIES = 3
def calculate_total(items: list[int]) -> int:
return sum(items)
class DataProcessor:
pass
- Private members should start with a single underscore (_).
- Exception class names should end in Error.
class ValidationError(Exception):
pass
Import Style
-
Imports must be grouped in the following order:
-
Standard library imports
-
Third-party imports
-
Local application imports
-
-
Each group must be separated by one blank line.
Example:
import os
import sys
from fastapi import FastAPI
import requests
from app.models import User
from app.services import user_service
- Imports should be one per line.
- Absolute imports should be used.
- Wildcard imports (
from module import *) should not be used.
Error Handling
- Specific exceptions should be caught rather than using a bare
except:. - When raising exceptions, include an informative error message.
- Exceptions that represent a domain-specific error should subclass
Exceptionand be suffixed withError.
Example:
from fastapi import HTTPException
def fetch_data(url: str) -> str:
try:
response = requests.get(url)
response.raise_for_status()
except requests.RequestException as exc:
raise HTTPException(status_code=500, detail=str(exc))
return response.text
File and Module Structure
- Modules and packages must use short, all-lowercase names. Underscores can be used if necessary.
- Each module must have a single, clear responsibility.
Example project structure:
app/
├── main.py
├── api/
│ ├── users.py
│ └── items.py
├── models/
│ └── user.py
├── services/
│ └── user_service.py
├── utils/
│ └── helpers.py
├── config.py
Constants and Configuration
- Constants must be defined using UPPER_CASE.
- Configuration values should be loaded from environment variables where possible.
Example:
import os
MAX_RETRIES = 5
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
- Hard-coded configuration values should be avoided.
Significant changes
Package Management section and exclude-newer security settings added 13 May 2026.