Skip to content

Commit

Permalink
use devtools dfn utils, convert to toml before codegen, remove dfn at…
Browse files Browse the repository at this point in the history
…tr from generated classes
  • Loading branch information
wpbonelli committed Jan 14, 2025
1 parent f4bde21 commit fced371
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 732 deletions.
37 changes: 21 additions & 16 deletions autotest/test_codegen.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import pytest
from modflow_devtools.dfn import get_dfns
from modflow_devtools.dfn2toml import convert

from autotest.conftest import get_project_root_path
from flopy.mf6.utils.codegen import make_all
from flopy.mf6.utils.codegen.dfn import Dfn

PROJ_ROOT = get_project_root_path()
MF6_PATH = PROJ_ROOT / "flopy" / "mf6"
DFN_PATH = MF6_PATH / "data" / "dfn"
DFN_NAMES = [
dfn.stem for dfn in DFN_PATH.glob("*.dfn") if dfn.stem not in ["common", "flopy"]
]
DFN_PATH = PROJ_ROOT / "autotest" / "temp" / "dfn"
TOML_PATH = DFN_PATH / "toml"
MF6_OWNER = "MODFLOW-USGS"
MF6_REPO = "modflow6"
MF6_REF = "develop"


@pytest.mark.parametrize("dfn_name", DFN_NAMES)
def test_dfn_load(dfn_name):
with (
open(DFN_PATH / "common.dfn", "r") as common_file,
open(DFN_PATH / f"{dfn_name}.dfn", "r") as dfn_file,
):
name = Dfn.Name.parse(dfn_name)
common, _ = Dfn._load_v1_flat(common_file)
Dfn.load(dfn_file, name=name, common=common)
def pytest_generate_tests(metafunc):
if not any(DFN_PATH.glob("*.dfn")):
get_dfns(MF6_OWNER, MF6_REPO, MF6_REF, DFN_PATH, verbose=True)

convert(DFN_PATH, TOML_PATH)
dfns = list(DFN_PATH.glob("*.dfn"))
assert all(
(TOML_PATH / f"{dfn.stem}.toml").is_file()
for dfn in dfns
if "common" not in dfn.stem
)

def test_make_all(function_tmpdir):
make_all(DFN_PATH, function_tmpdir, verbose=True)

@pytest.mark.parametrize("version,dfn_path", [(1, DFN_PATH), (2, TOML_PATH)])
def test_make_all(function_tmpdir, version, dfn_path):
make_all(dfn_path, function_tmpdir, verbose=True, version=version)
assert any(function_tmpdir.glob("*.py"))
2 changes: 1 addition & 1 deletion docs/mf6_dev_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The latter is typically used with e.g. `python -m flopy.mf6.utils.generate_class

Generated files are created in `flopy/mf6/modflow/` and contain interface classes, one file/class per input component. These can be used to initialize and access model/package data as well as the input specification itself.

**Note**: Code generation requires a few extra dependencies, grouped in the `codegen` optional dependency group: `Jinja2`, `boltons`, `tomlkit` and `modflow-devtools`.
**Note**: Code generation requires a few extra dependencies, grouped in the `codegen` optional dependency group: `Jinja2` and `modflow-devtools`.

**Note**: Code generation scripts previously used `flopy/mf6/data/mfstructure.py` to read and represent definition files, and wrote Python by hand. They now use the `flopy.mf6.utils.codegen` module, which uses Jinja2.

Expand Down
1 change: 0 additions & 1 deletion etc/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ dependencies:
- Jinja2>=3.0
- pip:
- git+/~https://github.com/MODFLOW-USGS/modflow-devtools.git
- tomlkit

# lint
- cffconvert
Expand Down
11 changes: 4 additions & 7 deletions flopy/mf6/utils/codegen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ def _get_template_env():
env.filters["prefix"] = Filters.Cls.prefix
env.filters["parent"] = Filters.Cls.parent
env.filters["skip_init"] = Filters.Cls.skip_init

env.filters["attrs"] = Filters.Vars.attrs
env.filters["init"] = Filters.Vars.init

env.filters["untag"] = Filters.Var.untag
env.filters["type"] = Filters.Var.type

env.filters["safe_name"] = Filters.safe_name
env.filters["escape_trailing_underscore"] = (
Filters.escape_trailing_underscore
Expand Down Expand Up @@ -84,7 +81,7 @@ def _get_template_name(ctx_name) -> str:
if ctx_name.l == "exg":
return "exchange.py.jinja"
return "package.py.jinja"

for context in Context.from_dfn(dfn):
name = context["name"]
target_path = outdir / f"mf{Filters.Cls.title(name)}.py"
Expand All @@ -96,12 +93,12 @@ def _get_template_name(ctx_name) -> str:
print(f"Wrote {target_path}")


def make_all(dfndir: Path, outdir: PathLike, verbose: bool = False):
def make_all(dfndir: Path, outdir: PathLike, verbose: bool = False, version: int = 1):
"""Generate Python source files from the DFN files in the given location."""

from flopy.mf6.utils.codegen.dfn import Dfn
from modflow_devtools.dfn import Dfn

dfns = Dfn.load_all(dfndir)
dfns = Dfn.load_all(dfndir, version=version)
make_init(dfns, outdir, verbose)
for dfn in dfns.values():
make_targets(dfn, outdir, verbose)
14 changes: 7 additions & 7 deletions flopy/mf6/utils/codegen/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
TypedDict,
)

from flopy.mf6.utils.codegen.dfn import Dfn, Vars
from modflow_devtools.dfn import Dfn, Vars


class Context(TypedDict):
Expand Down Expand Up @@ -42,17 +42,17 @@ def from_dfn(dfn: Dfn) -> List["Context.Name"]:
Returns a list of context names this definition produces.
An input definition may produce one or more input contexts.
"""
name = dfn["name"]
if name.r == "nam":
if name.l == "sim":
name = dfn["name"].split("-")
if name[1] == "nam":
if name[0] == "sim":
return [
Context.Name(None, name.r), # nam pkg
Context.Name(None, name[1]), # nam pkg
Context.Name(*name), # simulation
]
else:
return [
Context.Name(*name), # nam pkg
Context.Name(name.l, None), # model
Context.Name(name[0], None), # model
]
elif name in [
("gwf", "mvr"),
Expand All @@ -62,7 +62,7 @@ def from_dfn(dfn: Dfn) -> List["Context.Name"]:
# TODO: deduplicate mfmvr.py/mfgwfmvr.py etc and remove special cases
return [
Context.Name(*name),
Context.Name(None, name.r),
Context.Name(None, name[1]),
]
return [Context.Name(*name)]

Expand Down
Loading

0 comments on commit fced371

Please sign in to comment.