Skip to content

ADR-001: Development and Testing Practices

Status: Implemented Date: 2024-06-10 Context: To ensure maintainability, reliability, and team alignment, OmniButler established clear development and testing practices from the outset.

Decision: 1. Type Safety and Data Validation - Use Pydantic models for all data structures - Use Python type hints for all function parameters and return values - Use | None instead of Optional[T] for nullable types - Use standard Python collection types (e.g., dict[str, int] instead of Dict[str, int])

  1. Code Organization
  2. Use absolute imports for cross-module code
  3. Group imports: standard library, third-party, local
  4. Sort imports alphabetically within groups
  5. Maximum line length: 100 characters
  6. Maximum file length: 500 lines

  7. Naming Conventions

  8. Files: snake_case (e.g., user_mapping.py)
  9. Classes: PascalCase (e.g., UserMapping)
  10. Functions/Methods: snake_case (e.g., get_user_by_id)
  11. Variables: snake_case (e.g., user_count)
  12. Constants: UPPER_SNAKE_CASE (e.g., MAX_RETRY_COUNT)

  13. Documentation

  14. Every module must have a docstring explaining its purpose
  15. Every class must have a docstring describing its responsibility
  16. Every public method must have a docstring with:
    • Description
    • Parameters
    • Return value
    • Raises (if applicable)
  17. Use type hints instead of documenting types in docstrings

  18. Testing Requirements

  19. Every new feature must include:
    • Unit tests for business logic
    • Integration tests for API endpoints
    • Error case coverage
  20. Test files must mirror source file structure
  21. Test names must describe the scenario and expected outcome
  22. Use pytest fixtures for common setup
  23. Mock external dependencies

  24. Quality Checks

  25. Run mypy for type checking
  26. Run black for code formatting
  27. Run isort for import sorting
  28. Run flake8 for style checking
  29. Run pytest for test execution
  30. All checks must pass before merging

Examples: - Good:

from typing import List
from pydantic import BaseModel, Field

class User(BaseModel):
    """Represents a user in the system.

    Attributes:
        name: User's full name
        age: User's age in years
        roles: List of user roles
    """
    name: str
    age: int | None = Field(default=None)
    roles: List[str] = Field(default_factory=list)

def get_user_by_id(user_id: str) -> User | None:
    """Retrieve a user by their ID.

    Args:
        user_id: The unique identifier of the user

    Returns:
        User object if found, None otherwise

    Raises:
        DatabaseError: If there's an error accessing the database
    """
    # Implementation
- Bad:
from dataclasses import dataclass
from typing import Optional, List

@dataclass
class User:
    # No docstring
    name: str
    age: Optional[int] = None
    roles: List[str] = None

def getUserById(userId):  # No type hints, no docstring
    # Implementation

Consequences: - Code is consistent, readable, and maintainable - Regressions are caught early through comprehensive testing - Onboarding is streamlined with clear expectations - Code reviews are more efficient with standardized practices - Technical debt is reduced through consistent quality checks

References: - See docs/dev_practices.md for additional details - See whatsapp_restructure_scratch.md for implementation examples