Skip to content

Per-format install extras: nlr-gat[sienna|plexos|reeds|plots|...] - #29

Merged
micahpw merged 5 commits into
mainfrom
feat/split-packaging-extras-lazy-imports
Aug 3, 2026
Merged

micahpw merged 5 commits into
mainfrom
feat/split-packaging-extras-lazy-imports

Conversation

@micahpw

@micahpw micahpw commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #23. Closes #11 (expanded from docs-only to full removal — see below).

Packaging

Base deps trimmed to the format-agnostic floor (numpy, pandas, pydantic, click, loguru, pyyaml, orjson). Everything format-specific moves into extras:

Extra Adds Installs
sienna SiennaScenario h5py, polars, geopandas
plexos PlexosScenario (both backends, for now) h5py, duckdb, plexos2duckdb
reeds ReEDsScenario (base only)
plots gat.quickplots / .plot property matplotlib (+PySide6 on macOS)
server/client gained their actual real deps (duckdb, pyarrow) — previously hidden in base
all everything

dev now pulls in every format + plots — fixes #23's root cause directly (pip install -e ".[dev,reports]" never installed plexos-duckdb, so the dual-backend PLEXOS suite silently never ran, anywhere, ever). plexos-duckdb kept as a backward-compat alias since it's already published on PyPI under that name. tables removed outright — confirmed dead, never imported anywhere in src/gat.

Lazy imports (the part that makes the extras real)

Declaring extras means nothing if the import graph ignores them. from gat.scenariohandlers import PlexosScenario previously eagerly imported every handler regardless of which one was wanted — a [reeds]-only install would ImportError the instant anything touched gat.scenariohandlers at all. Fixed everywhere with the same shape:

  • scenariohandlers/__init__.py, simulations/__init__.py, systems/__init__.py → PEP 562 __getattr__ (mirroring gat/__init__.py's existing pattern), with __all__ so both import * and named imports stay lazy.
  • datahelpers/__init__.py → gutted its blanket from .X import *. Every real caller already used fully-qualified submodule imports; the one exception (an example script) is fixed.
  • datahelpers/parsers.py → h5py import moved into the 3 functions that actually touch h5py.File. This module sits on BaseScenario's mandatory path, so ReEDS/EGRET were paying for h5py despite never using it.
  • gat/colors.py → random_color/standard_color_dict relocated here (dependency-free; old locations re-export for compat). Both were reachable from models/scenario.py's TechnologyMapping, which BaseScenario imports unconditionally — so constructing any handler transitively pulled in matplotlib + triggered quickplots' macOS Qt-backend detection just to pick a fallback color.
  • Drive-by: two dead, shadowed _find_solution_files definitions removed (base.py, reeds.py each defined it twice in one class — Python keeps only the later copy silently).

Verification

  • sys.modules probing: touching PlexosScenario only imports h5py; SiennaScenario only h5py+geopandas+polars. Neither leaks into the other or into matplotlib/duckdb.
  • Built the wheel, installed bare / sienna / plexos / reeds / plots / all / dev into six independent fresh venvs. Each behaves exactly as designed — clean imports where expected, clear ModuleNotFoundError (not a confusing crash) where a format's deps are absent.
  • Full local suite: 236 passed, 58 skipped, 19 failed — the failures are exactly the Legacy PLEXOS h5 regression baselines don't match any available fixture (19/20 fail) #27 class (legacy h5 regression baselines vs. an unavailable original fixture), already triaged and dismissed as expected. Zero new failures.

EGRET removal (#11, expanded scope)

egret.py and its docs page deleted outright rather than just de-listed. Confirmed before deletion: its only real dependency (egret_map_simple) had zero other consumers, EGRETScenario was referenced nowhere but its own file and the __init__ import, and nothing tested it.

🤖 Generated with Claude Code

micahpw and others added 3 commits August 3, 2026 11:10
Closes #23. Also closes #11 (removes EGRET from code and docs, not
just the docs page as originally scoped — the handler had zero test
coverage or other consumers, confirmed before deletion).

## The packaging half

Trims base dependencies to the truly format-agnostic floor (numpy,
pandas, pydantic, click, loguru, pyyaml, orjson) and moves everything
format-specific into extras:

- `sienna` — h5py, polars, geopandas
- `plexos` — h5py + duckdb + plexos2duckdb, bundled together "for now"
  per the H5PLEXOS.jl deprecation notice (README); `plexos-duckdb`
  kept as a backward-compat alias for the plexos2duckdb-only slice,
  since it's already published under that name on PyPI
- `reeds` — declared empty; ReEDsScenario needs nothing beyond base
- `plots` — matplotlib + PySide6 (gat.quickplots), independent of
  source format
- `server`/`client` — gained duckdb/pyarrow, their actual real deps,
  previously hidden inside the old blanket base dependencies
- `all` — convenience bundle of everything above
- `dev` — now pulls in every format + plots extra, fixing the root
  cause of #23 (the dual-backend PLEXOS suite silently never ran
  because `pip install -e ".[dev,reports]"` never installed
  plexos-duckdb)
- removed `tables` entirely — confirmed dead, never imported anywhere
  in src/gat

## The lazy-import half (required for the extras to mean anything)

