Skip to content

[GP-02] Clarify gems_craft package layout: remove dead libs/, rename model/library/, isolate pydantic schemas #291

Description

@aoustry

Affected Component: Study loading / model resolution (study/) — specifically src/gems_craft/model/ and src/gems_craft/libs/
Type of Change: Minor — backward-compatible refactor (deprecation shim retained); no change to GEMS syntax, no change to solver results
Current version: 0.1.3


Description

Two independent layout problems in gems_craft make the package harder to read than it needs to be. They are worth fixing together because they touch the same files.

Problem 1 — src/gems_craft/libs/ is dead code

The package contains exactly one file, __init__.py, whose entire payload is:

lib_path = Path(__file__).parent

Its stated purpose (AGENTS.md:97) is to "resolve the path to bundled YAML model libraries shipped with the package". Evidence that it no longer does anything:

  • No bundled libraries exist. find src/gems_craft/libs -type f returns only __init__.py.
  • Zero importers. grep -rn "lib_path\|gems_craft\.libs" src/ tests/ docs/ matches only the definition itself.
  • The libraries were left behind in the package split. Git history shows the pre-split src/gems/libs/ did ship antares_historic.yml, pypsa_models.yml and andromede_v1_models.yml. Commit 0dcfc40 ("Split gems into gems_craft/gems_runner") carried only __init__.py across to gems_craft/. Those YAMLs now live under tests/e2e/models/.
  • They could not ship even if restored. pyproject.toml declares no package-data and no include-package-data, and there is no MANIFEST.in, so non-.py files under src/ are not packaged into the wheel.

Consequences: a misleading empty package at the top level of gems_craft, a stale line in AGENTS.md, and a stale libs/ entry in the "Affected Component" dropdown of .github/ISSUE_TEMPLATE/gp-02.yml.

Problem 2 — src/gems_craft/model/ is named after one element of the file it actually owns

The folder is not organized around the Model class. It is a one-to-one mirror of a model library YAML file:

library:
  id: simple_models        # → library.py
  dependencies: [...]      # → resolve_library.py
  taxonomy: ...            # → taxonomy.py
  port-types:              # → port.py   (library-scoped, shared across models)
    - id: flow
  models:                  # → model.py + parameter/variable/constraint
    - id: bus

This causes two recurring points of confusion:

(a) Library, PortType and Taxonomy look like strays. They are not model parts — they are library-level declarations:

  • PortType is declared at library level and shared by many models. It is also the only thing that crosses library boundaries: the dependency-resolution machinery in resolve_library.py (_add_preloaded_port_types_to_current_lib, _add_resolved_dependent_port_types_to_current_lib, lines 136–150) propagates port_types and nothing else. Models are never merged across libraries — they are namespaced library_id.model_id (resolve_library.py:378) and stay put.
  • taxonomy.py imports only LibrarySchema/ModelSchema from parsing.py. It never touches Model, Parameter, Variable or any resolved object.

Every other package in gems_craft is named after the input artifact it owns — expression/ for Expr.g4 expressions, optim_config/ for optim-config.yml, study/ for the study directory. model/ is the odd one out.

(b) "model" collides with pydantic's meaning. model/parsing.py contains ModelSchema (a pydantic model describing a GEMS model) next to model_config (pure pydantic API, parsing.py:37). The disambiguating convention already exists and is applied consistently — *Schema = pydantic, everything else = GEMS — but it is nowhere written down, and the directory layout does not reflect it.

Note that (a) and (b) are separate: renaming the folder does not fix (b), because parsing.py would still hold ModelSchema alongside model_config.


Proposed change

Step 1 — Remove the dead libs/ package

  • Delete src/gems_craft/libs/.
  • Remove the gems_craft/libs/ entry from AGENTS.md:97.
  • Update the libs/ option in .github/ISSUE_TEMPLATE/gp-02.yml to point at the new location.

Decision required before implementing. If bundled model libraries are intended to ship with GemsPy again, the correct fix is the opposite: repopulate libs/, add [tool.setuptools.package-data] so the YAMLs actually land in the wheel, and keep the package. This issue assumes they are not coming back. Please confirm.

Step 2 — Rename model/library/ and isolate the pydantic schemas

gems_craft/library/
├── schema/                                   ← 100% pydantic; every public name ends in Schema
│   ├── parsing.py
│   └── taxonomy.py
├── resolve_library.py                        ← the bridge: schema → domain (only file importing both)
├── library.py   model.py   port.py           ← library-level + model-level domain objects
└── parameter.py  variable.py  constraint.py  common.py

The schema/ boundary follows a seam that already exists in the import graph: parsing.py imports nothing from the folder, and taxonomy.py imports only parsing.py. Neither touches a domain object.

Steps 1 and 2 are deliberately bundled: Step 1 frees the name library/ (currently shadowed by libs/), and the import churn — 146 lines across 55 files (15 in src/, 36 in tests/, 4 in docs/) — is paid once instead of twice.

Step 3 — Backward compatibility

