Skip to content

Commit 526e538

Browse files
authored
fix(dfn): distinguish file format from schema version (#233)
Up to now we conflated the format of definition files (DFN vs TOML) with the data schema we expect their contents to follow. Separate these concerns. Add a schema conversion layer for v1 -> v2 transformations. Refactoring/usability-wise, make Dfn a dataclass instead of a frankenstein typed dict. Miscellaneous other tidying. And drop the renaming of simulation and model components without "-nam". We can do that later, for now stay consistent.
1 parent 5832feb commit 526e538

File tree

13 files changed

+970
-929
lines changed

13 files changed

+970
-929
lines changed

autotest/test_dfn.py

Lines changed: 67 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22

33
import pytest
44

5-
from modflow_devtools.dfn import Dfn, get_dfns
5+
from modflow_devtools.dfn import _load_common, load, load_flat
6+
from modflow_devtools.dfn.fetch import fetch_dfns
67
from modflow_devtools.dfn2toml import convert
78
from modflow_devtools.markers import requires_pkg
89

910
PROJ_ROOT = Path(__file__).parents[1]
1011
DFN_DIR = PROJ_ROOT / "autotest" / "temp" / "dfn"
1112
TOML_DIR = DFN_DIR / "toml"
12-
VERSIONS = {1: DFN_DIR, 2: TOML_DIR}
13+
SPEC_DIRS = {1: DFN_DIR, 2: TOML_DIR}
1314
MF6_OWNER = "MODFLOW-ORG"
1415
MF6_REPO = "modflow6"
1516
MF6_REF = "develop"
17+
EMPTY_DFNS = {"exg-gwfgwe", "exg-gwfgwt", "exg-gwfprt", "sln-ems"}
1618

1719

1820
def pytest_generate_tests(metafunc):
1921
if "dfn_name" in metafunc.fixturenames:
2022
if not any(DFN_DIR.glob("*.dfn")):
21-
get_dfns(MF6_OWNER, MF6_REPO, MF6_REF, DFN_DIR, verbose=True)
23+
fetch_dfns(MF6_OWNER, MF6_REPO, MF6_REF, DFN_DIR, verbose=True)
2224
dfn_names = [
2325
dfn.stem
2426
for dfn in DFN_DIR.glob("*.dfn")
@@ -28,12 +30,10 @@ def pytest_generate_tests(metafunc):
2830

2931
if "toml_name" in metafunc.fixturenames:
3032
convert(DFN_DIR, TOML_DIR)
31-
dfn_paths = list(DFN_DIR.glob("*.dfn"))
32-
assert all(
33-
(TOML_DIR / f"{dfn.stem.replace('-nam', '')}.toml").is_file()
34-
for dfn in dfn_paths
35-
if "common" not in dfn.stem
36-
)
33+
expected_toml_paths = [
34+
dfn for dfn in DFN_DIR.glob("*.dfn") if "common" not in dfn.stem
35+
]
36+
assert all(toml_path.exists() for toml_path in expected_toml_paths)
3737
toml_names = [toml.stem for toml in TOML_DIR.glob("*.toml")]
3838
metafunc.parametrize("toml_name", toml_names, ids=toml_names)
3939

@@ -44,82 +44,73 @@ def test_load_v1(dfn_name):
4444
(DFN_DIR / "common.dfn").open() as common_file,
4545
(DFN_DIR / f"{dfn_name}.dfn").open() as dfn_file,
4646
):
47-
common, _ = Dfn._load_v1_flat(common_file)
48-
dfn = Dfn.load(dfn_file, name=dfn_name, common=common)
49-
assert any(dfn)
47+
common = _load_common(common_file)
48+
dfn = load(dfn_file, name=dfn_name, format="dfn", common=common)
49+
assert any(dfn.fields) == (dfn.name not in EMPTY_DFNS)
5050

5151

5252
@requires_pkg("boltons")
5353
def test_load_v2(toml_name):
5454
with (TOML_DIR / f"{toml_name}.toml").open(mode="rb") as toml_file:
55-
toml = Dfn.load(toml_file, name=toml_name, version=2)
56-
assert any(toml)
55+
dfn = load(toml_file, name=toml_name, format="toml")
56+
assert any(dfn.fields) == (dfn.name not in EMPTY_DFNS)
5757

5858

5959
@requires_pkg("boltons")
60-
@pytest.mark.parametrize("version", list(VERSIONS.keys()))
61-
def test_load_all(version):
62-
dfns = Dfn.load_all(VERSIONS[version], version=version)
63-
assert any(dfns)
64-
60+
@pytest.mark.parametrize("schema_version", list(SPEC_DIRS.keys()))
61+
def test_load_all(schema_version):
62+
dfns = load_flat(path=SPEC_DIRS[schema_version])
63+
for dfn in dfns.values():
64+
assert any(dfn.fields) == (dfn.name not in EMPTY_DFNS)
6565

66-
@requires_pkg("boltons")
67-
def test_load_tree():
68-
import tempfile
6966

67+
@requires_pkg("boltons", "tomli")
68+
def test_convert(function_tmpdir):
7069
import tomli
7170

72-
with tempfile.TemporaryDirectory() as tmp_dir:
73-
tmp_path = Path(tmp_dir)
74-
convert(DFN_DIR, tmp_path)
75-
76-
# Test file conversion and naming
77-
assert (tmp_path / "sim.toml").exists()
78-
assert (tmp_path / "gwf.toml").exists()
79-
assert not (tmp_path / "sim-nam.toml").exists()
80-
81-
# Test parent relationships in files
82-
with (tmp_path / "sim.toml").open("rb") as f:
83-
sim_data = tomli.load(f)
84-
assert sim_data["name"] == "sim"
85-
assert "parent" not in sim_data
86-
87-
with (tmp_path / "gwf.toml").open("rb") as f:
88-
gwf_data = tomli.load(f)
89-
assert gwf_data["name"] == "gwf"
90-
assert gwf_data["parent"] == "sim"
91-
92-
# Test hierarchy enforcement and completeness
93-
dfns = Dfn.load_all(tmp_path, version=2)
94-
roots = [name for name, dfn in dfns.items() if not dfn.get("parent")]
95-
assert len(roots) == 1
96-
assert roots[0] == "sim"
97-
98-
for dfn in dfns.values():
99-
parent = dfn.get("parent")
100-
if parent:
101-
assert parent in dfns
102-
103-
# Test tree building and navigation
104-
tree = Dfn.load_tree(tmp_path, version=2)
105-
assert "sim" in tree
106-
assert tree["sim"]["name"] == "sim"
107-
108-
for model_type in ["gwf", "gwt", "gwe"]:
109-
if model_type in tree["sim"]:
110-
assert tree["sim"][model_type]["name"] == model_type
111-
assert tree["sim"][model_type]["parent"] == "sim"
112-
113-
if "gwf" in tree["sim"]:
114-
gwf_packages = [
115-
k
116-
for k in tree["sim"]["gwf"].keys()
117-
if k.startswith("gwf-") and isinstance(tree["sim"]["gwf"][k], dict)
118-
]
119-
assert len(gwf_packages) > 0
120-
121-
if "gwf-dis" in tree["sim"]["gwf"]:
122-
dis = tree["sim"]["gwf"]["gwf-dis"]
123-
assert dis["name"] == "gwf-dis"
124-
assert dis["parent"] == "gwf"
125-
assert "options" in dis or "dimensions" in dis
71+
convert(DFN_DIR, function_tmpdir)
72+
73+
assert (function_tmpdir / "sim-nam.toml").exists()
74+
assert (function_tmpdir / "gwf-nam.toml").exists()
75+
76+
with (function_tmpdir / "sim-nam.toml").open("rb") as f:
77+
sim_data = tomli.load(f)
78+
assert sim_data["name"] == "sim-nam"
79+
assert sim_data["schema_version"] == "2"
80+
assert "parent" not in sim_data
81+
82+
with (function_tmpdir / "gwf-nam.toml").open("rb") as f:
83+
gwf_data = tomli.load(f)
84+
assert gwf_data["name"] == "gwf-nam"
85+
assert gwf_data["parent"] == "sim-nam"
86+
assert gwf_data["schema_version"] == "2"
87+
88+
dfns = load_flat(function_tmpdir)
89+
roots = []
90+
for dfn in dfns.values():
91+
if dfn.parent:
92+
assert dfn.parent in dfns
93+
else:
94+
roots.append(dfn.name)
95+
assert len(roots) == 1
96+
root = dfns[roots[0]]
97+
assert root.name == "sim-nam"
98+
99+
models = root.children or {}
100+
for mdl in models:
101+
assert models[mdl].name == mdl
102+
assert models[mdl].parent == "sim-nam"
103+
104+
if gwf := models.get("gwf-nam", None):
105+
pkgs = gwf.children or {}
106+
pkgs = {
107+
k: v
108+
for k, v in pkgs.items()
109+
if k.startswith("gwf-") and isinstance(v, dict)
110+
}
111+
assert len(pkgs) > 0
112+
if dis := pkgs.get("gwf-dis", None):
113+
assert dis.name == "gwf-dis"
114+
assert dis.parent == "gwf"
115+
assert "options" in (dis.blocks or {})
116+
assert "dimensions" in (dis.blocks or {})

0 commit comments

Comments
 (0)