Docstring Standards Guide¶
Comprehensive guide for writing Google-style docstrings across all Python code in Dashtam, covering modules, classes, functions, tests, and fixtures with practical examples and best practices.
Overview¶
This guide provides comprehensive instructions for writing Google-style docstrings across all Python code in Dashtam, covering modules, classes, functions, tests, and fixtures with practical examples and best practices.
What You'll Learn¶
- How to write Google-style docstrings for all Python code types
- Module, class, function, and test documentation patterns
- FastAPI endpoint documentation requirements
- Pytest fixture documentation standards
- Common patterns and anti-patterns to avoid
- How to verify docstring quality with linting tools
When to Use This Guide¶
Use this guide when:
- Writing new Python code (modules, classes, functions)
- Writing tests and pytest fixtures
- Implementing FastAPI endpoints
- Reviewing pull requests for documentation quality
- Updating existing code to meet documentation standards
Why Google-Style Docstrings¶
The Dashtam project uses Google-style docstrings as mandated in WARP.md for the following reasons:
- Readability: Clean, natural language format that's easy to scan
- Consistency: Industry-standard format used by major Python projects
- Tooling Support: Compatible with Sphinx, MkDocs, mkdocstrings, and IDEs
- Maintainability: Clear structure makes documentation updates straightforward
- Onboarding: New developers can understand code behavior without reading implementation
Google-Style Quick Reference:
"""One-line summary ending with period.
Longer description paragraph providing more context. Can span
multiple lines and include multiple paragraphs.
Args:
param1: Description of param1 (type optional if annotated)
param2: Description of param2
Returns:
Description of return value and type
Raises:
ErrorType: Description of when this error is raised
Note:
Any additional notes or warnings about usage
Example:
Optional example usage
"""
Official Resources:
- Google Python Style Guide - Docstrings
- PEP 257 - Docstring Conventions
- WARP.md - Project-specific docstring requirements
Prerequisites¶
Before starting, ensure you have:
- Python 3.13+ development environment
- Familiarity with Python type hints
- Understanding of Google-style docstring format
- Ruff linter configured in project
Required Tools:
- Ruff - For linting and docstring validation
- Python 3.13+ - With type hints support
- IDE with docstring preview (VSCode, PyCharm)
Required Knowledge:
- Familiarity with Python syntax and structure
- Understanding of classes, functions, and methods
- Basic knowledge of pytest for test documentation
- Familiarity with FastAPI for endpoint documentation
Step-by-Step Instructions¶
Step 1: Write Module-Level Docstrings¶
Every Python module MUST have a module-level docstring at the top of the file (after imports header comments):
"""User authentication service.
Provides secure user authentication using JWT tokens with refresh token
rotation. Implements password hashing, token generation/validation, and
email verification flows.
This module coordinates between PasswordService, JWTService, and EmailService
to provide complete user authentication capabilities including:
- User registration with email verification
- Login with JWT access + refresh tokens
- Token refresh with rotation
- Password reset flow
- Account lockout on failed attempts
Security Features:
- Bcrypt password hashing (12 rounds)
- JWT access tokens (30 min expiry)
- Opaque refresh tokens (30 days expiry)
- Email verification required for login
- Account lockout after 10 failed attempts
Note:
All methods are async and require AsyncSession for database operations.
Token hashing uses bcrypt for security (not reversible encryption).
"""
Required Elements:
- One-line summary
- Blank line
- Detailed description of module purpose
- List of key components/classes if applicable
- Optional sections: Security Features, Architecture, Dependencies, Note
Step 2: Write Class Docstrings¶
All classes MUST have comprehensive docstrings explaining their purpose, responsibilities, and usage.
Service Classes¶
class AuthService:
"""User authentication and authorization service.
Orchestrates user authentication flows including registration, login,
token management, email verification, and password resets. Coordinates
between database operations, password hashing, JWT creation, and email
notifications.
Attributes:
session: AsyncSession for database operations
password_service: Service for password hashing and validation
jwt_service: Service for JWT token creation and validation
email_service: Service for sending verification/reset emails
Security:
- All passwords hashed with bcrypt (12 rounds)
- Refresh tokens hashed before storage (one-way)
- Account lockout after 10 failed login attempts
- Email verification required before login
Example:
>>> auth_service = AuthService(session, password_svc, jwt_svc, email_svc)
>>> user = await auth_service.register_user("user@example.com", "SecurePass123!")
>>> tokens = await auth_service.login("user@example.com", "SecurePass123!")
Note:
This service is async and all methods require await.
Use get_auth_service() dependency for FastAPI endpoints.
"""
Model Classes (SQLModel/Pydantic)¶
class User(SQLModel, table=True):
"""User model representing authenticated users.
Stores user account information including authentication credentials,
profile data, and account status. Users must verify their email before
gaining full access to the platform.
Attributes:
id: Unique user identifier (UUID, primary key)
email: User's email address (unique, required for login)
password_hash: Bcrypt-hashed password (never stored in plaintext)
email_verified: Whether email has been verified (required for login)
failed_login_attempts: Counter for account lockout (resets on success)
locked_until: Timestamp when account lockout expires (nullable)
created_at: Account creation timestamp (UTC, timezone-aware)
updated_at: Last modification timestamp (UTC, auto-updated)
deleted_at: Soft delete timestamp (nullable, for audit trail)
Relationships:
providers: List of connected financial providers (one-to-many)
refresh_tokens: Active refresh tokens for this user (one-to-many)
Security:
- Password stored as bcrypt hash (12 rounds, ~300ms compute)
- Email must be verified before login allowed
- Account locks for 1 hour after 10 failed attempts
Note:
All datetime fields are timezone-aware (TIMESTAMPTZ in PostgreSQL).
Soft deletes preserve data for audit/compliance requirements.
"""
Step 3: Write Function/Method Docstrings¶
All public functions and methods MUST have comprehensive docstrings.
Standard Function Pattern¶
async def create_user(
self,
email: str,
password: str,
session: AsyncSession
) -> User:
"""Create a new user account with email verification.
Registers a new user with hashed password and sends email verification.
User cannot login until email is verified. Validates password strength
and checks for duplicate email addresses.
Args:
email: User's email address (must be unique)
password: Plain text password (will be hashed with bcrypt)
session: AsyncSession for database operations
Returns:
User: Created user instance with id and hashed password
Raises:
ValueError: If email already exists or password too weak
HTTPException: If email service fails to send verification
Note:
Password must meet complexity requirements:
- Minimum 8 characters
- At least one uppercase, lowercase, digit, special character
Email verification token expires in 24 hours.
Example:
>>> user = await auth_service.create_user(
... "user@example.com",
... "SecurePass123!",
... session
... )
>>> print(f"User {user.id} created, verification email sent")
"""
Required Sections:
- Summary (one line)
- Description (detailed explanation)
- Args (all parameters with types and descriptions)
- Returns (return value type and description)
- Raises (all possible exceptions)
- Optional: Note, Example, Warning
Step 4: Write FastAPI Endpoint Docstrings¶
FastAPI endpoint functions require special documentation patterns:
@router.post("/auth/login", response_model=LoginResponse)
async def login(
credentials: LoginRequest,
auth_service: AuthService = Depends(get_auth_service)
) -> LoginResponse:
"""Authenticate user and return JWT tokens.
Validates user credentials and returns access + refresh tokens if successful.
Tracks failed login attempts and enforces account lockout policy.
Request Body:
- email: User's email address
- password: User's password (plaintext, transmitted over HTTPS)
- device_info: Optional device information for session tracking
Response:
- access_token: JWT access token (30 min expiry)
- refresh_token: Opaque refresh token (30 days expiry)
- token_type: Always "bearer"
- user: User profile data (id, email)
Status Codes:
- 200: Login successful, tokens returned
- 400: Invalid request format
- 401: Invalid credentials or email not verified
- 429: Too many failed attempts, account locked
Args:
credentials: Login request with email and password
auth_service: Injected AuthService dependency
Returns:
LoginResponse: Access token, refresh token, and user data
Raises:
HTTPException: 401 if credentials invalid or email not verified
HTTPException: 429 if account is locked due to failed attempts
Security:
- Requires HTTPS (enforced by TrustedHostMiddleware)
- Rate limited to 5 requests per minute per IP
- Account locks after 10 failed attempts (1 hour)
- Tracks device and IP for session management
Example:
>>> POST /api/v1/auth/login
>>> {
... "email": "user@example.com",
... "password": "SecurePass123!"
... }
>>> # Returns: { "access_token": "eyJ...", "refresh_token": "...", ... }
"""
FastAPI-Specific Sections:
- Request Body (schema description)
- Response (schema description)
- Status Codes (all possible HTTP status codes)
- Security (authentication/authorization requirements)
- Example (cURL or HTTP request example)
Step 5: Write Test Docstrings¶
Tests are documentation of expected behavior. Comprehensive test docstrings are critical for understanding what's being tested, debugging failures quickly, and onboarding new developers.
Test Module Docstrings¶
Every test module MUST have a module-level docstring:
"""Unit tests for AuthService.
Tests complete user authentication flows including:
- User registration with password hashing
- Login with credential validation
- Token refresh with rotation
- Email verification
- Password reset flow
- Account lockout on failed attempts
- Session management
Coverage:
- All AuthService public methods
- Error handling and edge cases
- Security features (lockout, hashing)
- Token rotation logic
Note:
Uses synchronous test pattern (regular def test_*(), NOT async def)
following FastAPI TestClient conventions. Database operations are
mocked using unittest.mock for true unit testing.
"""
Required Elements:
- One-line summary of what module tests
- List of tested functionality
- Optional Coverage section
- Optional Note for testing patterns
Test Class Docstrings¶
Test classes group related test scenarios:
class TestAuthServiceLogin:
"""Test suite for AuthService login functionality.
Covers all login scenarios including success, failure, account lockout,
and edge cases. Validates credential checking, token generation, and
audit logging.
Fixtures:
verified_user: Pre-created user with verified email
auth_service: AuthService instance with mocked dependencies
Note:
Uses pytest fixtures for consistent test data.
All tests are synchronous (no async/await needed).
"""
Test Function Docstrings¶
Standard Test Pattern:
def test_login_success(self, client: TestClient, verified_user: User):
"""Test successful user login with valid credentials.
Verifies that:
- Login returns 200 status code
- Response includes both access_token and refresh_token
- Response includes user profile data
- Token type is "bearer"
- User email matches the logged-in user
- Refresh token is stored in database (hashed)
- Failed login attempts counter is reset to 0
Args:
client: FastAPI TestClient fixture for making HTTP requests
verified_user: Pre-created user with verified email (from fixtures/users.py)
Note:
User must have email_verified=True to successfully login.
Uses TestClient's synchronous pattern (no await needed).
"""
Test with Mocks:
def test_login_with_account_lockout(
self,
mock_session: AsyncSession,
locked_user: User
):
"""Test login failure when account is locked due to failed attempts.
Scenario:
User has 10+ failed login attempts and account is locked until
future timestamp. Login should fail even with correct password.
Setup:
- Creates user with failed_login_attempts=10
- Sets locked_until to 1 hour in future
- Mocks database session to return locked user
Verifies that:
- Login returns 429 status (Too Many Requests)
- Error message indicates account is locked
- Response includes locked_until timestamp
- Failed attempts counter is NOT incremented
Args:
mock_session: Mocked AsyncSession for database operations
locked_user: User fixture with locked account status
Raises:
AssertionError: If lockout not properly enforced
Note:
Account lockout is automatic after 10 failed attempts.
Lockout duration is 1 hour (configurable in settings).
"""
Parametrized Test Pattern:
@pytest.mark.parametrize("password,expected_valid", [
("short", False), # Too short (< 8 chars)
("NoDigits!", False), # Missing digit
("noupperca5e!", False), # Missing uppercase
("NOLOWERCASE5!", False), # Missing lowercase
("NoSpecialChar5", False), # Missing special char
("ValidPass123!", True), # Meets all requirements
])
def test_password_strength_validation(self, password: str, expected_valid: bool):
"""Test password strength validation with various inputs.
Validates password requirements:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one digit
- At least one special character (!@#$%^&*)
Args:
password: Password string to validate
expected_valid: Expected validation result (True/False)
Note:
Uses pytest.mark.parametrize for multiple test cases.
Tests cover all password validation rules.
"""
Step 6: Write Pytest Fixture Docstrings¶
Fixtures MUST have comprehensive docstrings:
@pytest.fixture
def verified_user(db_session: Session) -> User:
"""Create test user with verified email for login tests.
This fixture creates a user with email_verified=True, which is
required for successful login operations. Password is set to
"TestPass123!" and hashed with bcrypt.
Args:
db_session: Database session for persisting user
Returns:
User: Test user instance with:
- email: "testuser@example.com"
- password: "TestPass123!" (hashed)
- email_verified: True
- failed_login_attempts: 0
Yields:
User instance persisted in test database
Cleanup:
User is automatically cleaned up by db_session fixture rollback.
No manual cleanup needed.
Note:
Use this fixture when testing authenticated operations.
For testing unverified users, use base test_user fixture.
Example:
def test_login(verified_user: User):
response = client.post("/auth/login", json={
"email": verified_user.email,
"password": "TestPass123!"
})
assert response.status_code == 200
"""
user = User(
email="testuser@example.com",
password_hash=hash_password("TestPass123!"),
email_verified=True,
failed_login_attempts=0
)
db_session.add(user)
db_session.commit()
db_session.refresh(user)
yield user
# Cleanup handled by db_session rollback
Fixture-Specific Sections:
- Args (fixture dependencies)
- Returns or Yields (what fixture provides)
- Cleanup (how resources are cleaned up)
- Example (how to use the fixture)
Examples¶
Example 1: Complete Service Class¶
Here's a complete example of a service class with proper docstrings:
class AuthService:
"""User authentication and authorization service.
Orchestrates user authentication flows including registration, login,
token management, email verification, and password resets. Coordinates
between database operations, password hashing, JWT creation, and email
notifications.
Attributes:
session: AsyncSession for database operations
password_service: Service for password hashing and validation
jwt_service: Service for JWT token creation and validation
email_service: Service for sending verification/reset emails
Security:
- All passwords hashed with bcrypt (12 rounds)
- Refresh tokens hashed before storage (one-way)
- Account lockout after 10 failed login attempts
- Email verification required before login
Example:
>>> auth_service = AuthService(session, password_svc, jwt_svc, email_svc)
>>> user = await auth_service.register_user("user@example.com", "SecurePass123!")
>>> tokens = await auth_service.login("user@example.com", "SecurePass123!")
Note:
This service is async and all methods require await.
Use get_auth_service() dependency for FastAPI endpoints.
"""
Example 2: FastAPI Endpoint¶
Here's a FastAPI endpoint with comprehensive documentation:
@router.post("/auth/login", response_model=LoginResponse)
async def login(
credentials: LoginRequest,
auth_service: AuthService = Depends(get_auth_service)
) -> LoginResponse:
"""Authenticate user and return JWT tokens.
Validates user credentials and returns access + refresh tokens if successful.
Tracks failed login attempts and enforces account lockout policy.
Request Body:
- email: User's email address
- password: User's password (plaintext, transmitted over HTTPS)
- device_info: Optional device information for session tracking
Response:
- access_token: JWT access token (30 min expiry)
- refresh_token: Opaque refresh token (30 days expiry)
- token_type: Always "bearer"
- user: User profile data (id, email)
Status Codes:
- 200: Login successful, tokens returned
- 400: Invalid request format
- 401: Invalid credentials or email not verified
- 429: Too many failed attempts, account locked
Args:
credentials: Login request with email and password
auth_service: Injected AuthService dependency
Returns:
LoginResponse: Access token, refresh token, and user data
Raises:
HTTPException: 401 if credentials invalid or email not verified
HTTPException: 429 if account is locked due to failed attempts
Security:
- Requires HTTPS (enforced by TrustedHostMiddleware)
- Rate limited to 5 requests per minute per IP
- Account locks after 10 failed attempts (1 hour)
- Tracks device and IP for session management
"""
Example 3: Test Function¶
Here's a test function with proper documentation:
def test_login_success(self, client: TestClient, verified_user: User):
"""Test successful user login with valid credentials.
Verifies that:
- Login returns 200 status code
- Response includes both access_token and refresh_token
- Response includes user profile data
- Token type is "bearer"
- User email matches the logged-in user
- Refresh token is stored in database (hashed)
- Failed login attempts counter is reset to 0
Args:
client: FastAPI TestClient fixture for making HTTP requests
verified_user: Pre-created user with verified email (from fixtures/users.py)
Note:
User must have email_verified=True to successfully login.
Uses TestClient's synchronous pattern (no await needed).
"""
Example 4: Pytest Fixture¶
Here's a pytest fixture with complete documentation:
@pytest.fixture
def verified_user(db_session: Session) -> User:
"""Create test user with verified email for login tests.
This fixture creates a user with email_verified=True, which is
required for successful login operations. Password is set to
"TestPass123!" and hashed with bcrypt.
Args:
db_session: Database session for persisting user
Returns:
User: Test user instance with:
- email: "testuser@example.com"
- password: "TestPass123!" (hashed)
- email_verified: True
- failed_login_attempts: 0
Yields:
User instance persisted in test database
Cleanup:
User is automatically cleaned up by db_session fixture rollback.
No manual cleanup needed.
Note:
Use this fixture when testing authenticated operations.
For testing unverified users, use base test_user fixture.
Example:
def test_login(verified_user: User):
response = client.post("/auth/login", json={
"email": verified_user.email,
"password": "TestPass123!"
})
assert response.status_code == 200
"""
user = User(
email="testuser@example.com",
password_hash=hash_password("TestPass123!"),
email_verified=True,
failed_login_attempts=0
)
db_session.add(user)
db_session.commit()
db_session.refresh(user)
yield user
# Cleanup handled by db_session rollback
Verification¶
Check 1: Run Linting¶
Run the following commands to verify docstring quality:
# Lint code (includes docstring checks)
make lint
# Format code (auto-formats docstrings)
make format
Expected Output:
- No linting errors related to missing or malformed docstrings
- Code formatting applied successfully
What This Checks: Ensures all docstrings follow proper format and all required sections are present.
Check 2: Review Completeness¶
Manually review your docstrings against this checklist:
For every function/method:
[ ] One-line summary
[ ] Args section (all parameters)
[ ] Returns section (return value description)
[ ] Raises section (all exceptions)
[ ] Example for complex functions
For every class:
[ ] Class-level docstring
[ ] Attributes section
[ ] Example usage
For every test:
[ ] Summary of what's tested
[ ] "Verifies that:" section
[ ] Args section for all fixtures
What This Checks: Ensures completeness beyond what automated tools can verify.
Check 3: Test Documentation Coverage¶
Verify test documentation quality:
What to Look For:
- Test names are descriptive and match docstrings
- Failed tests show clear context from docstrings
- All fixtures used have documentation
Troubleshooting¶
Issue 1: Docstring Too Brief¶
def test_login(self, client, user):
"""Test login.""" # TOO BRIEF - doesn't explain what's validated
Why This is Wrong:
- Doesn't explain what's being tested
- No information about fixtures
- Missing verification details
- Not helpful for debugging failures
✅ Correct Version:
def test_login_success(self, client: TestClient, verified_user: User):
"""Test successful user login with valid credentials.
Verifies that login returns 200 status, includes access and refresh
tokens, and resets failed login attempts counter.
Args:
client: FastAPI TestClient for HTTP requests
verified_user: User with verified email (fixture)
"""
Issue 2: Missing Fixture Documentation¶
def test_token_refresh(self, client, refresh_token):
"""Test token refresh."""
# MISSING: No Args section documenting fixtures
✅ Correct Version:
def test_token_refresh(self, client: TestClient, refresh_token: str):
"""Test successful token refresh with valid refresh token.
Args:
client: FastAPI TestClient for HTTP requests
refresh_token: Valid refresh token string (fixture)
"""
Issue 3: Implementation vs Intent¶
def test_password_reset(self, auth_service):
"""Calls auth_service.reset_password() and checks result."""
# WRONG: Describes implementation, not test purpose/validation
✅ Correct Version:
def test_password_reset_sends_email(self, auth_service, mock_email_service):
"""Test password reset sends email with reset token.
Verifies that requesting password reset generates secure token,
stores it in database with expiration, and sends email to user.
Args:
auth_service: AuthService instance with mocked email
mock_email_service: Mocked EmailService to verify call
"""
Issue 4: Missing Error Documentation¶
async def authenticate_user(email: str, password: str) -> User:
"""Authenticate user with email and password."""
# MISSING: No Raises section for possible exceptions
✅ Correct Version:
async def authenticate_user(email: str, password: str) -> User:
"""Authenticate user with email and password.
Args:
email: User's email address
password: Plain text password to verify
Returns:
User: Authenticated user instance
Raises:
HTTPException: 401 if credentials invalid
HTTPException: 429 if account is locked
ValueError: If email format invalid
"""
Best Practices¶
Quick Reference Checklist¶
Every Module:
- Module-level docstring at top
- One-line summary + detailed description
- Lists key components/functionality
Every Class:
- Class-level docstring
- Attributes section (if applicable)
- Example usage (for public APIs)
- Note section for patterns/warnings
Every Function/Method:
- One-line summary
- Detailed description (if needed)
- Args section (all parameters documented)
- Returns section (describes return value)
- Raises section (all possible exceptions)
- Optional: Example, Note, Warning
Every Test Function:
- Summary of what's tested
- "Verifies that:" section with checks
- Args section documenting all fixtures
- Optional: Scenario, Setup, Note sections
Every Pytest Fixture:
- Summary of fixture purpose
- Args section (dependencies)
- Returns/Yields section (what's provided)
- Cleanup section (resource cleanup)
- Example usage
Google-Style Section Order¶
Standard order for sections (not all required):
- Summary (one line)
- Detailed description (paragraph)
- Args
- Returns / Yields
- Raises
- Note / Warning
- Example
- References (for complex algorithms)
Common Patterns¶
Follow these patterns when writing docstrings:
- ✅ Write docstrings first: Define behavior before implementing (TDD for documentation)
- ✅ Use type hints: Always combine type annotations with docstring descriptions
- ✅ Include examples: Provide usage examples for complex functions and public APIs
- ✅ Document errors: List all possible exceptions in Raises section
- ✅ Test docstrings: Treat tests as first-class documentation
- ✅ Keep current: Update docstrings when behavior changes
For test docstrings, use these additional sections:
For test functions, use these additional sections:
- Scenario: Context/background for the test
- Setup: Test data preparation steps
- Verifies that: Specific checks performed
- Raises: Expected assertion failures
Next Steps¶
After completing this guide, consider:
- Review existing modules and update docstrings to meet standards
- Set up automated docstring checks in CI/CD pipeline
- Create project-specific docstring templates for common patterns
- Document complex algorithms with References section
- Review Testing Guide for comprehensive testing documentation
- Review Test Docstring Standards for test-specific patterns
- Verify WARP.md compliance for all new code
References¶
Official Style Guides¶
Tools and Automation¶
- mkdocstrings - Auto-generate docs from docstrings
- Sphinx - Documentation builder
- Ruff - Fast Python linter (checks docstrings)
Project Documentation¶
- WARP.md - Complete project rules and context
- Test Docstring Standards - Test-specific patterns
- Documentation Implementation Guide - MkDocs setup
Document Information¶
Template: guide-template.md Created: 2025-10-11 Last Updated: 2025-10-15