gems_craft is explicitly documented as standalone-importable for an API layer, so gems_craft/model/__init__.py is retained for one release as a deprecation shim re-exporting from gems_craft.library, emitting DeprecationWarning. Removing the shim in a later release is a Major change and needs its own issue.

Step 4 — Small correctness fixes in the same area

  • resolve_library.py:59 and :62 import model twice (via gems_craft.model and gems_craft.model.model); the second silently overwrites the first with the same object. Drop one.
  • model.py:124 — the parameter model: "Model" shadows the module-level factory model() defined 88 lines below. Rename the parameter.
  • Add a module docstring to the package __init__.py stating the convention explicitly: *Schema = pydantic mirror of the YAML (strings, no semantics); everything else = resolved GEMS domain object; model_config / model_validate / model_dump are pydantic API and unrelated to GEMS models.

Out of scope

check_library_against_taxonomy is currently unreachable from the loading pipeline — load_study never calls it, only tests/unittests/gems_craft/lib_parsing/test_taxonomy_check.py does. Deciding whether to wire it into load_study or to document it as a deliberately opt-in validation API is a separate question and should get its own issue.


Results Impact

No change to solver output values is expected or intended.

This is a pure module-layout change: file moves, import-path updates, one duplicate import removed, one local parameter renamed, and documentation. No expression parsing, constraint construction, indexing, or optimization logic is touched. No behavior is added, removed, or reordered.

This must be confirmed via tests, not assumed — see Validation Strategy.


Validation Strategy

Because no behavior should change, validation is about proving equivalence rather than checking new expected values.

  • Solver equivalence (primary): the full E2E suite under tests/e2e/ must pass unchanged, with no reference study expectations edited. Any diff to an expected-results file in this PR is a red flag and must be justified or reverted.
  • Unit tests: the tests/unittests/gems_craft/ suite covers library parsing, resolution and taxonomy checking (lib_parsing/test_taxonomy_check.py among them) and must pass with import paths updated only.
  • Deprecation shim: add a test asserting that from gems_craft.model import Model, model, PortType still resolves to the same objects as gems_craft.library, and emits DeprecationWarning.
  • Static checks: mypy is configured with disallow_untyped_defs over packages = ["gems_craft", "gems_craft_hybrid", "gems_runner"] and will catch any import path missed by the rename. black and isort --profile black must pass.
  • Dead-code claim: re-run grep -rn "lib_path\|gems_craft\.libs" src/ tests/ docs/ at review time to confirm libs/ still has no importers before deleting it.
  • Packaging: build the wheel and confirm gems_craft.library and gems_craft.library.schema are both present ([tool.setuptools.packages.find] uses where = ["src"] auto-discovery, so new subpackages are picked up automatically — worth verifying rather than assuming).

Process Checklist

Step 1 — Issue Creation

  • Issue created and linked to process GP-02

Step 2 — Triage

  • Process confirmed applicable
  • Decision: are bundled model libraries coming back? (blocks Step 1 of the proposed change)
  • Assigned to responsible contributor
  • Priority and milestone set (if applicable)

Step 3 — Impact Analysis ⚠️ extended results analysis required

  • Affected modules identified (gems_craft/model/, gems_craft/libs/, plus 55 importing files)
  • Results impact explicitly stated: no change intended
  • Breaking vs backward-compatible determined: backward-compatible while the shim is retained
  • Downstream consumers of gems_craft.model outside this repo identified and notified

Step 4 — Implementation

  • src/gems_craft/libs/ deleted (or repopulated + package-data added, per the Step 2 decision)
  • model/ renamed to library/; parsing.py and taxonomy.py moved to library/schema/
  • All 146 import references updated across src/, tests/, docs/
  • Deprecation shim added at gems_craft/model/__init__.py
  • Duplicate model import in resolve_library.py removed; shadowing parameter in model.py:124 renamed
  • Package docstring documenting the *Schema convention added

Step 5 — Testing & Validation

  • Results must not change → solver equivalence to be confirmed via the existing E2E suite
  • No reference study expected-results file modified
  • Unit tests pass with import paths updated only
  • Deprecation shim test added

Step 6 — CI Validation

  • Type checking passes (uv run mypy)
  • Formatting passes (uv run black src tests, uv run isort --profile black src tests)
  • All tests pass in CI (uv run pytest)

Step 7 — Review & Merge

  • PR reviewed; documentation changes included

Step 8 — Versioning

  • pyproject.toml version bumped (Minor)

Step 9 — Supporting Files

  • AGENTS.md updated: gems_craft/libs/ entry removed, gems_craft/model/ section renamed and the schema/ split described
  • .github/ISSUE_TEMPLATE/gp-02.yml "Affected Component" dropdown updated
  • docs/ references to gems_craft.model updated (4 occurrences)

Step 10 — Release

  • If a release is needed: follow the release process in the Developer Guidelines

Metadata

Metadata

Assignees

No one assigned

    Labels

    GP-02Internal GemsPy bug fixes, features, or improvements (not driven by a GEMS Language release)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions