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])
- Code Organization
- Use absolute imports for cross-module code
- Group imports: standard library, third-party, local
- Sort imports alphabetically within groups
- Maximum line length: 100 characters
-
Maximum file length: 500 lines
-
Naming Conventions
- Files: snake_case (e.g.,
user_mapping.py) - Classes: PascalCase (e.g.,
UserMapping) - Functions/Methods: snake_case (e.g.,
get_user_by_id) - Variables: snake_case (e.g.,
user_count) -
Constants: UPPER_SNAKE_CASE (e.g.,
MAX_RETRY_COUNT) -
Documentation
- Every module must have a docstring explaining its purpose
- Every class must have a docstring describing its responsibility
- Every public method must have a docstring with:
- Description
- Parameters
- Return value
- Raises (if applicable)
-
Use type hints instead of documenting types in docstrings
-
Testing Requirements
- Every new feature must include:
- Unit tests for business logic
- Integration tests for API endpoints
- Error case coverage
- Test files must mirror source file structure
- Test names must describe the scenario and expected outcome
- Use pytest fixtures for common setup
-
Mock external dependencies
-
Quality Checks
- Run
mypyfor type checking - Run
blackfor code formatting - Run
isortfor import sorting - Run
flake8for style checking - Run
pytestfor test execution - 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
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