# Coding Conventions **Analysis Date:** 2026-04-01 ## Naming Patterns **Files:** - `snake_case.py` throughout — `coordinates.py`, `chunk_manager.py`, `processor.py` - Test files: `test_.py` prefix — `test_coordinates.py`, `test_api_flights.py` - Schema files grouped by domain — `flight.py`, `vo.py`, `graph.py`, `metric.py` **Classes:** - `PascalCase` for all classes — `FlightProcessor`, `CoordinateTransformer`, `FactorGraphOptimizer` - Interface/ABC classes prefixed with `I` — `IImageMatcher`, `ISequentialVisualOdometry`, `IModelManager` - Error/Exception classes suffixed with `Error` — `OriginNotSetError`, `QueueFullError`, `ValidationError` - Pydantic schema classes named after the concept they represent, suffixed with `Request`/`Response` for API boundaries — `FlightCreateRequest`, `FlightDetailResponse`, `BatchUpdateResponse` - Config classes suffixed with `Config` or `Settings` — `DatabaseConfig`, `RecoveryConfig`, `AppSettings` **Functions/Methods:** - `snake_case` — `set_enu_origin`, `compute_relative_pose`, `retrieve_candidate_tiles` - Async functions not distinguished by name from sync (no `async_` prefix); the `async def` keyword is the signal - Private/internal methods prefixed with single underscore — `_init_flight`, `_cleanup_flight`, `_publish_frame_result` **Variables:** - `snake_case` — `flight_id`, `rel_pose`, `chunk_mgr` - Per-flight in-memory dicts named `_` keyed by `flight_id` — `_origins`, `_flight_states`, `_prev_images`, `_flight_cameras` - Constants: uppercase not consistently enforced; numeric magic values appear in comments (e.g., `111319.5`) **Type Parameters:** - `PascalCase` — standard Python typing conventions ## Code Style **Formatter/Linter:** - `ruff` — configured in `pyproject.toml` - Line length: 100 characters (`line-length = 100`) - Target: Python 3.11 (`target-version = "py311"`) - Ruff rule sets active: `E` (pycodestyle errors), `F` (Pyflakes), `I` (isort), `W` (pycodestyle warnings) - No `B` (flake8-bugbear) or `UP` (pyupgrade) rules enabled **Type Hints:** - Type hints on all public method signatures — parameters and return types - `from __future__ import annotations` used in modules with complex forward references (`processor.py`, `flight.py`, `config.py`) - `Optional[T]` used alongside `T | None` syntax (inconsistency — both styles present) - `dict[str, X]` and `list[X]` lowercase generics (Python 3.9+ style) - Untyped component slots use `= None` without annotation — e.g., `self._vo = None` — intentional for lazy init ## Module Docstrings Every source module starts with a one-line docstring naming the component: ```python """Coordinate Transformer (Component F13).""" """Core Flight Processor — Full Processing Pipeline (Stage 10).""" """Sequential Visual Odometry (Component F07).""" ``` Test files also carry docstrings stating what they test and which component ID they cover. ## Import Organization **Order (enforced by ruff `I` rules):** 1. Standard library (`__future__`, `asyncio`, `logging`, `math`, `abc`) 2. Third-party (`fastapi`, `numpy`, `cv2`, `pydantic`, `sqlalchemy`) 3. Internal project (`gps_denied.*`) **Internal imports:** - Always absolute — `from gps_denied.core.coordinates import CoordinateTransformer` - Schema imports collected into explicit multi-line blocks at top of file - Lazy in-function imports used to avoid circular imports: `from gps_denied.schemas import Geofences` inside a method body in `processor.py` ## ABC/Interface Pattern All major processing components define an Abstract Base Class in the same module: ```python class ISequentialVisualOdometry(ABC): @abstractmethod def compute_relative_pose(...) -> RelativePose | None: ... class SequentialVisualOdometry(ISequentialVisualOdometry): ... ``` Components that depend on another component accept the interface type in `__init__`, enabling mock injection: ```python class SequentialVisualOdometry(ISequentialVisualOdometry): def __init__(self, model_manager: IModelManager): ... ``` This pattern appears in: `vo.py`, `models.py`, `rotation.py`. `FlightProcessor` uses a post-construction injection method instead: ```python def attach_components(self, vo=None, gpr=None, metric=None, ...): ... ``` ## Pydantic Schema Conventions - All schemas inherit from `pydantic.BaseModel` - Validation constraints use `Field(..., ge=..., le=..., gt=..., min_length=..., max_length=...)` - Default values use `Field(default_factory=...)` for mutable defaults - `model_dump()` and `model_validate_json()` used (Pydantic v2 API) - Config classes inherit from `pydantic_settings.BaseSettings` with `SettingsConfigDict(env_prefix=...)` - Grouped into separate schema files by domain: `flight.py`, `vo.py`, `graph.py`, `metric.py`, `gpr.py`, `chunk.py`, `rotation.py`, `satellite.py`, `events.py`, `image.py`, `model.py` ## Error Handling **Custom exceptions:** - Defined in the module where they originate — `OriginNotSetError` in `coordinates.py`, `QueueFullError` / `ValidationError` in `pipeline.py` - Inherit directly from `Exception` (no base project exception class) **In async pipeline code:** - `try/except Exception as exc` with `logger.warning(...)` — swallowed errors are logged, not re-raised: ```python except Exception as exc: logger.warning("VO failed for frame %d: %s", frame_id, exc) ``` - HTTP layer raises `HTTPException` from FastAPI — no custom HTTP exception hierarchy **Type-ignore suppressions:** - `# type: ignore[union-attr]` used on SQLAlchemy `result.rowcount` accesses in `repository.py` - `# type: ignore` on two FastAPI dependency parameters in `flights.py` (multipart form endpoint) ## Logging **Framework:** Python stdlib `logging` **Setup per module:** ```python import logging logger = logging.getLogger(__name__) ``` **Usage patterns:** - `logger.warning(...)` for recoverable failures (VO failure, drift correction failure) - `logger.info(...)` for state machine transitions: `"Flight %s → LOST at frame %d"` - `logger.debug(...)` for high-frequency data: optimization convergence results ## Section Separators Long modules use visual comment dividers to delineate sections: ```python # --------------------------------------------------------------------------- # State Machine # --------------------------------------------------------------------------- ``` and ```python # ========================================================= # process_frame — central orchestration # ========================================================= ``` ## Function Design **Size:** Methods tend to be 10-40 lines; `process_frame` is intentionally longer (~80 lines) as the central orchestrator. **Parameters:** Typed, ordered: `self`, required positional, optional keyword with defaults. **Return Values:** - Async methods return domain schema objects or `None` on not-found - Sync methods return `bool` for success/failure of CRUD operations - `Optional[T]` / `T | None` used for nullable returns consistently ## Comments **Inline comments** used liberally to explain non-obvious math or mock logic: ```python # 111319.5 meters per degree at equator # FAKE Math for mockup: # Very rough scaling: assume 1 pixel is ~0.1 meter ``` **Section-level comments** label major logical steps inside long methods: ```python # ---- 1. Visual Odometry (frame-to-frame) ---- # ---- 2. State Machine transitions ---- ``` ## Module Design **Exports:** No explicit `__all__` in any module; consumers import directly from sub-modules. **Barrel Files:** `src/gps_denied/schemas/__init__.py` re-exports common types (`GPSPoint`, `CameraParameters`, `Geofences`, `Polygon`). Other packages use direct imports. --- *Convention analysis: 2026-04-01*