Development#
This guide covers everything needed to develop mapflpy from a source checkout: setting up an
environment, building the compiled Fortran extension, running the test suite, building the
documentation, and reproducing the continuous-integration (CI) pipeline locally before opening a
pull request.
Attention
We highly recommend using a virtual environment to manage your Python packages and avoid conflicts with other
projects. For the best results, we recommend using conda – via Miniforge (preferred), Miniconda, or Anaconda
– to create and manage your virtual environments.
Note
mapflpy ships a compiled Fortran extension. Unlike a pure-Python package, an editable
pip install -e . is not sufficient for day-to-day development: any change to the Fortran
sources (or the meson.build that compiles them) requires the shared object to be rebuilt and
reinstalled. The make_build.py helper exists to make that loop painless.
Overview of the Workflow#
Most contributors only ever need two helper scripts, both in the tools/ directory:
tools/make_build.py— build (and optionally install/extract) the compiled wheel, then run tests.tools/make_docs.py— build the Sphinx documentation.
Everything else (Nox sessions, the GitHub Actions workflows, the smaller utility scripts) wraps those same two operations for isolated, reproducible, or multi-version builds. A typical local loop looks like:
# 1. edit Fortran/Python sources ...
python tools/make_build.py --clean --sdist --extract # rebuild + reinstall the extension
python -m pytest # quick test run against the source tree
python tools/make_docs.py --clean # rebuild the docs
Before pushing, reproduce the CI matrix locally with Nox (see Reproducing CI Locally with Nox) so failures surface on your machine rather than in the pipeline.
Setting Up the Development Environment#
1. Clone the repository#
Over HTTPS:
git clone https://github.com/predsci/mapflpy.git
or over SSH:
git clone git@github.com:predsci/mapflpy.git
2. Create the virtual environment(s)#
There are two distinct environments used during development, each with a different purpose:
mapflpy-devA full-featured environment containing the compiler toolchain, runtime dependencies, and all testing, linting, and documentation tools. This is your interactive environment — the one you activate to edit code, run
make_build.py/make_docs.py, and executepytestdirectly. It is defined byenvironment.yml.mapflpy-ciA minimal environment whose only job is to run Nox. Nox in turn creates its own throwaway conda environments for each session, so this environment deliberately contains nothing but Python and Nox. Use it to reproduce CI locally.
cd mapflpy
conda env create -n mapflpy-dev -f environment.yml
conda create -n mapflpy-ci python=3.13 nox
Tip
Keeping the two environments separate means the isolated Nox builds cannot accidentally pick up a dependency that only exists in your interactive environment — exactly the kind of “works on my machine” bug CI is meant to catch.
Running Tests Locally#
Once the extension is built and extracted into the source tree, the fastest feedback loop is to run
pytest directly against the working copy:
conda activate mapflpy-dev
python -m pytest
This runs the suite (with coverage, per the [tool.pytest.ini_options] settings in
pyproject.toml) against the source tree and the extension you just extracted. It is fast and
ideal for iterating, but it tests the code as laid out in your checkout rather than as an installed,
packaged artifact.
make_build.py will also run pytest automatically after a successful build if a tests/
directory is present, so a single python tools/make_build.py doubles as a build-and-test command.
Important
Testing against the source tree is convenient but does not validate packaging. A wheel can build and import locally yet still be broken once installed on another machine (missing bundled libraries, wrong platform tags, etc.). For that, use the isolated Nox builds described in Reproducing CI Locally with Nox, which install the repaired wheel into a clean environment before testing.
Building the Documentation Locally#
conda activate mapflpy-dev
python tools/make_docs.py --clean
This invokes sphinx-build on docs/source and writes HTML to docs/_build/html. The
--clean flag first removes the previous _build/ output as well as the auto-generated
source/autodoc and source/gallery directories, guaranteeing a from-scratch build (important
when API signatures or gallery examples change, since Sphinx otherwise caches them).
The documentation depends on Sphinx-Gallery, which executes
the example scripts under examples/ at build time. This means the docs build will fail if the
examples fail — making it a useful (if heavyweight) integration check in its own right.
See also
See make_docs.py for the full list of flags, and make_intersphinx.py for refreshing the cross-reference inventories used to link out to NumPy, SciPy, etc.
Reproducing CI Locally with Nox#
The CI pipeline does not test the source tree directly. Instead it builds a wheel in an isolated environment, repairs it so that third-party shared libraries (HDF5, the Fortran runtime, …) are bundled into the wheel, installs that repaired wheel into a fresh environment, and only then runs the tests. This proves the package works as a standalone, redistributable artifact. Nox orchestrates these steps, and you can run the exact same sessions on your own machine.
conda activate mapflpy-ci
nox -s build # compile the wheel(s) in an isolated conda env
nox -s repair # bundle external libs into the wheel (delocate/auditwheel/delvewheel)
nox -s test # install the repaired wheel into a clean env and run pytest
To target a single interpreter version instead of the whole matrix, append the version to the session name:
conda activate mapflpy-ci
nox -s build-3.11
nox -s repair
nox -s test-3.11
The repair step is platform-specific and is what makes the wheels portable:
Platform |
Repair tool |
Purpose |
|---|---|---|
Linux |
|
Bundle external |
macOS |
|
Copy external |
Windows |
|
Copy external |
Nox also exposes convenience sessions that chain the individual steps into a single entry point:
nox -s build_matrixsdist→build→repair→testacross every supported Python version.nox -s build_docsThe build/repair/test chain followed by a documentation build against the installed wheel.
nox -s build_qaThe build/repair/test chain followed by type checking (
mypy) and linting (ruff).
Standalone quality sessions are available too: nox -s lint (Ruff), nox -s types (mypy),
nox -s sdist (source distribution only), and nox -s docs (docs against an installed wheel).
Note
Nox uses a conda-compatible backend (conda, mamba, or micromamba) for the compiled
sessions because the Fortran toolchain comes from conda-forge. The artifacts it produces are
written under .nox/_artifacts/ (wheels/, wheelhouse/, sdist/, docs/).
Reference: the tools/ Scripts#
The tools/ directory holds the helper scripts used throughout development. In day-to-day work you
should only need the first two; the remainder are small utilities invoked by the build configuration
and CI.
Script |
Audience |
Purpose |
|---|---|---|
|
Everyday |
Build the wheel/sdist, optionally extract the compiled extension into the source tree, and run tests. |
|
Everyday |
Build the Sphinx documentation with optional cleanup. |
|
Occasional |
Download/refresh the intersphinx inventory ( |
|
Internal |
Print the project version read from |
|
Internal |
Print the |
|
Internal |
Print the list of supported Python versions read from the trove classifiers. |
make_build.py#
Builds the package wheel (and optionally an sdist), selects the wheel that best matches the current interpreter and platform, optionally extracts the compiled extension back into the source tree, and runs the test suite.
python tools/make_build.py [options]
Option |
Effect |
|---|---|
|
Remove |
|
Also build a source distribution. |
|
Unpack the compiled extension from the wheel into |
|
Project root (defaults to the parent of |
|
Override the package name (defaults to |
|
Test directory to run (defaults to |
|
Extra arguments forwarded to |
Warning
--extract deliberately copies a compiled binary into your source tree so that the in-place
package imports the latest build. This is convenient for development but is not how the package
is normally installed — do not commit the extracted artifact, and prefer the Nox builds when you
need to validate real packaging behavior.
make_docs.py#
Builds the Sphinx documentation. By default it calls sphinx-build directly (portable across
platforms); pass --use-make to drive the docs/Makefile instead.
python tools/make_docs.py [options]
Option |
Effect |
|---|---|
|
Remove the previous |
|
Use |
|
Extra arguments forwarded to |
Tip
To treat warnings as errors locally (as a stricter check), forward the -W flag:
python tools/make_docs.py -- -W --keep-going. Documentation warnings most commonly come from
malformed numpydoc docstrings (for example, unexpected indentation in a Notes block), so a
warning-free local build is the easiest way to keep the docs pipeline green.
make_intersphinx.py#
Downloads the intersphinx inventory files (objects.inv) that let the docs cross-reference other
projects’ APIs (Python, NumPy, SciPy, Matplotlib, pytest, psi-data-utils). Run it when adding a
new cross-referenced dependency or refreshing stale inventories.
python tools/make_intersphinx.py
python tools/make_intersphinx.py --target docs/_intersphinx
python tools/make_intersphinx.py --add pandas=https://pandas.pydata.org/docs/objects.inv
Continuous Integration / Delivery#
CI/CD runs on GitHub Actions and reuses the same Nox sessions described above, so a green local
nox run is a strong predictor of a green pipeline. There are two workflows in
.github/workflows/:
publish.yml(Publish Build)Triggered on pull requests, manual dispatch, and
v*tag pushes. It builds an sdist, builds and repairs wheels across a platform matrix (ubuntu-24.04,ubuntu-22.04-arm,macos-15-intel,macos-15), installs and tests each repaired wheel, and — only on a tag — publishes the distributions to GitHub Releases and PyPI.docs.yml(Publish Docs)Triggered on pull requests, manual dispatch, and
v*tag pushes. It runsnox -s build_docsto build the documentation against the freshly built wheel, uploads the HTML as an artifact, and deploys it to the documentation server overrsync.
Note
A new release is cut by pushing a v* tag (matching the [project].version in
pyproject.toml). That single action drives wheel/sdist publication to PyPI and the
documentation deployment. Validate locally with nox -s build_qa and nox -s build_docs
before tagging.