Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions autotest/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,11 +414,7 @@ def synced_registry(self):
def test_registry_has_metadata(self, synced_registry):
"""Test that registry has required metadata."""
assert hasattr(synced_registry, "schema_version")
assert hasattr(synced_registry, "generated_at")
assert hasattr(synced_registry, "devtools_version")
assert synced_registry.schema_version is not None
assert synced_registry.generated_at is not None
assert synced_registry.devtools_version is not None

def test_registry_has_files(self, synced_registry):
"""Test that registry has files."""
Expand Down
3 changes: 0 additions & 3 deletions docs/md/dev/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,7 @@ The registry file contains:
Example `models.toml`:

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

[files]
"ex-gwf-twri01/mfsim.nam" = {hash = "sha256:abc123..."}
Expand Down
72 changes: 40 additions & 32 deletions docs/md/dev/programs.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,45 +168,54 @@ Each source repository must make a **program registry** file available. Program
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:

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

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

[programs.mf6.binaries.linux]
[[programs.mf6.dists]]
name = "linux"
asset = "mf6.6.3_linux.zip"
hash = "sha256:..."
exe = "bin/mf6"

[programs.mf6.binaries.mac]
asset = "mf6.6.3_macarm.zip"
[[programs.mf6.dists]]
name = "mac"
asset = "mf6.6.3_mac.zip"
hash = "sha256:..."
exe = "bin/mf6"

[programs.mf6.binaries.win64]
[[programs.mf6.dists]]
name = "win64"
asset = "mf6.6.3_win64.zip"
hash = "sha256:..."
exe = "bin/mf6.exe"

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

[programs.zbud6.binaries.linux]
[[programs.zbud6.dists]]
name = "linux"
asset = "mf6.6.3_linux.zip"
hash = "sha256:..."
exe = "bin/zbud6"

[[programs.zbud6.dists]]
name = "mac"
asset = "mf6.6.3_mac.zip"
hash = "sha256:..."

[[programs.zbud6.dists]]
name = "win64"
asset = "mf6.6.3_win64.zip"
hash = "sha256:..."
```

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

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`.

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

# Sync specific source to specific release tag
python -m modflow_devtools.programs sync --repo MODFLOW-ORG/modflow6 --tag 6.6.3
# Sync specific source to specific release version
python -m modflow_devtools.programs sync --repo MODFLOW-ORG/modflow6 --version 6.6.3

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

# Sync specific
sync_registries(repo="MODFLOW-ORG/modflow6", tag="6.6.3")
sync_registries(repo="MODFLOW-ORG/modflow6", version="6.6.3")

# Check status
status = get_sync_status()
Expand Down Expand Up @@ -573,31 +582,32 @@ Examples:

The Programs API uses a consolidated object-oriented design with Pydantic models and concrete classes.

#### ProgramBinary
#### ProgramDistribution

Represents platform-specific binary information:
Represents platform-specific distribution information:

```python
class ProgramBinary(BaseModel):
"""Platform-specific binary information."""
class ProgramDistribution(BaseModel):
"""Distribution-specific information."""
name: str # Distribution name (e.g., linux, mac, win64)
asset: str # Release asset filename
hash: str | None # SHA256 hash
exe: str # Executable path within archive
```

#### ProgramMetadata

Program metadata in registry:

Example:
```python
class ProgramMetadata(BaseModel):
"""Program metadata in registry."""
version: str
description: str | None
repo: str # Source repository (owner/name)
license: str | None
binaries: dict[str, ProgramBinary] # Platform-specific binaries
exe: str | None # Optional: defaults to bin/{program}
dists: list[ProgramDistribution] # Available distributions

def get_exe_path(self, program_name: str, platform: str | None = None) -> str:
"""Get executable path, using default if not specified."""
```

#### ProgramRegistry
Expand All @@ -608,8 +618,6 @@ Top-level registry data model:
class ProgramRegistry(BaseModel):
"""Program registry data model."""
schema_version: str | None
generated_at: datetime | None
devtools_version: str | None
programs: dict[str, ProgramMetadata]
```

Expand Down Expand Up @@ -881,7 +889,7 @@ The Programs API has been implemented following a consolidated object-oriented a
```bash
python -m modflow_devtools.programs.make_registry \
--repo MODFLOW-ORG/modflow6 \
--tag 6.6.3 \
--version 6.6.3 \
--programs mf6 zbud6 libmf6 mf5to6 \
--compute-hashes \
--output programs.toml
Expand Down
18 changes: 0 additions & 18 deletions modflow_devtools/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import urllib
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import datetime, timezone
from functools import partial
from os import PathLike
from pathlib import Path
Expand Down Expand Up @@ -150,10 +149,6 @@ class ModelRegistry(BaseModel):
"""

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

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

@field_serializer("generated_at")
def serialize_datetime(self, dt: datetime | None, _info):
"""Serialize datetime to ISO format string."""
return dt.isoformat() if dt is not None else None

def copy_to(
self, workspace: str | PathLike, model_name: str, verbose: bool = False
) -> Path | None:
Expand Down Expand Up @@ -871,8 +861,6 @@ def __init__(self) -> None:
# Initialize Pydantic parent with empty data (no metadata for local registries)
super().__init__(
schema_version=None,
generated_at=None,
devtools_version=None,
files={},
models={},
examples={},
Expand Down Expand Up @@ -1015,8 +1003,6 @@ def __init__(
# Initialize Pydantic parent with empty data (will be populated by _load())
super().__init__(
schema_version=None,
generated_at=None,
devtools_version=None,
files={},
models={},
examples={},
Expand Down Expand Up @@ -1114,8 +1100,6 @@ def _try_load_from_cache(self) -> bool:
# Store metadata from first registry
if not self.schema_version and registry.schema_version:
self.schema_version = registry.schema_version
self.generated_at = registry.generated_at
self.devtools_version = registry.devtools_version

if not self.files:
return False
Expand Down Expand Up @@ -1244,8 +1228,6 @@ def index(

registry_data = {
"schema_version": "1.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"devtools_version": modflow_devtools.__version__,
"files": remap(dict(sorted(existing_files.items())), visit=drop_none_or_empty),
"models": dict(sorted(existing_models.items())),
"examples": dict(sorted(existing_examples.items())),
Expand Down
Loading