Files
thrillwiki_django_no_react/backend/docs/code_standards.md
pacnpal edcd8f2076 Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
2025-12-23 16:41:42 -05:00

7.3 KiB

Code Standards

This document defines the code quality standards for the ThrillWiki backend.

Formatting & Style

PEP 8 Compliance

All Python code must comply with PEP 8, verified using:

  • black: Code formatting (line length: 88)
  • flake8: Style checking (max-line-length: 88, max-complexity: 10)
  • ruff: Fast linting and import sorting

Running Formatters

# Format code
uv run black backend/

# Check style
uv run flake8 backend/ --max-line-length=88 --max-complexity=10

# Lint and fix
uv run ruff check backend/ --fix

Docstring Requirements

Coverage

  • 100% coverage for public classes and methods
  • 100% coverage for all functions
  • Optional for private methods (but encouraged)

Style

Follow Google-style docstrings:

def function_name(arg1: Type1, arg2: Type2) -> ReturnType:
    """
    Brief description of what this function does.

    Longer description if needed, explaining the purpose,
    behavior, and any important details.

    Args:
        arg1: Description of arg1
        arg2: Description of arg2

    Returns:
        Description of return value

    Raises:
        ExceptionType: When this exception is raised

    Example:
        >>> function_name("value1", "value2")
        "result"
    """

Class Docstrings

class ClassName:
    """
    Brief description of what this class does.

    Longer description if needed.

    Attributes:
        attr1: Description of attr1
        attr2: Description of attr2

    Example:
        instance = ClassName()
        instance.method()
    """

View Docstrings

Views should include URL patterns and permissions:

class MyView(DetailView):
    """
    Brief description of what this view does.

    View Type: CBV (DetailView)
    URL Pattern: /resource/<slug>/
    Template: app/resource_detail.html
    Permissions: LoginRequired
    """

Complexity Guidelines

Limits

  • Maximum McCabe complexity: 10
  • Maximum method length: 50 lines
  • Maximum nesting depth: 3 levels

Checking Complexity

# Check McCabe complexity
uv run flake8 backend/ --max-complexity=10 --select=C901

# Get complexity metrics
uv run radon cc backend/apps/ -a

Refactoring Strategies

  1. Extract helper methods for distinct responsibilities:
# Before
def process_data(self, data):
    # Validate data (10 lines)
    # Transform data (10 lines)
    # Save data (10 lines)
    # Send notifications (10 lines)
    pass

# After
def process_data(self, data):
    self._validate_data(data)
    transformed = self._transform_data(data)
    result = self._save_data(transformed)
    self._send_notifications(result)
    return result
  1. Use early returns to reduce nesting:
# Before
def process(self, data):
    if data:
        if data.get('field1'):
            if data.get('field2'):
                return result
    return None

# After
def process(self, data):
    if not data:
        return None
    if not data.get('field1'):
        return None
    if not data.get('field2'):
        return None
    return result
  1. Move complex logic to service layer

Service Layer Patterns

Service Method Signature

Always use keyword-only arguments for service methods:

class MyService:
    @staticmethod
    def create_entity(
        *,  # Force keyword-only arguments
        name: str,
        description: str = "",
        created_by: Optional[User] = None,
    ) -> Entity:
        """Create a new entity."""
        pass

Validation Pattern

Always call full_clean() before save:

@staticmethod
def create_park(*, name: str, ...) -> Park:
    with transaction.atomic():
        park = Park(name=name, ...)
        park.full_clean()  # Validate before save
        park.save()
        return park

Import Organization

Imports should be organized in this order:

  1. Standard library
  2. Third-party packages
  3. Django imports
  4. Local app imports
import logging
from typing import Any, Dict, Optional

from django.contrib.auth import get_user_model
from django.db import transaction
from rest_framework import status

from apps.core.exceptions import ServiceError
from .models import MyModel

Type Hints

Use type hints for all function signatures:

def process_data(
    data: Dict[str, Any],
    user: Optional[User] = None,
) -> ProcessResult:
    """Process data and return result."""
    pass

Testing Requirements

  • Maintain or improve test coverage with changes
  • Add tests for new service methods
  • Add tests for new mixins and base classes
  • Run tests before committing:
pytest backend/tests/ --cov=backend/apps --cov-report=html

Logging Standards

Logger Initialization

Every view and middleware file should initialize a logger:

import logging

logger = logging.getLogger(__name__)

Centralized Logging Utilities

Use the centralized logging utilities from apps.core.logging for structured logging:

from apps.core.logging import log_exception, log_business_event, log_security_event

When to Use Each Log Level

  • logger.debug(): Detailed diagnostic information (disabled in production)
  • logger.info(): General operational events (search queries, user actions)
  • logger.warning(): Unexpected conditions that don't prevent operation
  • logger.error(): Error conditions that require attention
  • log_exception(): Exception handling with full stack trace

Exception Logging

Use log_exception for all exception handlers:

try:
    # operation
except Exception as e:
    log_exception(
        logger,
        e,
        context={"operation": "get_filtered_queryset", "filters": filter_params},
        request=self.request,
    )
    messages.error(self.request, f"Error: {str(e)}")

Business Event Logging

Use log_business_event for significant business operations:

log_business_event(
    logger,
    event_type="fsm_transition",
    message=f"Park approved: {park.name}",
    context={
        "model": "Park",
        "object_id": park.id,
        "old_state": old_status,
        "new_state": park.status,
    },
    request=request,
)

Security Event Logging

Use log_security_event for authentication and security-related events:

log_security_event(
    logger,
    event_type="user_login",
    message=f"User {user.username} logged in successfully",
    severity="low",  # low, medium, high, critical
    context={"user_id": user.id, "username": user.username},
    request=request,
)

What NOT to Log

Never log:

  • Passwords or password hashes
  • API tokens or secrets
  • Session IDs
  • Full credit card numbers
  • Other sensitive PII

Log Message Guidelines

  • Use clear, concise messages
  • Include relevant context (IDs, usernames, operation names)
  • Use consistent naming conventions
  • Avoid logging large data structures

Pre-commit Configuration

The following pre-commit hooks are configured:

repos:
  - repo: https://github.com/psf/black
    rev: 24.1.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/flake8
    rev: 7.1.1
    hooks:
      - id: flake8
        args: [--max-line-length=88, --max-complexity=10]

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.10
    hooks:
      - id: ruff
        args: [--fix]