Developer Guide¶
This page describes the internal layout of HyPlan and the conventions to follow when adding new modules or extending existing ones.
Package layout¶
HyPlan uses a mix of multi-file packages and single-file modules. The rule of thumb: a module becomes a package when it exceeds ~500 lines or when it has clearly separable concerns (e.g. base class vs providers).
Package |
Contents |
|---|---|
|
|
|
|
|
|
|
|
|
|
Single-file modules (terrain.py, flight_line.py, pattern.py,
campaign.py, dubins3d.py, etc.) stay as single files until they
outgrow their scope.
Naming conventions¶
_base.py,_common.py— internal implementation, not imported directly_models.py— concrete instances (aircraft definitions, sensor presets)Leading underscore on classes (
_GriddedWindField) — internal base, not part of the public API but re-exported forisinstancechecks
Re-export pattern¶
Every package has an __init__.py that re-exports its public API:
from ._base import Aircraft # noqa: F401
from ._models import NASA_GV # noqa: F401
__all__ = ["Aircraft", "NASA_GV"]
The top-level hyplan/__init__.py then re-exports key names from each
package so that from hyplan import Aircraft, FlightLine works. When
adding a new public name, add it to both the package __init__.py and
the top-level __init__.py __all__ list.
Backward-compatible shims (e.g. flight_plan.py re-exporting from
planning/) preserve old import paths after refactors. These are thin
files with only re-imports.
Extending HyPlan¶
Adding a wind provider¶
Create
hyplan/winds/providers/mydata.pySubclass
_GriddedWindFieldfromhyplan.winds.griddedImplement the required hooks:
_build_urls()— return OPeNDAP/download URLs for the time range_open_dataset(url)— open a single dataset (override for auth)Override
_var_names(),_dim_names(),_decode_time(),_time_slice()if the data source uses non-standard conventionsOr override
_fetch_slab()entirely for non-OPeNDAP sources (seeGFSWindField)
Re-export from
hyplan/winds/providers/__init__.pyAdd a branch in
hyplan/winds/factory.py→wind_field_from_plan()Add tests in
tests/test_winds.py
Adding a sensor¶
Subclass
Sensor(orLineScanner,SidelookingRadar, etc.) in the appropriate file underhyplan/instruments/Implement
half_angleandswath_width(altitude_agl)at minimumAdd the class to
hyplan/instruments/__init__.pyre-exports and__all__Add to
SENSOR_REGISTRYin_base.pyif it should be discoverable viacreate_sensor(name)
Adding an export format¶
Create
hyplan/exports/myformat.pywith ato_myformat(flight_plan_gdf, path)functionRe-export from
hyplan/exports/__init__.pyAdd to
hyplan/__init__.pyif it should be a top-level import
Adding or updating a pattern generator¶
Implement the generator in
hyplan/flight_patterns.pyReturn a
hyplan.pattern.Pattern, not a bare listStore enough plain-JSON-compatible parameters in
Pattern.paramsto supportPattern.regenerate()If the pattern is line-based, populate
Pattern.lines; if it is waypoint-based, populatePattern.waypointsAdd tests for generation, serialization round-trip, and campaign persistence when relevant
Where future refactors should land¶
If you are adding… |
Put it in… |
|---|---|
A new data source for winds |
|
Wind vector math or heading/track solvers |
|
A new flight pattern generator |
|
Shared reusable pattern behavior |
|
Campaign persistence or mutation behavior |
|
A new segment type or record builder |
|
Changes to the planning orchestrator |
|
A new aircraft model |
|
A new sensor class |
|
A new output format |
|
Coordinate math or projection helpers |
|
DEM or elevation helpers |
|
If a single-file module grows past ~500 lines with separable concerns,
follow the existing pattern: create a package directory, split into
focused files, add __init__.py re-exports, and leave a shim at the
old path for backward compatibility.
Testing conventions¶
All tests live in a flat
tests/directory (no mirroring of package structure)One test file per top-level module or package:
test_winds.py,test_flight_plan.py, etc.Import from the public API:
from hyplan.winds import ConstantWindFieldPrivate functions can be tested by importing from canonical locations:
from hyplan.winds.utils import _wind_factorShared fixtures go in
tests/conftest.pyAirport data initialization uses
@pytest.fixture(scope="module")to avoid repeated downloads
CI workflows¶
Workflow |
Trigger |
What it does |
|---|---|---|
|
Push/PR to main |
Lint (ruff), type check (mypy, non-blocking), pytest with coverage on Python 3.10/3.11/3.12 |
|
Push to main |
Build Sphinx docs and deploy to GitHub Pages |
|
Nightly + PR (if notebooks changed) |
Execute tutorial, exports, and aircraft notebooks via papermill |