Skip to content

Commit c8a1368

Browse files
authored
feat(models): improve models API (#268)
Reimplement programmatic access to models for looser coupling and better developer/user experience. Instead of baking a model registry into the package, let repositories publish their own registries, discoverable by devtools. Allows devtools and model repositories to move independently, and allows explicit model versioning. See the design document for more detail.
1 parent 7adce08 commit c8a1368

33 files changed

+3310
-42863
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ jobs:
105105
env:
106106
REPOS_PATH: ${{ github.workspace }}
107107
# use --dist loadfile to so tests requiring pytest-virtualenv run on the same worker
108-
run: uv run pytest -v -n auto --dist loadfile --durations 0 --ignore test_download.py
108+
run: uv run pytest -v -n auto --dist loadfile --durations 0 --ignore test_download.py --ignore test_models.py
109109

110110
- name: Run network-dependent tests
111111
# only invoke the GH API on one OS and Python version
@@ -116,7 +116,11 @@ jobs:
116116
env:
117117
REPOS_PATH: ${{ github.workspace }}
118118
GITHUB_TOKEN: ${{ github.token }}
119-
run: uv run pytest -v -n auto --durations 0 test_download.py
119+
TEST_REPO: wpbonelli/modflow6-testmodels
120+
TEST_REF: registry
121+
TEST_SOURCE: modflow6-testmodels
122+
TEST_SOURCE_NAME: mf6/test
123+
run: uv run pytest -v -n auto --dist loadgroup --durations 0 test_download.py test_models.py
120124

121125
rtd:
122126
name: Docs

autotest/mf6examples.zip.lock

Whitespace-only changes.

autotest/test_build.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@
1515
_modflow6_repo_path = _repos_path / "modflow6"
1616
_system = platform.system()
1717
_exe_ext = ".exe" if _system == "Windows" else ""
18-
_lib_ext = (
19-
".so" if _system == "Linux" else (".dylib" if _system == "Darwin" else ".dll")
20-
)
18+
_lib_ext = ".so" if _system == "Linux" else (".dylib" if _system == "Darwin" else ".dll")
2119

2220

2321
@requires_exe("meson", "ninja")
24-
@pytest.mark.skipif(
25-
not _modflow6_repo_path.is_dir(), reason="modflow6 repository not found"
26-
)
22+
@pytest.mark.skipif(not _modflow6_repo_path.is_dir(), reason="modflow6 repository not found")
2723
def test_meson_build(tmp_path):
2824
build_path = tmp_path / "builddir"
2925
bin_path = tmp_path / "bin"

autotest/test_dfn.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ def pytest_generate_tests(metafunc):
2020
if not any(DFN_DIR.glob("*.dfn")):
2121
get_dfns(MF6_OWNER, MF6_REPO, MF6_REF, DFN_DIR, verbose=True)
2222
dfn_names = [
23-
dfn.stem
24-
for dfn in DFN_DIR.glob("*.dfn")
25-
if dfn.stem not in ["common", "flopy"]
23+
dfn.stem for dfn in DFN_DIR.glob("*.dfn") if dfn.stem not in ["common", "flopy"]
2624
]
2725
metafunc.parametrize("dfn_name", dfn_names, ids=dfn_names)
2826

autotest/test_download.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_get_release(repo):
4545
tag = "latest"
4646
release = get_release(repo, tag, verbose=True)
4747
assets = release["assets"]
48-
expected_names = ["linux.zip", "mac.zip", "win64.zip"]
48+
expected_names = ["linux.zip", "macarm.zip", "win64.zip"]
4949
actual_names = [asset["name"] for asset in assets]
5050

5151
if repo == "MODFLOW-ORG/modflow6":

autotest/test_fixtures.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ def test_function_scoped_tmpdir_slash_in_name(function_tmpdir, name):
5555
)
5656
assert (
5757
f"{inspect.currentframe().f_code.co_name}_{replaced1}_" in function_tmpdir.stem
58-
or f"{inspect.currentframe().f_code.co_name}_{replaced2}_"
59-
in function_tmpdir.stem
58+
or f"{inspect.currentframe().f_code.co_name}_{replaced2}_" in function_tmpdir.stem
6059
)
6160

6261

@@ -157,9 +156,7 @@ def test_keep_class_scoped_tmpdir(tmp_path, arg):
157156
tmp_path,
158157
]
159158
assert pytest.main(args) == ExitCode.OK
160-
assert (
161-
tmp_path / f"{TestKeepClassScopedTmpdirInner.__name__}0" / test_keep_fname
162-
).is_file()
159+
assert (tmp_path / f"{TestKeepClassScopedTmpdirInner.__name__}0" / test_keep_fname).is_file()
163160

