modflow-devtools 1.7.0__tar.gz → 1.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/PKG-INFO +17 -7
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/README.md +3 -4
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/__init__.py +2 -2
- modflow_devtools-1.9.0/modflow_devtools/cli.py +63 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/dfn.py +61 -49
- modflow_devtools-1.9.0/modflow_devtools/dfn2toml.py +126 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/__init__.py +931 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/__main__.py +268 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/dfns.toml +24 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/fetch.py +25 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/make_registry.py +184 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/parse.py +212 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/registry.py +789 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/schema/block.py +22 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/schema/field.py +21 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/schema/ref.py +13 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/schema/v1.py +60 -0
- modflow_devtools-1.9.0/modflow_devtools/dfns/schema/v2.py +32 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/download.py +31 -24
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/fixtures.py +5 -15
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/markers.py +4 -10
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/misc.py +33 -21
- modflow_devtools-1.9.0/modflow_devtools/models/__init__.py +1382 -0
- modflow_devtools-1.9.0/modflow_devtools/models/__main__.py +417 -0
- modflow_devtools-1.9.0/modflow_devtools/models/make_registry.py +392 -0
- modflow_devtools-1.9.0/modflow_devtools/models/models.toml +34 -0
- modflow_devtools-1.9.0/modflow_devtools/programs/__init__.py +1897 -0
- modflow_devtools-1.9.0/modflow_devtools/programs/__main__.py +418 -0
- modflow_devtools-1.9.0/modflow_devtools/programs/make_registry.py +521 -0
- modflow_devtools-1.9.0/modflow_devtools/programs/programs.csv +26 -0
- modflow_devtools-1.9.0/modflow_devtools/programs/programs.toml +54 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/snapshots.py +3 -15
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/zip.py +1 -3
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/pyproject.toml +26 -4
- modflow_devtools-1.7.0/modflow_devtools/dfn2toml.py +0 -46
- modflow_devtools-1.7.0/modflow_devtools/make_registry.py +0 -89
- modflow_devtools-1.7.0/modflow_devtools/models.py +0 -522
- modflow_devtools-1.7.0/modflow_devtools/registry/examples.toml +0 -382
- modflow_devtools-1.7.0/modflow_devtools/registry/models.toml +0 -9485
- modflow_devtools-1.7.0/modflow_devtools/registry/registry.toml +0 -31890
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/.gitignore +0 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/LICENSE.md +0 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/build.py +0 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/imports.py +0 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/latex.py +0 -0
- {modflow_devtools-1.7.0 → modflow_devtools-1.9.0}/modflow_devtools/ostags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modflow-devtools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Python tools for MODFLOW development
|
|
5
5
|
Project-URL: Documentation, https://modflow-devtools.readthedocs.io/en/latest/
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/MODFLOW-ORG/modflow-devtools/issues
|
|
@@ -37,6 +37,7 @@ Requires-Dist: ninja; extra == 'dev'
|
|
|
37
37
|
Requires-Dist: numpy; extra == 'dev'
|
|
38
38
|
Requires-Dist: pandas; extra == 'dev'
|
|
39
39
|
Requires-Dist: pooch; extra == 'dev'
|
|
40
|
+
Requires-Dist: pydantic; extra == 'dev'
|
|
40
41
|
Requires-Dist: pytest!=8.1.0; extra == 'dev'
|
|
41
42
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
42
43
|
Requires-Dist: pytest-dotenv; extra == 'dev'
|
|
@@ -45,17 +46,26 @@ Requires-Dist: pyyaml; extra == 'dev'
|
|
|
45
46
|
Requires-Dist: ruff; extra == 'dev'
|
|
46
47
|
Requires-Dist: sphinx; extra == 'dev'
|
|
47
48
|
Requires-Dist: sphinx-rtd-theme; extra == 'dev'
|
|
48
|
-
Requires-Dist: syrupy; extra == 'dev'
|
|
49
|
+
Requires-Dist: syrupy<5.0.0; extra == 'dev'
|
|
49
50
|
Requires-Dist: tomli; extra == 'dev'
|
|
50
51
|
Requires-Dist: tomli-w; extra == 'dev'
|
|
51
52
|
Provides-Extra: dfn
|
|
52
53
|
Requires-Dist: boltons; extra == 'dfn'
|
|
54
|
+
Requires-Dist: pooch; extra == 'dfn'
|
|
55
|
+
Requires-Dist: pydantic; extra == 'dfn'
|
|
53
56
|
Requires-Dist: tomli; extra == 'dfn'
|
|
54
57
|
Requires-Dist: tomli-w; extra == 'dfn'
|
|
55
58
|
Provides-Extra: docs
|
|
56
59
|
Requires-Dist: myst-parser; extra == 'docs'
|
|
57
60
|
Requires-Dist: sphinx; extra == 'docs'
|
|
58
61
|
Requires-Dist: sphinx-rtd-theme; extra == 'docs'
|
|
62
|
+
Provides-Extra: ecosystem
|
|
63
|
+
Requires-Dist: boltons; extra == 'ecosystem'
|
|
64
|
+
Requires-Dist: filelock; extra == 'ecosystem'
|
|
65
|
+
Requires-Dist: pooch; extra == 'ecosystem'
|
|
66
|
+
Requires-Dist: pydantic; extra == 'ecosystem'
|
|
67
|
+
Requires-Dist: tomli; extra == 'ecosystem'
|
|
68
|
+
Requires-Dist: tomli-w; extra == 'ecosystem'
|
|
59
69
|
Provides-Extra: lint
|
|
60
70
|
Requires-Dist: codespell[toml]; extra == 'lint'
|
|
61
71
|
Requires-Dist: mypy; extra == 'lint'
|
|
@@ -64,6 +74,7 @@ Provides-Extra: models
|
|
|
64
74
|
Requires-Dist: boltons; extra == 'models'
|
|
65
75
|
Requires-Dist: filelock; extra == 'models'
|
|
66
76
|
Requires-Dist: pooch; extra == 'models'
|
|
77
|
+
Requires-Dist: pydantic; extra == 'models'
|
|
67
78
|
Requires-Dist: tomli; extra == 'models'
|
|
68
79
|
Requires-Dist: tomli-w; extra == 'models'
|
|
69
80
|
Provides-Extra: test
|
|
@@ -82,7 +93,7 @@ Requires-Dist: pytest-dotenv; extra == 'test'
|
|
|
82
93
|
Requires-Dist: pytest-xdist; extra == 'test'
|
|
83
94
|
Requires-Dist: pyyaml; extra == 'test'
|
|
84
95
|
Requires-Dist: ruff; extra == 'test'
|
|
85
|
-
Requires-Dist: syrupy; extra == 'test'
|
|
96
|
+
Requires-Dist: syrupy<5.0.0; extra == 'test'
|
|
86
97
|
Description-Content-Type: text/markdown
|
|
87
98
|
|
|
88
99
|
# MODFLOW developer tools
|
|
@@ -130,11 +141,10 @@ Python development tools for MODFLOW 6 and related projects.
|
|
|
130
141
|
|
|
131
142
|
Python3.10+, dependency-free by default.
|
|
132
143
|
|
|
133
|
-
|
|
144
|
+
Two main dependency groups are available, oriented around specific use cases:
|
|
134
145
|
|
|
135
|
-
- `
|
|
136
|
-
- `
|
|
137
|
-
- `models`: example model access
|
|
146
|
+
- `test`: pytest fixtures, markers, and extensions
|
|
147
|
+
- `ecosystem`: program/model management, definition file utilities
|
|
138
148
|
|
|
139
149
|
## Installation
|
|
140
150
|
|
|
@@ -43,11 +43,10 @@ Python development tools for MODFLOW 6 and related projects.
|
|
|
43
43
|
|
|
44
44
|
Python3.10+, dependency-free by default.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Two main dependency groups are available, oriented around specific use cases:
|
|
47
47
|
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `models`: example model access
|
|
48
|
+
- `test`: pytest fixtures, markers, and extensions
|
|
49
|
+
- `ecosystem`: program/model management, definition file utilities
|
|
51
50
|
|
|
52
51
|
## Installation
|
|
53
52
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Root CLI for modflow-devtools.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
mf models sync
|
|
6
|
+
mf models info
|
|
7
|
+
mf models list
|
|
8
|
+
mf models copy <model> <workspace>
|
|
9
|
+
mf models cp <model> <workspace> # cp is an alias for copy
|
|
10
|
+
mf programs sync
|
|
11
|
+
mf programs info
|
|
12
|
+
mf programs list
|
|
13
|
+
mf programs install <program>
|
|
14
|
+
mf programs uninstall <program>
|
|
15
|
+
mf programs history
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
"""Main entry point for the mf CLI."""
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="mf",
|
|
26
|
+
description="MODFLOW development tools",
|
|
27
|
+
)
|
|
28
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="Available commands")
|
|
29
|
+
|
|
30
|
+
# Models subcommand
|
|
31
|
+
subparsers.add_parser("models", help="Manage MODFLOW model registries")
|
|
32
|
+
|
|
33
|
+
# Programs subcommand
|
|
34
|
+
subparsers.add_parser("programs", help="Manage MODFLOW program registries")
|
|
35
|
+
|
|
36
|
+
# Parse only the first level to determine which submodule to invoke
|
|
37
|
+
args, remaining = parser.parse_known_args()
|
|
38
|
+
|
|
39
|
+
if not args.subcommand:
|
|
40
|
+
parser.print_help()
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
# Dispatch to the appropriate module CLI with remaining args
|
|
44
|
+
if args.subcommand == "models":
|
|
45
|
+
from modflow_devtools.models.__main__ import main as models_main
|
|
46
|
+
|
|
47
|
+
# Replace sys.argv to make it look like we called the submodule directly
|
|
48
|
+
sys.argv = ["mf models", *remaining]
|
|
49
|
+
models_main()
|
|
50
|
+
elif args.subcommand == "programs":
|
|
51
|
+
import warnings
|
|
52
|
+
|
|
53
|
+
# Suppress experimental warning for official CLI
|
|
54
|
+
with warnings.catch_warnings():
|
|
55
|
+
warnings.filterwarnings("ignore", message=".*modflow_devtools.programs.*experimental.*")
|
|
56
|
+
from modflow_devtools.programs.__main__ import main as programs_main
|
|
57
|
+
|
|
58
|
+
sys.argv = ["mf programs", *remaining]
|
|
59
|
+
programs_main()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|
|
@@ -185,6 +185,29 @@ class Dfn(TypedDict):
|
|
|
185
185
|
`str` to `Field`, and metadata, of which only a
|
|
186
186
|
limited set of keys are allowed. Block names and
|
|
187
187
|
metadata keys may not overlap.
|
|
188
|
+
|
|
189
|
+
Attributes
|
|
190
|
+
----------
|
|
191
|
+
name : str
|
|
192
|
+
Component name.
|
|
193
|
+
advanced : bool
|
|
194
|
+
Whether this is an advanced package.
|
|
195
|
+
multi : bool
|
|
196
|
+
Whether this is a multi-package.
|
|
197
|
+
ref : Ref | None
|
|
198
|
+
Metadata if this component is a subpackage (child's perspective).
|
|
199
|
+
Populated from: # flopy subpackage <key> <abbr> <param> <val>
|
|
200
|
+
sln : Sln | None
|
|
201
|
+
Solution package metadata.
|
|
202
|
+
fkeys : Dfns | None
|
|
203
|
+
Field-level foreign key references to other components.
|
|
204
|
+
Maps field names to Ref objects. Populated from flopy subpackage
|
|
205
|
+
metadata when specific fields reference other components.
|
|
206
|
+
subcomponents : list[str] | None
|
|
207
|
+
Allowed child component types (schema-level constraint).
|
|
208
|
+
Populated from: # mf6 subpackage <abbr>
|
|
209
|
+
Example: ['UTL-NCF'] means this component can have utl-ncf children.
|
|
210
|
+
Distinct from fkeys, which are field-level references.
|
|
188
211
|
"""
|
|
189
212
|
|
|
190
213
|
name: str
|
|
@@ -193,6 +216,7 @@ class Dfn(TypedDict):
|
|
|
193
216
|
ref: Ref | None = None
|
|
194
217
|
sln: Sln | None = None
|
|
195
218
|
fkeys: Dfns | None = None
|
|
219
|
+
subcomponents: list[str] | None = None
|
|
196
220
|
|
|
197
221
|
@staticmethod
|
|
198
222
|
def _load_v1_flat(f, common: dict | None = None) -> tuple[Mapping, list[str]]:
|
|
@@ -220,6 +244,11 @@ class Dfn(TypedDict):
|
|
|
220
244
|
_, sep, tail = line.partition("package-type")
|
|
221
245
|
if sep == "package-type":
|
|
222
246
|
meta.append(f"package-type {tail.strip()}")
|
|
247
|
+
# Parse mf6 subpackage declarations (schema-level composition constraints).
|
|
248
|
+
# Distinct from flopy subpackage (field-level foreign keys, parsed above).
|
|
249
|
+
_, sep, tail = line.partition("mf6 subpackage")
|
|
250
|
+
if sep == "mf6 subpackage":
|
|
251
|
+
meta.append(f"mf6-subpackage {tail.strip()}")
|
|
223
252
|
continue
|
|
224
253
|
|
|
225
254
|
# if we hit a newline and the parameter dict
|
|
@@ -249,16 +278,11 @@ class Dfn(TypedDict):
|
|
|
249
278
|
subs = literal_eval(subs)
|
|
250
279
|
cmmn = common.get(key, None)
|
|
251
280
|
if cmmn is None:
|
|
252
|
-
warn(
|
|
253
|
-
"Can't substitute description text, "
|
|
254
|
-
f"common variable not found: {key}"
|
|
255
|
-
)
|
|
281
|
+
warn(f"Can't substitute description text, common variable not found: {key}")
|
|
256
282
|
else:
|
|
257
283
|
descr = cmmn.get("description", "")
|
|
258
284
|
if any(subs):
|
|
259
|
-
descr = descr.replace("\\", "").replace(
|
|
260
|
-
"{#1}", subs["{#1}"]
|
|
261
|
-
)
|
|
285
|
+
descr = descr.replace("\\", "").replace("{#1}", subs["{#1}"])
|
|
262
286
|
field["description"] = descr
|
|
263
287
|
|
|
264
288
|
# add the final parameter
|
|
@@ -331,8 +355,7 @@ class Dfn(TypedDict):
|
|
|
331
355
|
|
|
332
356
|
# explicit record
|
|
333
357
|
if n_item_names == 1 and (
|
|
334
|
-
item_types[0].startswith("record")
|
|
335
|
-
or item_types[0].startswith("keystring")
|
|
358
|
+
item_types[0].startswith("record") or item_types[0].startswith("keystring")
|
|
336
359
|
):
|
|
337
360
|
return _convert_field(next(iter(flat.getlist(item_names[0]))))
|
|
338
361
|
|
|
@@ -343,9 +366,7 @@ class Dfn(TypedDict):
|
|
|
343
366
|
type="record",
|
|
344
367
|
block=block,
|
|
345
368
|
fields=_fields(),
|
|
346
|
-
description=description.replace(
|
|
347
|
-
"is the list of", "is the record of"
|
|
348
|
-
),
|
|
369
|
+
description=description.replace("is the list of", "is the record of"),
|
|
349
370
|
reader=reader,
|
|
350
371
|
**field,
|
|
351
372
|
)
|
|
@@ -358,19 +379,13 @@ class Dfn(TypedDict):
|
|
|
358
379
|
}
|
|
359
380
|
first = next(iter(fields.values()))
|
|
360
381
|
single = len(fields) == 1
|
|
361
|
-
item_type =
|
|
362
|
-
"keystring"
|
|
363
|
-
if single and "keystring" in first["type"]
|
|
364
|
-
else "record"
|
|
365
|
-
)
|
|
382
|
+
item_type = "keystring" if single and "keystring" in first["type"] else "record"
|
|
366
383
|
return Field(
|
|
367
384
|
name=first["name"] if single else _name,
|
|
368
385
|
type=item_type,
|
|
369
386
|
block=block,
|
|
370
387
|
fields=first["fields"] if single else fields,
|
|
371
|
-
description=description.replace(
|
|
372
|
-
"is the list of", f"is the {item_type} of"
|
|
373
|
-
),
|
|
388
|
+
description=description.replace("is the list of", f"is the {item_type} of"),
|
|
374
389
|
reader=reader,
|
|
375
390
|
**field,
|
|
376
391
|
)
|
|
@@ -390,11 +405,7 @@ class Dfn(TypedDict):
|
|
|
390
405
|
fields = {}
|
|
391
406
|
for name in names:
|
|
392
407
|
v = flat.get(name, None)
|
|
393
|
-
if (
|
|
394
|
-
not v
|
|
395
|
-
or not v.get("in_record", False)
|
|
396
|
-
or v["type"].startswith("record")
|
|
397
|
-
):
|
|
408
|
+
if not v or not v.get("in_record", False) or v["type"].startswith("record"):
|
|
398
409
|
continue
|
|
399
410
|
fields[name] = v
|
|
400
411
|
return fields
|
|
@@ -490,11 +501,7 @@ class Dfn(TypedDict):
|
|
|
490
501
|
|
|
491
502
|
def _sln() -> Sln | None:
|
|
492
503
|
sln = next(
|
|
493
|
-
iter(
|
|
494
|
-
m
|
|
495
|
-
for m in meta
|
|
496
|
-
if isinstance(m, str) and m.startswith("solution_package")
|
|
497
|
-
),
|
|
504
|
+
iter(m for m in meta if isinstance(m, str) and m.startswith("solution_package")),
|
|
498
505
|
None,
|
|
499
506
|
)
|
|
500
507
|
if sln:
|
|
@@ -505,9 +512,7 @@ class Dfn(TypedDict):
|
|
|
505
512
|
def _sub() -> Ref | None:
|
|
506
513
|
def _parent():
|
|
507
514
|
line = next(
|
|
508
|
-
iter(
|
|
509
|
-
m for m in meta if isinstance(m, str) and m.startswith("parent")
|
|
510
|
-
),
|
|
515
|
+
iter(m for m in meta if isinstance(m, str) and m.startswith("parent")),
|
|
511
516
|
None,
|
|
512
517
|
)
|
|
513
518
|
if not line:
|
|
@@ -517,9 +522,7 @@ class Dfn(TypedDict):
|
|
|
517
522
|
|
|
518
523
|
def _rest():
|
|
519
524
|
line = next(
|
|
520
|
-
iter(
|
|
521
|
-
m for m in meta if isinstance(m, str) and m.startswith("subpac")
|
|
522
|
-
),
|
|
525
|
+
iter(m for m in meta if isinstance(m, str) and m.startswith("subpac")),
|
|
523
526
|
None,
|
|
524
527
|
)
|
|
525
528
|
if not line:
|
|
@@ -548,6 +551,22 @@ class Dfn(TypedDict):
|
|
|
548
551
|
return Ref(parent=parent, **rest)
|
|
549
552
|
return None
|
|
550
553
|
|
|
554
|
+
def _subcomponents() -> list[str] | None:
|
|
555
|
+
"""
|
|
556
|
+
Extract allowed child component types from mf6 subpackage metadata.
|
|
557
|
+
|
|
558
|
+
This parses '# mf6 subpackage <abbr>' declarations to determine
|
|
559
|
+
schema-level composition constraints (which component types can be
|
|
560
|
+
children). Distinct from fkeys, which are field-level foreign keys
|
|
561
|
+
populated from '# flopy subpackage ...' declarations.
|
|
562
|
+
"""
|
|
563
|
+
result = []
|
|
564
|
+
for m in meta:
|
|
565
|
+
if m.startswith("mf6-subpackage "):
|
|
566
|
+
abbr = m.removeprefix("mf6-subpackage ").strip().upper()
|
|
567
|
+
result.append(abbr)
|
|
568
|
+
return result if result else None
|
|
569
|
+
|
|
551
570
|
return cls(
|
|
552
571
|
name=name,
|
|
553
572
|
fkeys=fkeys,
|
|
@@ -555,6 +574,7 @@ class Dfn(TypedDict):
|
|
|
555
574
|
multi=_multi(),
|
|
556
575
|
sln=_sln(),
|
|
557
576
|
ref=_sub(),
|
|
577
|
+
subcomponents=_subcomponents(),
|
|
558
578
|
**blocks,
|
|
559
579
|
)
|
|
560
580
|
|
|
@@ -586,13 +606,11 @@ class Dfn(TypedDict):
|
|
|
586
606
|
|
|
587
607
|
@staticmethod
|
|
588
608
|
def _load_all_v1(dfndir: PathLike) -> Dfns:
|
|
589
|
-
paths: list[Path] = [
|
|
590
|
-
p for p in dfndir.glob("*.dfn") if p.stem not in ["common", "flopy"]
|
|
591
|
-
]
|
|
609
|
+
paths: list[Path] = [p for p in dfndir.glob("*.dfn") if p.stem not in ["common", "flopy"]]
|
|
592
610
|
|
|
593
611
|
# load common variables
|
|
594
612
|
common_path: Path | None = dfndir / "common.dfn"
|
|
595
|
-
if not common_path.is_file:
|
|
613
|
+
if not common_path.is_file():
|
|
596
614
|
common = None
|
|
597
615
|
else:
|
|
598
616
|
with common_path.open() as f:
|
|
@@ -618,9 +636,7 @@ class Dfn(TypedDict):
|
|
|
618
636
|
|
|
619
637
|
@staticmethod
|
|
620
638
|
def _load_all_v2(dfndir: PathLike) -> Dfns:
|
|
621
|
-
paths: list[Path] = [
|
|
622
|
-
p for p in dfndir.glob("*.toml") if p.stem not in ["common", "flopy"]
|
|
623
|
-
]
|
|
639
|
+
paths: list[Path] = [p for p in dfndir.glob("*.toml") if p.stem not in ["common", "flopy"]]
|
|
624
640
|
dfns: Dfns = {}
|
|
625
641
|
for path in paths:
|
|
626
642
|
with path.open(mode="rb") as f:
|
|
@@ -640,9 +656,7 @@ class Dfn(TypedDict):
|
|
|
640
656
|
raise ValueError(f"Unsupported version, expected one of {version.__args__}")
|
|
641
657
|
|
|
642
658
|
|
|
643
|
-
def get_dfns(
|
|
644
|
-
owner: str, repo: str, ref: str, outdir: str | PathLike, verbose: bool = False
|
|
645
|
-
):
|
|
659
|
+
def get_dfns(owner: str, repo: str, ref: str, outdir: str | PathLike, verbose: bool = False):
|
|
646
660
|
"""Fetch definition files from the MODFLOW 6 repository."""
|
|
647
661
|
url = f"https://github.com/{owner}/{repo}/archive/{ref}.zip"
|
|
648
662
|
if verbose:
|
|
@@ -655,6 +669,4 @@ def get_dfns(
|
|
|
655
669
|
raise ValueError(f"Missing proj dir in {dl_path}, found {contents}")
|
|
656
670
|
if verbose:
|
|
657
671
|
print("Copying dfns from download dir to output dir")
|
|
658
|
-
shutil.copytree(
|
|
659
|
-
proj_path / "doc" / "mf6io" / "mf6ivar" / "dfn", outdir, dirs_exist_ok=True
|
|
660
|
-
)
|
|
672
|
+
shutil.copytree(proj_path / "doc" / "mf6io" / "mf6ivar" / "dfn", outdir, dirs_exist_ok=True)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Convert DFNs to TOML."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import textwrap
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
from os import PathLike
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import tomli_w as tomli
|
|
11
|
+
from boltons.iterutils import remap
|
|
12
|
+
|
|
13
|
+
from modflow_devtools.dfns import Dfn, is_valid, load, load_flat, map, to_flat, to_tree
|
|
14
|
+
from modflow_devtools.dfns.schema.block import block_sort_key
|
|
15
|
+
from modflow_devtools.misc import drop_none_or_empty
|
|
16
|
+
|
|
17
|
+
# mypy: ignore-errors
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def convert(inpath: PathLike, outdir: PathLike, schema_version: str = "2") -> None:
|
|
21
|
+
"""
|
|
22
|
+
Convert DFN files in `inpath` to TOML files in `outdir`.
|
|
23
|
+
By default, convert the definitions to schema version 2.
|
|
24
|
+
"""
|
|
25
|
+
inpath = Path(inpath).expanduser().absolute()
|
|
26
|
+
outdir = Path(outdir).expanduser().absolute()
|
|
27
|
+
outdir.mkdir(exist_ok=True, parents=True)
|
|
28
|
+
|
|
29
|
+
if inpath.is_file():
|
|
30
|
+
if inpath.name == "common.dfn":
|
|
31
|
+
raise ValueError("Cannot convert common.dfn as a standalone file")
|
|
32
|
+
|
|
33
|
+
common_path = inpath.parent / "common.dfn"
|
|
34
|
+
if common_path.exists():
|
|
35
|
+
with common_path.open() as f:
|
|
36
|
+
from modflow_devtools.dfn import parse_dfn
|
|
37
|
+
|
|
38
|
+
common, _ = parse_dfn(f)
|
|
39
|
+
else:
|
|
40
|
+
common = {}
|
|
41
|
+
|
|
42
|
+
with inpath.open() as f:
|
|
43
|
+
dfn = load(f, name=inpath.stem, common=common, format="dfn")
|
|
44
|
+
|
|
45
|
+
dfn = map(dfn, schema_version=schema_version)
|
|
46
|
+
_convert(dfn, outdir / f"{inpath.stem}.toml")
|
|
47
|
+
else:
|
|
48
|
+
dfns = {
|
|
49
|
+
name: map(dfn, schema_version=schema_version) for name, dfn in load_flat(inpath).items()
|
|
50
|
+
}
|
|
51
|
+
tree = to_tree(dfns)
|
|
52
|
+
flat = to_flat(tree)
|
|
53
|
+
for dfn_name, dfn in flat.items():
|
|
54
|
+
_convert(dfn, outdir / f"{dfn_name}.toml")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _convert(dfn: Dfn, outpath: Path) -> None:
|
|
58
|
+
with Path.open(outpath, "wb") as f:
|
|
59
|
+
# TODO if we start using c/attrs, swap out
|
|
60
|
+
# all this for a custom unstructuring hook
|
|
61
|
+
dfn_dict = asdict(dfn)
|
|
62
|
+
dfn_dict["schema_version"] = str(dfn_dict["schema_version"])
|
|
63
|
+
if blocks := dfn_dict.pop("blocks", None):
|
|
64
|
+
for block_name, block_fields in blocks.items():
|
|
65
|
+
if block_name not in dfn_dict:
|
|
66
|
+
dfn_dict[block_name] = {}
|
|
67
|
+
for field_name, field_data in block_fields.items():
|
|
68
|
+
dfn_dict[block_name][field_name] = field_data
|
|
69
|
+
|
|
70
|
+
tomli.dump(
|
|
71
|
+
dict(
|
|
72
|
+
sorted(
|
|
73
|
+
remap(dfn_dict, visit=drop_none_or_empty).items(),
|
|
74
|
+
key=block_sort_key,
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
f,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
"""
|
|
83
|
+
Convert DFN files in the original format and schema version 1
|
|
84
|
+
to TOML files, by default also converting to schema version 2.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
parser = argparse.ArgumentParser(
|
|
88
|
+
description="Convert DFN files to TOML.",
|
|
89
|
+
epilog=textwrap.dedent(
|
|
90
|
+
"""\
|
|
91
|
+
Convert DFN files in the original format and schema version 1
|
|
92
|
+
to TOML files, by default also converting to schema version 2.
|
|
93
|
+
"""
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--indir",
|
|
98
|
+
"-i",
|
|
99
|
+
type=str,
|
|
100
|
+
help="Directory containing DFN files, or a single DFN file.",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--outdir",
|
|
104
|
+
"-o",
|
|
105
|
+
help="Output directory.",
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--schema-version",
|
|
109
|
+
"-s",
|
|
110
|
+
type=str,
|
|
111
|
+
default="2",
|
|
112
|
+
help="Schema version to convert to.",
|
|
113
|
+
)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--validate",
|
|
116
|
+
"-v",
|
|
117
|
+
action="store_true",
|
|
118
|
+
help="Validate DFN files without converting them.",
|
|
119
|
+
)
|
|
120
|
+
args = parser.parse_args()
|
|
121
|
+
|
|
122
|
+
if args.validate:
|
|
123
|
+
if not is_valid(args.indir):
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
else:
|
|
126
|
+
convert(args.indir, args.outdir, args.schema_version)
|