Skip to content

Commit 75c6909

Browse files
authored
refactor(registries): leaner model/program registry file contents (#279)
1 parent 813a9c3 commit 75c6909

6 files changed

Lines changed: 107 additions & 140 deletions

File tree

autotest/test_models.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,7 @@ def synced_registry(self):
414414
def test_registry_has_metadata(self, synced_registry):
415415
"""Test that registry has required metadata."""
416416
assert hasattr(synced_registry, "schema_version")
417-
assert hasattr(synced_registry, "generated_at")
418-
assert hasattr(synced_registry, "devtools_version")
419417
assert synced_registry.schema_version is not None
420-
assert synced_registry.generated_at is not None
421-
assert synced_registry.devtools_version is not None
422418

423419
def test_registry_has_files(self, synced_registry):
424420
"""Test that registry has files."""

docs/md/dev/models.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,7 @@ The registry file contains:
181181
Example `models.toml`:
182182

183183
```toml
184-
# Metadata (top-level)
185184
schema_version = "1.0"
186-
generated_at = "2025-12-04T14:30:00Z"
187-
devtools_version = "1.9.0"
188185

189186
[files]
190187
"ex-gwf-twri01/mfsim.nam" = {hash = "sha256:abc123..."}

docs/md/dev/programs.md

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -168,45 +168,54 @@ Each source repository must make a **program registry** file available. Program
168168
Registry files shall be named **`programs.toml`** (not `registry.toml` - the specific naming distinguishes it from the Models and DFNs registries) and contain, at minimum, a dictionary `programs` enumerating programs provided by the source repository. For instance:
169169

170170
```toml
171-
# Metadata (top-level)
172171
schema_version = "1.0"
173-
generated_at = "2025-12-29T10:30:00Z"
174-
devtools_version = "2.0.0"
175172

176173
[programs.mf6]
177-
version = "6.6.3"
174+
# Optional: exe defaults to "bin/mf6" (or "bin/mf6.exe" on Windows), only specify if different
178175
description = "MODFLOW 6 groundwater flow model"
179-
repo = "MODFLOW-ORG/modflow6"
180176
license = "CC0-1.0"
181177

182-
[programs.mf6.binaries.linux]
178+
[[programs.mf6.dists]]
179+
name = "linux"
183180
asset = "mf6.6.3_linux.zip"
184181
hash = "sha256:..."
185-
exe = "bin/mf6"
186182

187-
[programs.mf6.binaries.mac]
188-
asset = "mf6.6.3_macarm.zip"
183+
[[programs.mf6.dists]]
184+
name = "mac"
185+
asset = "mf6.6.3_mac.zip"
189186
hash = "sha256:..."
190-
exe = "bin/mf6"
191187

192-
[programs.mf6.binaries.win64]
188+
[[programs.mf6.dists]]
189+
name = "win64"
193190
asset = "mf6.6.3_win64.zip"
194191
hash = "sha256:..."
195-
exe = "bin/mf6.exe"
196192

197193
[programs.zbud6]
198-
version = "6.6.3"
194+
# exe defaults to "bin/zbud6" (or "bin/zbud6.exe" on Windows)
199195
description = "MODFLOW 6 Zonebudget utility"
200-
repo = "MODFLOW-ORG/modflow6"
201196
license = "CC0-1.0"
202197

203-
[programs.zbud6.binaries.linux]
198+
[[programs.zbud6.dists]]
199+
name = "linux"
204200
asset = "mf6.6.3_linux.zip"
205201
hash = "sha256:..."
206-
exe = "bin/zbud6"
202+
203+
[[programs.zbud6.dists]]
204+
name = "mac"
205+
asset = "mf6.6.3_mac.zip"
206+
hash = "sha256:..."
207+
208+
[[programs.zbud6.dists]]
209+
name = "win64"
210+
asset = "mf6.6.3_win64.zip"
211+
hash = "sha256:..."
207212
```
208213

209-
The top-level metadata is optional; if a `schema_version` is not provided it will be inferred if possible.
214+
**Simplified format notes**:
215+
- Version and repository information come from the release tag and bootstrap configuration, not from the registry file
216+
- The `exe` field is optional and defaults to `bin/{program}` (with `.exe` automatically added on Windows)
217+
- Only specify `exe` when the executable location differs from the default
218+
- The `schema_version` field is optional but recommended for future compatibility
210219

211220
Platform identifiers are as defined in the [modflow-devtools OS tag specification](https://modflow-devtools.readthedocs.io/en/latest/md/ostags.html): `linux`, `mac`, `win64`.
212221

@@ -394,8 +403,8 @@ Exposed as a CLI command and Python API:
394403
# Sync all configured sources and release tags
395404
python -m modflow_devtools.programs sync
396405

397-
# Sync specific source to specific release tag
398-
python -m modflow_devtools.programs sync --repo MODFLOW-ORG/modflow6 --tag 6.6.3
406+
# Sync specific source to specific release version
407+
python -m modflow_devtools.programs sync --repo MODFLOW-ORG/modflow6 --version 6.6.3
399408

400409
# Force re-download
401410
python -m modflow_devtools.programs sync --force
@@ -416,7 +425,7 @@ from modflow_devtools.programs import sync_registries, get_sync_status
416425
sync_registries()
417426

418427
# Sync specific
419-
sync_registries(repo="MODFLOW-ORG/modflow6", tag="6.6.3")
428+
sync_registries(repo="MODFLOW-ORG/modflow6", version="6.6.3")
420429

421430
# Check status
422431
status = get_sync_status()
@@ -573,31 +582,32 @@ Examples:
573582
574583
The Programs API uses a consolidated object-oriented design with Pydantic models and concrete classes.
575584
576-
#### ProgramBinary
585+
#### ProgramDistribution
577586
578-
Represents platform-specific binary information:
587+
Represents platform-specific distribution information:
579588
580589
```python
581-
class ProgramBinary(BaseModel):
582-
"""Platform-specific binary information."""
590+
class ProgramDistribution(BaseModel):
591+
"""Distribution-specific information."""
592+
name: str # Distribution name (e.g., linux, mac, win64)
583593
asset: str # Release asset filename
584594
hash: str | None # SHA256 hash
585-
exe: str # Executable path within archive
586595
```
587596
588597
#### ProgramMetadata
589598
590599
Program metadata in registry:
591600
592-
Example:
593601
```python
594602
class ProgramMetadata(BaseModel):
595603
"""Program metadata in registry."""
596-
version: str
597604
description: str | None
598-
repo: str # Source repository (owner/name)
599605
license: str | None
600-
binaries: dict[str, ProgramBinary] # Platform-specific binaries
606+
exe: str | None # Optional: defaults to bin/{program}
607+
dists: list[ProgramDistribution] # Available distributions
608+
609+
def get_exe_path(self, program_name: str, platform: str | None = None) -> str:
610+
"""Get executable path, using default if not specified."""
601611
```
602612
603613
#### ProgramRegistry
@@ -608,8 +618,6 @@ Top-level registry data model:
608618
class ProgramRegistry(BaseModel):
609619
"""Program registry data model."""
610620
schema_version: str | None
611-
generated_at: datetime | None
612-
devtools_version: str | None
613621
programs: dict[str, ProgramMetadata]
614622
```
615623
@@ -881,7 +889,7 @@ The Programs API has been implemented following a consolidated object-oriented a
881889
```bash
882890
python -m modflow_devtools.programs.make_registry \
883891
--repo MODFLOW-ORG/modflow6 \
884-
--tag 6.6.3 \
892+
--version 6.6.3 \
885893
--programs mf6 zbud6 libmf6 mf5to6 \
886894
--compute-hashes \
887895
--output programs.toml

modflow_devtools/models/__init__.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import urllib
44
from collections.abc import Callable
55
from dataclasses import dataclass, field
6-
from datetime import datetime, timezone
76
from functools import partial
87
from os import PathLike
98
from pathlib import Path
@@ -150,10 +149,6 @@ class ModelRegistry(BaseModel):
150149
"""
151150

152151
schema_version: str | None = Field(None, description="Registry schema version")
153-
generated_at: datetime | None = Field(None, description="Timestamp when registry was generated")
154-
devtools_version: str | None = Field(
155-
None, description="Version of modflow-devtools used to generate"
156-
)
157152
files: dict[str, ModelInputFile] = Field(
158153
default_factory=dict, description="Map of file names to file entries"
159154
)
@@ -166,11 +161,6 @@ class ModelRegistry(BaseModel):
166161

167162
model_config = {"arbitrary_types_allowed": True, "populate_by_name": True}
168163

169-
@field_serializer("generated_at")
170-
def serialize_datetime(self, dt: datetime | None, _info):
171-
"""Serialize datetime to ISO format string."""
172-
return dt.isoformat() if dt is not None else None
173-
174164
def copy_to(
175165
self, workspace: str | PathLike, model_name: str, verbose: bool = False
176166
) -> Path | None:
@@ -871,8 +861,6 @@ def __init__(self) -> None:
871861
# Initialize Pydantic parent with empty data (no metadata for local registries)
872862
super().__init__(
873863
schema_version=None,
874-
generated_at=None,
875-
devtools_version=None,
876864
files={},
877865
models={},
878866
examples={},
@@ -1015,8 +1003,6 @@ def __init__(
10151003
# Initialize Pydantic parent with empty data (will be populated by _load())
10161004
super().__init__(
10171005
schema_version=None,
1018-
generated_at=None,
1019-
devtools_version=None,
10201006
files={},
10211007
models={},
10221008
examples={},
@@ -1114,8 +1100,6 @@ def _try_load_from_cache(self) -> bool:
11141100
# Store metadata from first registry
11151101
if not self.schema_version and registry.schema_version:
11161102
self.schema_version = registry.schema_version
1117-
self.generated_at = registry.generated_at
1118-
self.devtools_version = registry.devtools_version
11191103

11201104
if not self.files:
11211105
return False
@@ -1244,8 +1228,6 @@ def index(
12441228

12451229
registry_data = {
12461230
"schema_version": "1.0",
1247-
"generated_at": datetime.now(timezone.utc).isoformat(),
1248-
"devtools_version": modflow_devtools.__version__,
12491231
"files": remap(dict(sorted(existing_files.items())), visit=drop_none_or_empty),
12501232
"models": dict(sorted(existing_models.items())),
12511233
"examples": dict(sorted(existing_examples.items())),

0 commit comments

Comments
 (0)