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.
Environments
These standards advises the use of uv to manage different versions of Python you have installed.
To create virtual environments call uv venv -p python3.13
from your project root directory. This will create a virtual environment with that specific python version in a folder called .venv
. This folder should be excluded in your .gitignore
file. For more information see Python virtual environment primer
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:
python def get_item(item_id: int, detail: bool = False) -> dict[str, str]: ...
- Annotate variables where the type is not immediately clear:python items: list[str] = []
- Optional types should be annotated with | None or Optional from typing:python 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.
python 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: ```python 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.
python 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: ```python 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
Exception
and be suffixed withError
.Example: ```python 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: ```python import os
MAX_RETRIES = 5 DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db") ```
-
Hard-coded configuration values should be avoided.