Skip to content

Commit 750f63b

Browse files
authored
feat(dfns): specify and reimplement v2 dfn schema (#311)
Draft a new DFN specification and schema in the dfns module. Simplify the DFNs API. Clean up the older dfn module. Rewrite and reorganize some docs. Drop Python3.10 and add Python3.14. The schema mapping is messy, but temporary. Once we move to v2 upstream in mf6 it can go away. The tentative v2 spec/schema is what really matters here.
1 parent f9abb6a commit 750f63b

47 files changed

Lines changed: 6738 additions & 5219 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ jobs:
5151
steps:
5252

5353
- name: Checkout repo
54-
uses: actions/checkout@v3
54+
uses: actions/checkout@v4
5555

5656
- name: Setup uv
57-
uses: astral-sh/setup-uv@v5
57+
uses: astral-sh/setup-uv@v7
5858
with:
5959
cache-dependency-glob: "**/pyproject.toml"
6060

@@ -80,9 +80,10 @@ jobs:
8080
fail-fast: false
8181
matrix:
8282
os: [ ubuntu-22.04, macos-14, windows-2022 ]
83-
python: [ "3.10", "3.11", "3.12", "3.13" ]
83+
python: [ "3.11", "3.12", "3.13", "3.14" ]
8484
env:
8585
GCC_V: 11
86+
UV_PROJECT_ENVIRONMENT: ${{ github.workspace }}/.venv
8687
steps:
8788

8889
- name: Checkout repo
@@ -97,10 +98,11 @@ jobs:
9798
path: modflow6
9899

99100
- name: Setup uv
100-
uses: astral-sh/setup-uv@v5
101+
uses: astral-sh/setup-uv@v7
101102
with:
102103
cache-dependency-glob: "**/pyproject.toml"
103104
python-version: ${{ matrix.python }}
105+
working-directory: modflow-devtools
104106

105107
- name: Setup Fortran
106108
if: runner.os != 'Windows'
@@ -124,16 +126,16 @@ jobs:
124126
working-directory: modflow-devtools/autotest
125127
env:
126128
REPOS_PATH: ${{ github.workspace }}
129+
DFNS_PATH: ${{ github.workspace }}/modflow6/doc/mf6io/mf6ivar/dfn
127130
MODFLOW_DEVTOOLS_AUTO_SYNC: 0
128-
TEST_DFN_PATH: ${{ github.workspace }}/modflow6/doc/mf6io/mf6ivar/dfn
129131
# use --dist loadfile to so tests requiring pytest-virtualenv run on the same worker
130-
run: uv run pytest -v -n auto --dist loadfile --durations 0 --ignore test_download.py --ignore test_models.py --ignore test_dfns_registry.py
132+
run: uv run pytest -v -n auto --dist loadfile --durations 0 --ignore test_download.py --ignore test_models.py --ignore dfns/test_dfns_registry.py
131133

132134
- name: Run network-dependent tests
133135
# only invoke the GH API on one OS and Python version
134136
# to avoid rate limits (1000 rqs / hour / repository)
135137
# https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits
136-
if: runner.os == 'Linux' && matrix.python == '3.10'
138+
if: runner.os == 'Linux' && matrix.python == '3.11'
137139
working-directory: modflow-devtools/autotest
138140
env:
139141
REPOS_PATH: ${{ github.workspace }}
@@ -151,7 +153,7 @@ jobs:
151153
TEST_PROGRAMS_REPO: MODFLOW-ORG/modflow6
152154
TEST_PROGRAMS_REF: develop
153155
TEST_PROGRAMS_SOURCE: modflow6
154-
run: uv run pytest -v -n auto --dist loadgroup --durations 0 test_download.py test_models.py test_dfns_registry.py
156+
run: uv run pytest -v -n auto --dist loadgroup --durations 0 test_download.py test_models.py dfns/test_dfns_registry.py
155157

156158
rtd:
157159
name: Docs

DEVELOPER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ This document provides guidance to set up a development environment and discusse
1919

2020
## Requirements
2121

22-
Python3.10+ is currently required. This project has historically aimed to support several recent versions of Python, loosely following [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html#implementation). In current and future development this window may narrow to follow [SPEC 0](https://scientific-python.org/specs/spec-0000/#support-window) instead.
22+
Python3.11+. This project has historically aimed to support several recent versions of Python, loosely following [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html#implementation). In current and future development this window may narrow to follow [SPEC 0](https://scientific-python.org/specs/spec-0000/#support-window) instead.
2323

2424
## Installation
2525

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Python development tools for MODFLOW 6 and related projects.
4141

4242
## Requirements
4343

44-
Python3.10+, dependency-free by default.
44+
Python3.11+, dependency-free by default.
4545

4646
Two main dependency groups are available, oriented around specific use cases:
4747

autotest/dfn/__init__.py

Whitespace-only changes.

autotest/dfn/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
3+
from modflow_devtools.dfn import fetch_dfns
4+
5+
MF6_OWNER = "MODFLOW-ORG"
6+
MF6_REPO = "modflow6"
7+
MF6_REF = "develop"
8+
9+
10+
@pytest.fixture(scope="module")
11+
def dfn_dir(module_tmpdir):
12+
pytest.importorskip("boltons")
13+
path = module_tmpdir / "dfn"
14+
path.mkdir()
15+
fetch_dfns(MF6_OWNER, MF6_REPO, MF6_REF, path, verbose=True)
16+
return path

autotest/dfn/test_dfn.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from modflow_devtools.dfn import Dfn
2+
from modflow_devtools.markers import requires_pkg
3+
4+
5+
@requires_pkg("boltons")
6+
def test_load_v1(dfn_dir):
7+
common = {}
8+
common_path = dfn_dir / "common.dfn"
9+
if common_path.exists():
10+
with common_path.open() as f:
11+
common, _ = Dfn._load_v1_flat(f)
12+
names = [p.stem for p in dfn_dir.glob("*.dfn") if p.stem not in ("common", "flopy")]
13+
assert names
14+
for name in names:
15+
with (dfn_dir / f"{name}.dfn").open() as f:
16+
dfn = Dfn.load(f, name=name, common=common)
17+
assert any(dfn)
18+
19+
20+
@requires_pkg("boltons")
21+
def test_load_all(dfn_dir):
22+
dfns = Dfn.load_all(dfn_dir)
23+
assert any(dfns)

autotest/dfns/__init__.py

Whitespace-only changes.

autotest/dfns/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import os
2+
from pathlib import Path
3+
4+
import pytest
5+
6+
from modflow_devtools.dfns import fetch_dfns
7+
8+
PROJ_ROOT = Path(__file__).parents[1]
9+
10+
DFNS_REPO = os.getenv("TEST_DFNS_REPO", "MODFLOW-ORG/modflow6")
11+
DFNS_REF = os.getenv("TEST_DFNS_REF", "develop")
12+
DFNS_SOURCE = os.getenv("TEST_DFNS_SOURCE", "modflow6")
13+
DFNS_VERSION = os.getenv("TEST_DFNS_VERSION", "6.6.0")
14+
15+
16+
@pytest.fixture(scope="module")
17+
def dfn_dir(module_tmpdir):
18+
"""
19+
Path to DFN files: $DFNS_PATH if set, otherwise fetched from develop branch
20+
to a temp dir (for LocalDfnRegistry tests).
21+
"""
22+
env_var = "DFNS_PATH"
23+
if dfns_path := os.getenv(env_var):
24+
dfn_path = Path(dfns_path).expanduser().resolve()
25+
assert dfn_path.exists(), f"{env_var}={dfns_path} does not exist"
26+
assert any(dfn_path.glob("*.dfn")), f"{env_var}={dfns_path} empty"
27+
return dfn_path
28+
29+
dfns_path = module_tmpdir / "dfns"
30+
dfns_path.mkdir()
31+
owner = DFNS_REPO.split("/")[0]
32+
repo = DFNS_REPO.split("/")[1]
33+
fetch_dfns(owner, repo, DFNS_REF, dfns_path, verbose=True)
34+
return dfns_path

autotest/dfns/test_dfns.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from modflow_devtools.dfns import Dfns
2+
from modflow_devtools.markers import requires_pkg
3+
4+
5+
def test_load(dfn_dir):
6+
spec = Dfns.load(dfn_dir)
7+
assert spec.schema_version == "2"
8+
assert spec.root is not None
9+
assert spec.root.name == "sim-nam"
10+
assert len(spec.components) > 100
11+
assert "sim-nam" in spec.components
12+
assert "gwf-nam" in spec.components
13+
assert "gwf-chd" in spec.components
14+
assert "gwf-wel" in spec.components.keys()
15+
assert "garbage" not in spec.components
16+
17+
gwf_chd = spec.components["gwf-chd"]
18+
assert gwf_chd.name == "gwf-chd"
19+
assert gwf_chd.parent == "gwf-nam"
20+
21+
sim_children = spec.children("sim-nam")
22+
assert "gwf-nam" in sim_children
23+
24+
gwf_children = spec.children("gwf-nam")
25+
assert "gwf-chd" in gwf_children
26+
27+
28+
def test_load_empty_directory(function_tmpdir):
29+
spec = Dfns.load(function_tmpdir)
30+
assert len(spec.components) == 0
31+
32+
33+
# =============================================================================
34+
# CLI
35+
# =============================================================================
36+
37+
38+
@requires_pkg("pydantic")
39+
class TestCLI:
40+
def test_main_help(self):
41+
from modflow_devtools.dfns.__main__ import main
42+
43+
result = main([])
44+
assert result == 0
45+
46+
def test_info_command(self):
47+
from modflow_devtools.dfns.__main__ import main
48+
49+
result = main(["info"])
50+
assert result == 0
51+
52+
def test_clean_command(self):
53+
from modflow_devtools.dfns.__main__ import main
54+
55+
result = main(["clean"])
56+
assert result == 0

0 commit comments

Comments
 (0)