164161

165162
@pytest.mark.parametrize("arg", ["--keep", "-K"])

autotest/test_misc.py

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ def test_set_env():
4343
assert environ.get(key) is None
4444

4545

46-
_repos_path = environ.get("REPOS_PATH")
47-
if _repos_path is None:
48-
_repos_path = Path(__file__).parent.parent.parent.parent
49-
_repos_path = Path(_repos_path).expanduser().absolute()
46+
_repos_path_str = environ.get("REPOS_PATH")
47+
if _repos_path_str is None:
48+
_repos_path: Path = Path(__file__).parent.parent.parent.parent
49+
else:
50+
_repos_path = Path(_repos_path_str).expanduser().absolute()
5051
_testmodels_repo_path = _repos_path / "modflow6-testmodels"
5152
_testmodels_repo_paths_mf6 = sorted((_testmodels_repo_path / "mf6").glob("test*"))
5253
_testmodels_repo_paths_mf5to6 = sorted((_testmodels_repo_path / "mf5to6").glob("test*"))
@@ -57,9 +58,7 @@ def test_set_env():
5758
_example_paths = sorted(_examples_path.glob("ex-*")) if _examples_path.is_dir() else []
5859

5960

60-
@pytest.mark.skipif(
61-
not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found"
62-
)
61+
@pytest.mark.skipif(not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found")
6362
def test_get_packages():
6463
model_path = _testmodels_repo_paths_mf6[0]
6564

@@ -79,9 +78,7 @@ def test_get_packages():
7978
assert "ims" not in packages
8079

8180

82-
@pytest.mark.skipif(
83-
not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found"
84-
)
81+
@pytest.mark.skipif(not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found")
8582
def test_get_packages_fails_on_invalid_namefile(module_tmpdir):
8683
model_path = _testmodels_repo_paths_mf6[0]
8784
new_model_path = module_tmpdir / model_path.name
@@ -132,7 +129,7 @@ def get_expected_model_dirs(path, pattern="mfsim.nam") -> list[Path]:
132129

133130

134131
def get_expected_namefiles(path, pattern="mfsim.nam") -> list[Path]:
135-
folders = []
132+
folders: list[Path] = []
136133
for root, dirs, _ in os.walk(path):
137134
for d in dirs:
138135
p = Path(root) / d
@@ -154,9 +151,7 @@ def test_get_model_paths_examples():
154151
assert set(expected_paths) == set(paths)
155152

156153

157-
@pytest.mark.skipif(
158-
not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found"
159-
)
154+
@pytest.mark.skipif(not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found")
160155
def test_get_model_paths_largetestmodels():
161156
expected_paths = get_expected_model_dirs(_examples_path)
162157
paths = get_model_paths(_examples_path)
@@ -173,9 +168,7 @@ def test_get_model_paths_largetestmodels():
173168
not any(_largetestmodel_paths) or not any(_example_paths),
174169
reason="repos not found",
175170
)
176-
@pytest.mark.parametrize(
177-
"models", [(_examples_path, 63), (_largetestmodels_repo_path, 16)]
178-
)
171+
@pytest.mark.parametrize("models", [(_examples_path, 63), (_largetestmodels_repo_path, 16)])
179172
def test_get_model_paths_exclude_patterns(models):
180173
path, expected_count = models
181174
paths = get_model_paths(path, excluded=["gwt"])
@@ -195,9 +188,7 @@ def test_get_namefile_paths_examples():
195188
assert set(expected_paths) == set(paths)
196189

197190

198-
@pytest.mark.skipif(
199-
not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found"
200-
)
191+
@pytest.mark.skipif(not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found")
201192
def test_get_namefile_paths_largetestmodels():
202193
expected_paths = get_expected_namefiles(_largetestmodels_repo_path)
203194
paths = get_namefile_paths(_largetestmodels_repo_path)
@@ -214,9 +205,7 @@ def test_get_namefile_paths_largetestmodels():
214205
not any(_largetestmodel_paths) or not any(_example_paths),
215206
reason="repos not found",
216207
)
217-
@pytest.mark.parametrize(
218-
"models", [(_examples_path, 43), (_largetestmodels_repo_path, 19)]
219-
)
208+
@pytest.mark.parametrize("models", [(_examples_path, 43), (_largetestmodels_repo_path, 19)])
220209
def test_get_namefile_paths_exclude_patterns(models):
221210
path, expected_count = models
222211
paths = get_namefile_paths(path, excluded=["gwf"])

0 commit comments

Comments
 (0)