Comprehensive Python backend development guide with CI/CD workflows, testing, documentation, and best practices using uv, flake8, mypy, pytest, and Sphinx.
This skill guide provides production-ready workflows, CI/CD pipelines, testing strategies, and documentation standards for Python backend development. It covers modern Python tooling including uv package management, flake8 linting, mypy type checking, pytest testing, and Sphinx documentation generation.
Backend developers working on Python projects who need:
Purpose: Enforce code style following PEP 8 standards
Configuration ():
.flake8[flake8]
max-line-length = 100
ignore = E203, E266, E501, W503
max-complexity = 10
exclude =
.git,
__pycache__,
build,
dist,
.venv,
venv,
.eggs,
*.egg-info
Run Command:
flake8 src/ tests/
CI Integration: See complete workflow in section 4.1
Purpose: Static type analysis for type safety
Configuration (in pyproject.toml):
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
Run Command:
mypy src/
CI Integration: See complete workflow in section 4.1
Purpose: Comprehensive test coverage with detailed reporting
Configuration (in pyproject.toml):
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = [
"--cov=src",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing",
"--cov-fail-under=80",
"--junitxml=test-results/junit.xml",
"-v"
]
Run Command:
pytest tests/
CI Integration: See complete workflow in section 4.1
Purpose: Generate API documentation with GitHub Pages deployment
Initial Setup:
# Install Sphinx
uv add --dev sphinx sphinx-rtd-theme
# Initialize Sphinx
cd docs
sphinx-quickstart --quiet --project="My Project" --author="Your Name" -v "1.0"
Build Command:
sphinx-build -b html docs/source docs/build/html
CI Integration: See complete workflow in section 4.2
# Initialize new project
uv init my-project
cd my-project
# Initialize in existing directory
uv init
# Add production dependency
uv add requests
# Add development dependency
uv add --dev pytest pytest-cov
# Add specific version
uv add "fastapi==0.109.0"
# Install all dependencies
uv sync
# Update dependencies
uv lock --upgrade
uv automatically manages uv.lock file for reproducible builds:
# Build distribution packages
uv build
# Creates:
# - dist/*.whl (wheel)
# - dist/*.tar.gz (source distribution)
[project]
name = "my-backend-project"
version = "1.0.0"
description = "A production-ready Python backend"
authors = [
{name = "Your Name", email = "[email protected]"}
]
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.109.0",
"uvicorn>=0.27.0",
"sqlalchemy>=2.0.25",
"pydantic>=2.5.0",
"pydantic-settings>=2.1.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
"flake8>=7.0.0",
"mypy>=1.8.0",
"sphinx>=7.2.6",
"sphinx-rtd-theme>=2.0.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
dev-dependencies = [
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
"flake8>=7.0.0",
"mypy>=1.8.0",
]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = [
"--cov=src",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing",
"--cov-fail-under=80",
"--junitxml=test-results/junit.xml",
"-v"
]
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
[tool.coverage.run]
source = ["src"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/site-packages/*",
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
from typing import Optional, List, Dict, Any, Union, Tuple
from datetime import datetime
def process_user_data(
user_id: int,
username: str,
email: Optional[str] = None,
tags: List[str] = None,
metadata: Dict[str, Any] = None
) -> Dict[str, Union[str, int, List[str]]]:
"""
Process user data and return formatted result.
:param user_id: Unique identifier for the user
:type user_id: int
:param username: Username for the account
:type username: str
:param email: Optional email address
:type email: Optional[str]
:param tags: List of user tags
:type tags: Optional[List[str]]
:param metadata: Additional metadata dictionary
:type metadata: Optional[Dict[str, Any]]
:return: Formatted user data dictionary
:rtype: Dict[str, Union[str, int, List[str]]]
:raises ValueError: If user_id is negative
:raises TypeError: If username is not a string
.. note::
This function validates all input parameters before processing.
.. warning::
Large metadata dictionaries may impact performance.
.. seealso::
:func:`validate_user_data` for validation logic
**Example Usage:**
.. code-block:: python
result = process_user_data(
user_id=123,
username="john_doe",
email="[email protected]",
tags=["admin", "verified"]
)
print(result)
# {'id': 123, 'name': 'john_doe', 'email': '[email protected]', 'tags': ['admin', 'verified']}
"""
if user_id < 0:
raise ValueError("user_id must be non-negative")
if not isinstance(username, str):
raise TypeError("username must be a string")
tags = tags or []
metadata = metadata or {}
return {
"id": user_id,
"name": username,
"email": email or "not_provided",
"tags": tags,
}
from typing import Optional, List
from datetime import datetime
class DatabaseConnection:
"""
Manages database connection lifecycle with context manager support.
This class provides a robust connection manager with automatic resource
cleanup and error handling.
:ivar host: Database host address
:vartype host: str
:ivar port: Database port number
:vartype port: int
:ivar database: Database name
:vartype database: str
:ivar connected: Connection status flag
:vartype connected: bool
:ivar connection_time: Timestamp of last connection
:vartype connection_time: Optional[datetime]
.. note::
Always use this class as a context manager to ensure proper cleanup.
.. warning::
Connections are not thread-safe. Create separate instances per thread.
**Example Usage:**
.. code-block:: python
with DatabaseConnection("localhost", 5432, "mydb") as conn:
result = conn.execute("SELECT * FROM users")
print(result)
"""
def __init__(self, host: str, port: int, database: str) -> None:
"""
Initialize database connection parameters.
:param host: Database server hostname or IP
:type host: str
:param port: Database server port
:type port: int
:param database: Target database name
:type database: str
:raises ValueError: If port is not in valid range (1-65535)
"""
if not 1 <= port <= 65535:
raise ValueError("Port must be between 1 and 65535")
self.host: str = host
self.port: int = port
self.database: str = database
self.connected: bool = False
self.connection_time: Optional[datetime] = None
def __enter__(self) -> "DatabaseConnection":
"""
Enter context manager and establish connection.
:return: The connection instance
:rtype: DatabaseConnection
:raises ConnectionError: If connection fails
"""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
"""
Exit context manager and close connection.
:param exc_type: Exception type if raised
:param exc_val: Exception value if raised
:param exc_tb: Exception traceback if raised
:return: False to propagate exceptions
:rtype: bool
"""
self.disconnect()
return False
def connect(self) -> None:
"""
Establish database connection.
:raises ConnectionError: If connection cannot be established
.. note::
Connection parameters are validated before attempting connection.
"""
# Connection logic here
self.connected = True
self.connection_time = datetime.now()
def disconnect(self) -> None:
"""
Close database connection and cleanup resources.
.. note::
Safe to call multiple times - idempotent operation.
"""
if self.connected:
# Cleanup logic here
self.connected = False
def execute(self, query: str) -> List[Dict[str, Any]]:
"""
Execute SQL query and return results.
:param query: SQL query string
:type query: str
:return: List of result dictionaries
:rtype: List[Dict[str, Any]]
:raises RuntimeError: If not connected
:raises ValueError: If query is empty
.. warning::
This method does not provide SQL injection protection.
Use parameterized queries in production.
"""
if not self.connected:
raise RuntimeError("Not connected to database")
if not query.strip():
raise ValueError("Query cannot be empty")
# Query execution logic here
return []