Skip to content

Commit 1e94d1b

Browse files
authored
fix: fix version comparison, improve autosync behavior (#300)
* dfn schema versions needed to be coerced to packaging.version.Version before comparison * invert autosync flag semantics, default false while APIs are experimental, improve docstrings * add a top-level mf sync command that syncs all three APIs (dfns, models, programs), sneaking this in here as this is all still experimental anyway
1 parent 2dd65bf commit 1e94d1b

File tree

12 files changed

+120
-42
lines changed

12 files changed

+120
-42
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ jobs:
124124
working-directory: modflow-devtools/autotest
125125
env:
126126
REPOS_PATH: ${{ github.workspace }}
127-
MODFLOW_DEVTOOLS_NO_AUTO_SYNC: 1
127+
MODFLOW_DEVTOOLS_AUTO_SYNC: 0
128128
TEST_DFN_PATH: ${{ github.workspace }}/modflow6/doc/mf6io/mf6ivar/dfn
129129
# use --dist loadfile to so tests requiring pytest-virtualenv run on the same worker
130130
run: uv run pytest -v -n auto --dist loadfile --durations 0 --ignore test_download.py --ignore test_models.py --ignore test_dfns_registry.py

docs/md/dev/dfns.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ status = get_sync_status()
476476
- **At install time**: Best-effort sync to default refs during package installation (fail silently on network errors)
477477
- **On first use**: If registry cache is empty for requested ref, attempt to sync before raising errors
478478
- **Lazy loading**: Don't sync until DFN access is actually requested
479-
- **Configurable**: Users can disable auto-sync via environment variable: `MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1`
479+
- **Configurable (Experimental)**: Auto-sync is opt-in via environment variable: `MODFLOW_DEVTOOLS_AUTO_SYNC=1` (set to "1", "true", or "yes")
480480

481481
### Source repository integration
482482

@@ -1295,7 +1295,7 @@ dfn.name # attribute access
12951295
5. Implement `sync_dfns()` function
12961296
6. Add registry metadata caching with hash verification
12971297
7. Implement version-controlled registry discovery
1298-
8. Add auto-sync on first use (with opt-out via `MODFLOW_DEVTOOLS_NO_AUTO_SYNC`)
1298+
8. Add auto-sync on first use (opt-in via `MODFLOW_DEVTOOLS_AUTO_SYNC` while experimental)
12991299
9. **Implement `DfnSpec` dataclass** with `Mapping` protocol for single canonical hierarchical representation with flat dict access
13001300

13011301
**CLI and module API** (depends on Registry infrastructure):

docs/md/dev/programs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ status = get_sync_status()
509509

510510
- **At install time**: Best-effort sync during package installation (fail silently on network errors)
511511
- **On first use**: If registry cache is empty, attempt to sync before raising errors
512-
- **Configurable**: Users can disable auto-sync via environment variable: `MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1`
512+
- **Configurable (Experimental)**: Auto-sync is opt-in via environment variable: `MODFLOW_DEVTOOLS_AUTO_SYNC=1` (set to "1", "true", or "yes")
513513

514514
#### Force semantics
515515

docs/md/models.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ python -m modflow_devtools.models cp mf6/example/ex-gwf-twri01 /path/to/workspac
156156
```
157157

158158
The copy command:
159-
- Automatically attempts to sync registries before copying (unless `MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1`)
159+
- Automatically attempts to sync registries before copying (if `MODFLOW_DEVTOOLS_AUTO_SYNC=1`)
160160
- Creates the workspace directory if it doesn't exist
161161
- Copies all input files for the specified model
162162
- Preserves subdirectory structure within the workspace
@@ -316,16 +316,16 @@ mf models clear --force
316316

317317
## Automatic Synchronization
318318

319-
By default, `modflow-devtools` attempts to sync registries:
320-
- On first import (best-effort, fails silently on network errors)
321-
- When accessing models (unless `MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1`)
322-
323-
To disable auto-sync:
319+
Auto-sync is **opt-in** (experimental). To enable:
324320

325321
```bash
326-
export MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1
322+
export MODFLOW_DEVTOOLS_AUTO_SYNC=1 # or "true" or "yes"
327323
```
328324

325+
When enabled, `modflow-devtools` attempts to sync registries:
326+
- On first access (best-effort, fails silently on network errors)
327+
- When accessing models via the API or CLI
328+
329329
Then manually sync when needed:
330330

331331
```bash

docs/md/programs.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,17 +347,17 @@ mf programs install mf6 --force
347347
348348
## Automatic Synchronization
349349
350-
By default, `modflow-devtools` attempts to sync registries:
351-
- On first import (best-effort, fails silently on network errors)
352-
- Before installation (unless `MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1`)
353-
- Before listing available programs
354-
355-
To disable auto-sync:
350+
Auto-sync is **opt-in** (experimental). To enable:
356351
357352
```bash
358-
export MODFLOW_DEVTOOLS_NO_AUTO_SYNC=1
353+
export MODFLOW_DEVTOOLS_AUTO_SYNC=1 # or "true" or "yes"
359354
```
360355
356+
When enabled, `modflow-devtools` attempts to sync registries:
357+
- On first access (best-effort, fails silently on network errors)
358+
- Before installation
359+
- Before listing available programs
360+
361361
Then manually sync when needed:
362362
363363
```bash

modflow_devtools/cli.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Root CLI for modflow-devtools.
33
44
Usage:
5+
mf sync
6+
mf dfns sync
7+
mf dfns info
8+
mf dfns list
9+
mf dfns clean
510
mf models sync
611
mf models info
712
mf models list
@@ -17,6 +22,56 @@
1722

1823
import argparse
1924
import sys
25+
import warnings
26+
27+
28+
def _sync_all():
29+
"""Sync all registries (dfns, models, programs)."""
30+
print("Syncing all registries...")
31+
print()
32+
33+
# Sync DFNs
34+
print("=== DFNs ===")
35+
try:
36+
from modflow_devtools.dfns.registry import sync_dfns
37+
38+
registries = sync_dfns()
39+
for registry in registries:
40+
meta = registry.registry_meta
41+
print(f" {registry.ref}: {len(meta.files)} files")
42+
print(f"Synced {len(registries)} DFN registry(ies)")
43+
except Exception as e:
44+
print(f"Error syncing DFNs: {e}")
45+
print()
46+
47+
# Sync Models
48+
print("=== Models ===")
49+
try:
50+
from modflow_devtools.models import ModelSourceConfig
51+
52+
config = ModelSourceConfig.load()
53+
config.sync()
54+
print("Models synced successfully")
55+
except Exception as e:
56+
print(f"Error syncing models: {e}")
57+
print()
58+
59+
# Sync Programs
60+
print("=== Programs ===")
61+
try:
62+
# Suppress experimental warning
63+
with warnings.catch_warnings():
64+
warnings.filterwarnings("ignore", message=".*modflow_devtools.programs.*experimental.*")
65+
from modflow_devtools.programs import ProgramSourceConfig
66+
67+
config = ProgramSourceConfig.load()
68+
config.sync()
69+
print("Programs synced successfully")
70+
except Exception as e:
71+
print(f"Error syncing programs: {e}")
72+
print()
73+
74+
print("All registries synced!")
2075

2176

2277
def main():
@@ -27,6 +82,12 @@ def main():
2782
)
2883
subparsers = parser.add_subparsers(dest="subcommand", help="Available commands")
2984

85+
# Sync subcommand (syncs all APIs)
86+
subparsers.add_parser("sync", help="Sync all registries (dfns, models, programs)")
87+
88+
# DFNs subcommand
89+
subparsers.add_parser("dfns", help="Manage MODFLOW 6 definition files")
90+
3091
# Models subcommand
3192
subparsers.add_parser("models", help="Manage MODFLOW model registries")
3293

@@ -41,7 +102,14 @@ def main():
41102
sys.exit(1)
42103

43104
# Dispatch to the appropriate module CLI with remaining args
44-
if args.subcommand == "models":
105+
if args.subcommand == "sync":
106+
_sync_all()
107+
elif args.subcommand == "dfns":
108+
from modflow_devtools.dfns.__main__ import main as dfns_main
109+
110+
sys.argv = ["mf dfns", *remaining]
111+
sys.exit(dfns_main())
112+
elif args.subcommand == "models":
45113
from modflow_devtools.models.__main__ import main as models_main
46114

47115
# Replace sys.argv to make it look like we called the submodule directly

modflow_devtools/dfns/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,12 @@ def map(
575575
schema_version: str | Version = "2",
576576
) -> Dfn:
577577
"""Map a MODFLOW 6 specification to another schema version."""
578-
if dfn.schema_version == schema_version:
578+
version = Version(str(schema_version))
579+
if version == dfn.schema_version:
579580
return dfn
580-
elif Version(str(schema_version)) == Version("1"):
581+
elif version == Version("1"):
581582
raise NotImplementedError("Mapping to schema version 1 is not implemented yet.")
582-
elif Version(str(schema_version)) == Version("2"):
583+
elif version == Version("2"):
583584
return MapV1To2().map(dfn)
584585
raise ValueError(f"Unsupported schema version: {schema_version}. Expected 1 or 2.")
585586

modflow_devtools/dfns/__main__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Command-line interface for the DFNs API.
33
44
Usage:
5-
python -m modflow_devtools.dfns sync [--ref REF] [--force]
6-
python -m modflow_devtools.dfns info
7-
python -m modflow_devtools.dfns list [--ref REF]
8-
python -m modflow_devtools.dfns clean [--all]
5+
mf dfns sync [--ref REF] [--force]
6+
mf dfns info
7+
mf dfns list [--ref REF]
8+
mf dfns clean [--all]
99
"""
1010

1111
from __future__ import annotations
@@ -138,7 +138,7 @@ def cmd_list(args: argparse.Namespace) -> int:
138138

139139
except DfnRegistryNotFoundError as e:
140140
print(f"Error: {e}", file=sys.stderr)
141-
print("Try running 'python -m modflow_devtools.dfn sync' first.", file=sys.stderr)
141+
print("Try running 'mf dfns sync' first.", file=sys.stderr)
142142
return 1
143143
except Exception as e:
144144
print(f"Error: {e}", file=sys.stderr)
@@ -198,7 +198,7 @@ def _format_size(size_bytes: int) -> str:
198198
def main(argv: list[str] | None = None) -> int:
199199
"""Main entry point for the CLI."""
200200
parser = argparse.ArgumentParser(
201-
prog="python -m modflow_devtools.dfn",
201+
prog="mf dfns",
202202
description="MODFLOW 6 definition file tools",
203203
)
204204
parser.add_argument(

modflow_devtools/dfns/registry.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ def get_sync_status(source: str = "modflow6") -> dict[str, bool]:
734734
def get_registry(
735735
source: str = "modflow6",
736736
ref: str = "develop",
737-
auto_sync: bool = True,
737+
auto_sync: bool = False,
738738
path: str | PathLike | None = None,
739739
) -> DfnRegistry:
740740
"""
@@ -747,8 +747,9 @@ def get_registry(
747747
ref : str, optional
748748
Git ref (branch, tag, or commit hash). Default is "develop".
749749
auto_sync : bool, optional
750-
If True and registry is not cached, automatically sync. Default is True.
751-
Can be disabled via MODFLOW_DEVTOOLS_NO_AUTO_SYNC environment variable.
750+
If True and registry is not cached, automatically sync. Default is False
751+
(opt-in while experimental). Can be enabled via MODFLOW_DEVTOOLS_AUTO_SYNC
752+
environment variable (set to "1", "true", or "yes").
752753
Ignored when path is provided.
753754
path : str or PathLike, optional
754755
Path to a local directory containing DFN files. If provided, returns
@@ -775,9 +776,9 @@ def get_registry(
775776
if path is not None:
776777
return LocalDfnRegistry(path=Path(path), source=source, ref=ref)
777778

778-
# Check for auto-sync opt-out
779-
if os.environ.get("MODFLOW_DEVTOOLS_NO_AUTO_SYNC", "").lower() in ("1", "true", "yes"):
780-
auto_sync = False
779+
# Check for auto-sync opt-in (experimental - off by default)
780+
if os.environ.get("MODFLOW_DEVTOOLS_AUTO_SYNC", "").lower() in ("1", "true", "yes"):
781+
auto_sync = True
781782

782783
registry = RemoteDfnRegistry(source=source, ref=ref)
783784

modflow_devtools/models/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,15 +1062,15 @@ def _load(self):
10621062
Load registry data from cache.
10631063
10641064
Raises an error if no cached registries are found.
1065-
Run 'python -m modflow_devtools.models sync' to populate the cache.
1065+
Run 'mf models sync' to populate the cache.
10661066
"""
10671067
# Try to load from cache
10681068
loaded_from_cache = self._try_load_from_cache()
10691069

10701070
if not loaded_from_cache:
10711071
raise RuntimeError(
10721072
"No model registries found in cache. "
1073-
"Run 'python -m modflow_devtools.models sync' to download registries, "
1073+
"Run 'mf models sync' to download registries, "
10741074
"or use ModelSourceConfig.load().sync() programmatically."
10751075
)
10761076

@@ -1322,13 +1322,19 @@ def get_default_registry():
13221322
This allows the module to import successfully even if the cache
13231323
is empty, with a clear error message on first use.
13241324
1325+
Auto-sync can be enabled via MODFLOW_DEVTOOLS_AUTO_SYNC environment variable
1326+
(currently opt-in while experimental). Set to "1", "true", or "yes" to enable.
1327+
13251328
Returns
13261329
-------
13271330
PoochRegistry
13281331
The default model registry
13291332
"""
13301333
global _default_registry_cache
13311334
if _default_registry_cache is None:
1335+
# Opt-in auto-sync (experimental - off by default)
1336+
if os.environ.get("MODFLOW_DEVTOOLS_AUTO_SYNC", "").lower() in ("1", "true", "yes"):
1337+
_try_best_effort_sync()
13321338
_default_registry_cache = PoochRegistry(base_url=_DEFAULT_BASE_URL, env=_DEFAULT_ENV)
13331339
return _default_registry_cache
13341340

0 commit comments

Comments
 (0)