Declaring extras is meaningless if the import graph doesn't respect
them — `from gat.scenariohandlers import PlexosScenario` previously
eagerly imported every handler (Sienna, PLEXOS, EGRET, ReEDS, ...)
regardless of which one was requested, so a `[reeds]`-only install
would ImportError the moment anything touched gat.scenariohandlers at
all. Fixed at every layer that had the same eager-`import *`/blanket-
top-level-import shape:

- `gat/scenariohandlers/__init__.py`, `gat/simulations/__init__.py`,
  `gat/systems/__init__.py` — converted to PEP 562 module-level
  `__getattr__` (mirroring the pattern gat/__init__.py already used),
  with `__all__` so `from package import *` and `from package import
  Name` both still resolve lazily and correctly.
- `gat/datahelpers/__init__.py` — gutted its blanket `from .X import
  *` re-exports entirely. Every real caller already imports specific
  submodules directly (gat.datahelpers.sienna_system, .parsers,
  .h5Parsers, ...); only one example script relied on the flat
  re-export, fixed to the fully-qualified path.
- `gat/datahelpers/parsers.py` — h5py import moved from module level
  into the three functions that actually touch h5py.File(...). This
  module sits on BaseScenario's mandatory import path (`from
  gat.datahelpers.parsers import *`), so every handler — including
  ReEDS and EGRET, which never touch h5 — was paying for h5py.
- `gat/colors.py` — random_color() and standard_color_dict relocated
  here from gat.quickplots (dependency-free; the old locations
  re-export them for backward compatibility). Both were on the
  mandatory path too: standard_color_dict/random_color are used by
  TechnologyMapping in gat/models/scenario.py, which BaseScenario
  imports unconditionally — so constructing ANY scenario handler
  transitively imported matplotlib + triggered quickplots' macOS
  Qt-backend-detection, just to pick a fallback hex color.
- Two dead, shadowed `_find_solution_files` method definitions found
  and removed along the way (base.py, reeds.py each defined the
  method twice in the same class — Python silently keeps only the
  later one; harmless but confusing, cleaned up while touching this
  code for the h5py fix).

## Verification

- sys.modules probing confirms real isolation: touching PlexosScenario
  only imports h5py; SiennaScenario only h5py+geopandas+polars; neither
  touches the other's deps or matplotlib/duckdb inappropriately.
- Built the wheel and installed bare/sienna/plexos/reeds/plots/all/dev
  into six independent, fresh venvs. Confirmed each extra's handler
  imports cleanly, unrelated handlers fail with a clear
  ModuleNotFoundError (not a confusing crash), and dev's
  self-referential extra resolves every format + plots correctly.
- Full local suite: 236 passed, 58 skipped, 19 failed — the failures
  are exactly the #27 class (legacy h5 regression baselines vs. an
  unavailable original fixture), already triaged and dismissed by the
  maintainer as expected given the H5PLEXOS.jl deprecation timeline.
  Zero new failures from this change.

## EGRET removal (closes #11, expanded from docs-only)

Deleted gat/scenariohandlers/egret.py and its docs page. Confirmed
before deletion: egret_map_simple (its only real dependency, in
config_maps.py) had zero other consumers, EGRETScenario was
referenced nowhere outside its own file and the __init__ import, and
no test exercised it at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Autodoc imports every scenario handler module directly
(gat.scenariohandlers.plexos, .reeds, .sienna, ...) to pull
docstrings, and the gallery examples construct SiennaScenario and
call gat.quickplots. Neither is covered by doc/reports alone now
that format-specific deps moved into extras (see #23) — the docs
build would otherwise ImportError on the first gallery example.

Verified locally: pip install -e '.[doc,reports,all]' into a fresh
venv, confirmed SiennaScenario/PlexosScenario/MultiScenario/
gat.quickplots all import cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI caught this: gat.backends.duckdb_backend._write_parquet goes
through pandas.to_parquet(), which needs a parquet engine — pyarrow
was only declared under server/client, but the core v1 engine
(gat.scenario.Scenario / GATDatabase) needs it too, and it's reached
via the plexos extra (duckdb-backed PlexosScenario, from_plexos_duckdb,
etc.), not just server/client.

Verified in a fresh isolated venv (pip install -e '.[dev,reports]'):
tests/test_duckdb_backend.py 22/22 passed, including the exact
TestScenario/TestGroupedQueries tests that errored in CI before this
fix (ImportError resolving pandas' parquet engine).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
micahpw added a commit that referenced this pull request Aug 3, 2026
Follow-up to the earlier 'Sienna fixture refresh' run on main (run
30832525538) — the cache was refreshed but the baselines were never
regenerated to match, leaving main's own tests.yml red since then
and causing spurious sienna-regression failures on unrelated PRs
(e.g. #29, a packaging-only change that doesn't touch Sienna at all).

Regenerated against that run's exact artifact. Suite: 6/6 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
micahpw and others added 2 commits August 3, 2026 11:31
Covers this PR's breaking extras split, the tilde-path fix (#26/#28),
the H5PLEXOS.jl deprecation notice, and the EGRET removal — the first
changelog entry since the public release split; prior entries predate
the public v0.1.0 versioning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@micahpw
micahpw merged commit 176fd8e into main Aug 3, 2026
5 checks passed
@micahpw
micahpw deleted the feat/split-packaging-extras-lazy-imports branch August 4, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dual-backend PLEXOS test suite never actually runs (dev extras missing plexos-duckdb) Remove EGRET from the documentation

1 participant