diff --git a/flopy4/cli.py b/flopy4/cli.py new file mode 100644 index 00000000..d2af30d9 --- /dev/null +++ b/flopy4/cli.py @@ -0,0 +1,241 @@ +"""Top-level CLI entry point for flopy4.""" + +import argparse +import logging +import shutil +import sys +import warnings +from pathlib import Path + +_PROJ_ROOT = Path(__file__).parents[1].expanduser().resolve() +_MF6_ROOT = _PROJ_ROOT / "flopy4" / "mf6" +_DFN_SCHEMA_VERSION = "2.0.0.dev1" + + +def _resolve_release_id(release_id: str | None, verbose: bool = False) -> str: + """Resolve a release_id, falling back to the discovered binary's version + or the latest release if no release_id is given. + """ + if release_id is not None: + return release_id + + # Try to read the version from a binary on PATH. + from flopy4.mf6._compat import _query_mf6_version + + for name in ("mf6", "mf6.exe"): + exe = shutil.which(name) + if exe: + version = _query_mf6_version(exe) + if version: + if verbose: + print(f"Detected MF6 {version} at {exe}") + return f"MODFLOW-ORG/modflow6@{version}" + + # Fall back to @latest. + if verbose: + print("No MF6 binary found on PATH; resolving to latest release.") + return "MODFLOW-ORG/modflow6@latest" + + +def _cmd_sync(args: argparse.Namespace) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*modflow_devtools.programs.*experimental.*") + warnings.filterwarnings("ignore", message=".*modflow_devtools.dfns.*experimental.*") + from modflow_devtools.dfns import LocalDfnRegistry, RemoteDfnRegistry + # from modflow_devtools.programs import install_program + + from flopy4.mf6.utils.codegen.dfn2py import make + + release_id = _resolve_release_id(args.release_id, verbose=args.verbose) + + # For remote release IDs, install the matching binary unless --no-install. + from pathlib import Path + + # is_local = Path(release_id).expanduser().is_dir() + # if not is_local and not args.no_install: + # _, tag = release_id.split("@", 1) + # if tag not in ("latest",): + # version_arg = tag + # else: + # version_arg = None # let install_program resolve latest + # if args.verbose: + # label = version_arg or "latest" + # print(f"Installing mf6 {label} ...") + # try: + # installed = install_program( + # "mf6", + # version=version_arg, + # force=args.force, + # verbose=args.verbose, + # ) + # if args.verbose and installed: + # print(f"Installed: {', '.join(str(p) for p in installed)}") + # except Exception as exc: + # print(f"Warning: binary installation failed: {exc}", file=sys.stderr) + # print("Continuing with class generation only.", file=sys.stderr) + + if args.verbose: + print(f"Generating flopy4.mf6 from {release_id} ...") + + def _populate_remote_cache( + registry: RemoteDfnRegistry, release_id: str, force: bool = False + ) -> None: + """Populate the registry's DFN cache. + + Tries ``registry.sync()`` first (requires a ``dfns.zip`` release asset). + Falls back to fetching DFNs from the repository tree when that asset is + not yet available — writes directly into the registry's cache path so + ``registry.spec()`` finds the files in the expected location. + + Parameters + ---------- + force : + Re-fetch even if the cache already exists. Should be True for + branch refs (mutable) and False for pinned release tags. + """ + if not force and registry.cache_path.exists() and any(registry.cache_path.iterdir()): + return + try: + registry.sync() + except Exception: + # dfns.zip not yet a release asset; fetch from the git tree instead. + from modflow_devtools.dfn import get_dfns + + owner_repo, tag = release_id.split("@", 1) + owner, repo = owner_repo.split("/") + registry.cache_path.mkdir(parents=True, exist_ok=True) + get_dfns(owner=owner, repo=repo, ref=tag, outdir=registry.cache_path) + + def _write_contract(outdir: Path, mf6_version: str) -> None: + (outdir / "_contract.py").write_text( + "# autogenerated file, do not modify\n" + f'MF6_VERSION = "{mf6_version}"\n' + f'DFN_SCHEMA_VERSION = "{_DFN_SCHEMA_VERSION}"\n' + ) + + path = Path(release_id).expanduser() + if path.exists() and path.is_dir(): + registry = LocalDfnRegistry(path=path) + effective_version = "unknown" + else: + if "@" not in release_id or "/" not in release_id.split("@")[0]: + raise ValueError( + f"release_id must be 'owner/repo@tag' or a local path; got: {release_id!r}" + ) + registry = RemoteDfnRegistry(release_id=release_id) + effective_version = args.mf6_version or registry.latest_tag() or "unknown" + + if effective_version == "unknown": + warnings.warn( + f"MF6 version is unknown for local DFN path '{release_id}'. " + "Pass --mf6-version to record it explicitly in _contract.py." + ) + + if isinstance(registry, RemoteDfnRegistry): + # Branch refs are mutable: always re-fetch unless the caller explicitly + # passes a pinned semver tag (first char is a digit). + is_branch = not effective_version[0].isdigit() if effective_version else True + _populate_remote_cache(registry, release_id, force=args.force or is_branch) + + make( + dfndir=registry.cache_path if isinstance(registry, RemoteDfnRegistry) else registry.path, + outdir=_MF6_ROOT, + existing_only=not args.all_packages, + makedirs=args.all_packages, + fmt=not args.no_format, + ) + _write_contract(_MF6_ROOT, effective_version) + + if effective_version != "unknown": + print(f"Synced flopy4.mf6 to MF6 version: {effective_version}") + + +def _cmd_status(args: argparse.Namespace) -> None: + try: + from flopy4.mf6._contract import DFN_SCHEMA_VERSION, MF6_VERSION + except ImportError: + MF6_VERSION = "unknown" + DFN_SCHEMA_VERSION = "unknown" + + from flopy4.mf6._compat import _query_mf6_version + + exe = shutil.which("mf6") or shutil.which("mf6.exe") + binary_version = _query_mf6_version(exe) if exe else None + + synced = binary_version is not None and binary_version == MF6_VERSION + status = "(✓ in sync)" if synced else "(! mismatch)" if binary_version else "(not found)" + + print(f"flopy4.mf6 synced to : {MF6_VERSION}") + print(f"DFN schema version : {DFN_SCHEMA_VERSION}") + if exe: + print(f"Discovered binary : {binary_version} [{exe}] {status}") + else: + print("Discovered binary : none (not found on PATH)") + + +def main() -> None: + parser = argparse.ArgumentParser(prog="flopy4") + sub = parser.add_subparsers(dest="command") + + mf6_p = sub.add_parser("mf6", help="MF6 sync and status commands.") + mf6_sub = mf6_p.add_subparsers(dest="subcommand") + + # flopy4 mf6 sync + sync_p = mf6_sub.add_parser( + "sync", + help="Sync flopy4.mf6 to an MF6 release: install binary and regenerate classes.", + ) + sync_p.add_argument( + "release_id", + nargs="?", + default=None, + help="Remote release ID (owner/repo@tag) or local path to .dfn files. " + "Defaults to the discovered binary's version, or latest if no binary found.", + ) + sync_p.add_argument( + "--mf6-version", + default=None, + dest="mf6_version", + help="Override MF6 version in _contract.py (useful with local DFN paths).", + ) + sync_p.add_argument( + "--no-install", + action="store_true", + help="Skip binary installation; regenerate classes only.", + ) + sync_p.add_argument( + "--all-packages", + action="store_true", + dest="all_packages", + help="Generate all packages, including ones not yet on disk. " + "By default sync only updates already-generated files.", + ) + sync_p.add_argument( + "--no-format", + action="store_true", + help="Skip ruff formatting of generated files.", + ) + sync_p.add_argument("--force", action="store_true", help="Force binary reinstallation.") + sync_p.add_argument("--verbose", action="store_true") + sync_p.set_defaults(func=_cmd_sync) + + # flopy4 mf6 status + status_p = mf6_sub.add_parser( + "status", + help="Show current sync state: synced version vs. discovered binary.", + ) + status_p.set_defaults(func=_cmd_status) + + args = parser.parse_args() + if not hasattr(args, "func"): + parser.print_help() + sys.exit(1) + + if getattr(args, "verbose", False): + logging.basicConfig(level=logging.INFO) + + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/flopy4/mf6/__init__.py b/flopy4/mf6/__init__.py index a36a1f3d..4599d84d 100644 --- a/flopy4/mf6/__init__.py +++ b/flopy4/mf6/__init__.py @@ -5,8 +5,15 @@ from tomli import load as load_toml from tomli_w import dump as dump_toml +try: + from flopy4.mf6._contract import DFN_SCHEMA_VERSION, MF6_VERSION +except ImportError: + DFN_SCHEMA_VERSION = "unknown" + MF6_VERSION = "unknown" + # Import submodules to make them accessible via flopy4.mf6.* from flopy4.mf6 import gwe, gwf, gwt, prt, simulation, solution, utils +from flopy4.mf6._compat import check_mf6_compatibility from flopy4.mf6.codec import dump as dump_mf6 from flopy4.mf6.codec import load as load_mf6 from flopy4.mf6.component import Component @@ -36,6 +43,8 @@ "NetCDFModel", "Tdis", "Simulation", + "MF6_VERSION", + "DFN_SCHEMA_VERSION", ] @@ -98,3 +107,5 @@ def _write_toml(component: Component) -> None: DEFAULT_REGISTRY.register_writer(Component, "mf6", _write_mf6) DEFAULT_REGISTRY.register_writer(Component, "json", _write_json) DEFAULT_REGISTRY.register_writer(Component, "toml", _write_toml) + +check_mf6_compatibility() diff --git a/flopy4/mf6/_compat.py b/flopy4/mf6/_compat.py new file mode 100644 index 00000000..63b6ec84 --- /dev/null +++ b/flopy4/mf6/_compat.py @@ -0,0 +1,53 @@ +import re +import shutil +import subprocess +import warnings + +_VERSION_RE = re.compile(r"version\s+([\d]+\.[\d]+\.[\d]+(?:\.\S+)?)", re.I) + + +def _query_mf6_version(exe: str) -> str | None: + try: + out = subprocess.check_output([exe, "-v"], text=True, stderr=subprocess.STDOUT) + m = _VERSION_RE.search(out) + return m.group(1) if m else None + except Exception: + return None + + +def check_mf6_compatibility(exe: str | None = None) -> None: + """Warn if a discovered MF6 binary doesn't match the synced version. + + Does nothing when the synced version is ``"unknown"`` or a branch + name (non-semver). + + Parameters + ---------- + exe : + Path to an MF6 executable. If None, searches PATH for ``mf6`` + or ``mf6.exe``. Does nothing if no binary is found. + """ + try: + from flopy4.mf6._contract import MF6_VERSION + except ImportError: + return + + # Skip if version is unknown or a branch name rather than a semver tag. + if not MF6_VERSION or not MF6_VERSION[0].isdigit(): + return + + if exe is None: + exe = shutil.which("mf6") or shutil.which("mf6.exe") + if exe is None: + return + + binary_version = _query_mf6_version(exe) + if binary_version is None or binary_version == MF6_VERSION: + return + + warnings.warn( + f"flopy4.mf6 is synced to MF6 {MF6_VERSION} but the binary at '{exe}' " + f"reports {binary_version}. Run `flopy4 mf6 sync` to re-sync.", + UserWarning, + stacklevel=3, + ) diff --git a/flopy4/mf6/_contract.py b/flopy4/mf6/_contract.py new file mode 100644 index 00000000..520d9a71 --- /dev/null +++ b/flopy4/mf6/_contract.py @@ -0,0 +1,3 @@ +# autogenerated file, do not modify +MF6_VERSION = "unknown" +DFN_SCHEMA_VERSION = "2.0.0.dev1" diff --git a/flopy4/mf6/codec/reader/dfn2lark.py b/flopy4/mf6/codec/reader/dfn2lark.py index ff56a620..2a1f5161 100644 --- a/flopy4/mf6/codec/reader/dfn2lark.py +++ b/flopy4/mf6/codec/reader/dfn2lark.py @@ -1,10 +1,10 @@ -"""Convert (TOML/v2) DFNs to Lark grammars.""" +"""Generate Lark grammars from DFN files.""" import argparse from os import PathLike from pathlib import Path -from modflow_devtools.dfn import load_flat, map +import modflow_devtools.dfn as dfn from flopy4.mf6.codec.reader.grammar import make_grammars @@ -17,9 +17,8 @@ def make(dfndir: str | PathLike, outdir: str | PathLike): dfndir = Path(dfndir).expanduser().absolute() outdir = Path(outdir).expanduser().absolute() outdir.mkdir(exist_ok=True, parents=True) - dfns_v1 = load_flat(dfndir) - dfns_v2 = {name: map(dfn, schema_version=2) for name, dfn in dfns_v1.items()} - make_grammars(dfns_v2, outdir) + dfns = dfn.Dfn.load_all(dfndir, schema_version="2.0.0.dev1") + make_grammars(dfns, outdir) def main(): diff --git a/flopy4/mf6/utils/codegen/__init__.py b/flopy4/mf6/utils/codegen/__init__.py index 6b2a501e..c2e8b023 100644 --- a/flopy4/mf6/utils/codegen/__init__.py +++ b/flopy4/mf6/utils/codegen/__init__.py @@ -1,3 +1,3 @@ -from .generate_classes import generate_classes +from flopy4.mf6.utils.codegen.dfn2py import make -__all__ = ["generate_classes"] +__all__ = ["make"] diff --git a/flopy4/mf6/utils/codegen/dfn2py.py b/flopy4/mf6/utils/codegen/dfn2py.py new file mode 100644 index 00000000..125b360f --- /dev/null +++ b/flopy4/mf6/utils/codegen/dfn2py.py @@ -0,0 +1,128 @@ +"""Generate an MF6 module from DFN files.""" + +import argparse +import sys +from os import PathLike +from pathlib import Path + +import modflow_devtools.dfn as dfn + +from flopy4.mf6.utils.codegen.make import make_modules + +_PROJ_ROOT = Path(__file__).parents[4].expanduser().resolve() +_MF6_ROOT = _PROJ_ROOT / "flopy4" / "mf6" + +# DFN names that require special handling beyond simple package generation. +# These are skipped until their generation tier is implemented. +# +# nam files: model/simulation name files need special model-level handling. +# dis/disv: require DisBase + grid conversion methods (dis tier). +# tdis/ims: top-level hand-written files, not yet templated. +# *g / *a variants: gridded/array package variants, deferred. +# +# TODO (subpackage tier): detect `# flopy subpackage` DFN annotations and emit +# a typed child attrs field (e.g. ncf: Optional[Ncf]) alongside the existing path +# field; DisBase.write() already establishes the write pattern for NCF. +# utl-ts also needs period values referencing timeseries by name written as strings. +_SKIP = { + # discretization tier (require DisBase + grid methods) + "gwf-dis", + "gwf-disv", + "gwt-dis", + "gwe-dis", + "prt-dis", + # time discretization (hand-written tdis.py) + "sim-tdis", + # hand-written: wkt field type override + Ncf.from_grid() factory. + # TODO: move factory to NcfBase (utl/ncf_base.py) so codegen can own utl/ncf.py, + # matching the DisBase pattern used for discretization packages. + "utl-ncf", +} + + +def make( + dfndir: str | PathLike, + outdir: str | PathLike = _MF6_ROOT, + developmode: bool = False, + fmt: bool = True, + makedirs: bool = False, + existing_only: bool = False, +): + """Generate an MF6 module from DFNs.""" + dfndir = Path(dfndir).expanduser().resolve() + outdir = Path(outdir).expanduser().resolve() + outdir.mkdir(exist_ok=True, parents=True) + dfns = dfn.Dfn.load_all(dfndir, schema_version="2.0.0.dev1") + components = make_modules( + dfns=dfns, + outdir=outdir, + developmode=developmode, + fmt=fmt, + skip=_SKIP, + makedirs=makedirs, + existing_only=existing_only, + ) + print(f"Generated {len(components)} component modules") + + +def cli_main() -> None: + """Command-line entry point.""" + parser = argparse.ArgumentParser( + description="Generate MF6 Python classes from DFN specification files.", + ) + parser.add_argument( + "--dfndir", + "-d", + type=str, + help="Directory containing DFN files.", + ) + parser.add_argument( + "--outdir", + default=str(_MF6_ROOT), + help="Root output directory (default: flopy4/mf6/ in the project).", + ) + parser.add_argument( + "--mf6-version", + default=None, + dest="mf6_version", + help="MF6 version string to record in _contract.py. " + "Inferred from the release tag for remote IDs; defaults to 'unknown' for local paths.", + ) + parser.add_argument( + "--developmode", + action="store_true", + help="Include developmode fields.", + ) + parser.add_argument( + "--no-format", + action="store_true", + help="Skip ruff formatting.", + ) + parser.add_argument( + "--makedirs", + action="store_true", + help="Create missing output subdirectories (useful for preview paths).", + ) + parser.add_argument( + "--existing-only", + action="store_true", + help="Only regenerate files that already exist on disk.", + ) + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args() + + try: + make( + dfndir=args.dfndir, + outdir=args.outdir, + developmode=args.developmode, + fmt=not args.no_format, + makedirs=args.makedirs, + existing_only=args.existing_only, + ) + except (EOFError, KeyboardInterrupt): + sys.exit(f"Cancelled '{sys.argv[0]}'") + + +if __name__ == "__main__": + cli_main() diff --git a/flopy4/mf6/utils/codegen/generate_classes.py b/flopy4/mf6/utils/codegen/generate_classes.py deleted file mode 100644 index 4838071c..00000000 --- a/flopy4/mf6/utils/codegen/generate_classes.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -Orchestrate MF6 Python class generation from DFN specification files. - -Usage (CLI):: - - pixi run python -m flopy4.mf6.utils.codegen.generate_classes --ref 6.6.0 - pixi run python -m flopy4.mf6.utils.codegen.generate_classes --dfnpath /path/to/dfns -""" - -import argparse -import logging -import shutil -import sys -import tempfile -from pathlib import Path - -from modflow_devtools.dfn import get_dfns -from modflow_devtools.dfn2toml import convert as dfn2toml - -from .make import make_all - -logger = logging.getLogger(__name__) - -_PROJ_ROOT = Path(__file__).parents[4].expanduser().resolve() -_MF6_ROOT = _PROJ_ROOT / "flopy4" / "mf6" -_MF6_REPO_OWNER = "MODFLOW-ORG" -_MF6_REPO_NAME = "modflow6" - -# DFN names that require special handling beyond simple package generation. -# These are skipped until their generation tier is implemented. -# -# nam files: model/simulation name files need special model-level handling. -# dis/disv: require DisBase + grid conversion methods (dis tier). -# tdis/ims: top-level hand-written files, not yet templated. -# *g / *a variants: gridded/array package variants, deferred. -# -# TODO (subpackage tier): detect `# flopy subpackage` DFN annotations and emit -# a typed child attrs field (e.g. ncf: Optional[Ncf]) alongside the existing path -# field; DisBase.write() already establishes the write pattern for NCF. -# utl-ts also needs period values referencing timeseries by name written as strings. -_SKIP = { - # discretization tier (require DisBase + grid methods) - "gwf-dis", - "gwf-disv", - "gwt-dis", - "gwe-dis", - "prt-dis", - # time discretization (hand-written tdis.py) - "sim-tdis", - # hand-written: wkt field type override + Ncf.from_grid() factory. - # TODO: move factory to NcfBase (utl/ncf_base.py) so codegen can own utl/ncf.py, - # matching the DisBase pattern used for discretization packages. - "utl-ncf", -} - - -def generate_classes( - owner: str = _MF6_REPO_OWNER, - repo: str = _MF6_REPO_NAME, - ref: str | None = None, - dfnpath: str | None = None, - outdir: str | Path = _MF6_ROOT, - developmode: bool = False, - fmt: bool = True, - makedirs: bool = False, - existing_only: bool = False, -) -> None: - """Generate Python classes for MODFLOW 6 packages. - - Fetches (or reads) v1 DFN files, converts them to v2 TOML, then - generates a Python source file for each component into ``outdir``. - - Parameters - ---------- - owner : - GitHub organisation that owns the MODFLOW 6 repository. - repo : - Repository name. - ref : - Branch name, tag, or commit hash to fetch DFNs from. - Required when ``dfnpath`` is None. - dfnpath : - Path to a local directory of v1 ``.dfn`` files. Takes precedence - over remote fetching when supplied. - outdir : - Root output directory. Defaults to ``flopy4/mf6/`` inside the - project so generated files land alongside hand-written ones. - developmode : - Include fields marked ``developmode`` in the DFN. Default False. - fmt : - Run ``ruff format`` / ``ruff check --fix`` on each generated file. - """ - if dfnpath is None and ref is None: - raise ValueError("Provide either 'ref' (remote) or 'dfnpath' (local).") - - with tempfile.TemporaryDirectory() as tmp: - tmpdir = Path(tmp) - v1dir = tmpdir / "v1" - v2dir = tmpdir / "v2" - v1dir.mkdir() - v2dir.mkdir() - - if dfnpath is not None: - src = Path(dfnpath).expanduser().resolve() - if not src.is_dir(): - raise FileNotFoundError(f"dfnpath '{src}' is not a directory.") - shutil.copytree(src, v1dir, dirs_exist_ok=True) - logger.info(f"Using local DFNs from {src}") - else: - logger.info(f"Fetching DFNs from {owner}/{repo}@{ref}") - get_dfns( - owner=owner, - repo=repo, - ref=ref, - outdir=v1dir, - verbose=logger.isEnabledFor(logging.INFO), - ) - - logger.info("Converting v1 DFNs to v2 TOML") - dfn2toml(v1dir, v2dir) - - outdir = Path(outdir).expanduser().resolve() - generated = make_all( - dfndir=v2dir, - outdir=outdir, - developmode=developmode, - fmt=fmt, - skip=_SKIP, - makedirs=makedirs, - existing_only=existing_only, - v1dfndir=v1dir, - ) - - logger.info(f"Generated {len(generated)} files.") - - -def cli_main() -> None: - """Command-line entry point.""" - parser = argparse.ArgumentParser( - description="Generate MF6 Python classes from DFN specification files.", - ) - parser.add_argument("--owner", default=_MF6_REPO_OWNER) - parser.add_argument("--repo", default=_MF6_REPO_NAME) - parser.add_argument( - "--ref", - default=None, - help="Git ref (branch, tag, or commit) to fetch DFNs from.", - ) - parser.add_argument( - "--dfnpath", - default=None, - help="Path to a local directory of v1 .dfn files.", - ) - parser.add_argument( - "--outdir", - default=str(_MF6_ROOT), - help="Root output directory (default: flopy4/mf6/ in the project).", - ) - parser.add_argument( - "--developmode", - action="store_true", - help="Include developmode fields.", - ) - parser.add_argument( - "--no-format", - action="store_true", - help="Skip ruff formatting.", - ) - parser.add_argument( - "--makedirs", - action="store_true", - help="Create missing output subdirectories (useful for preview paths).", - ) - parser.add_argument( - "--existing-only", - action="store_true", - help="Only regenerate files that already exist on disk.", - ) - parser.add_argument("--verbose", action="store_true") - args = parser.parse_args() - - if args.verbose: - logging.basicConfig(level=logging.INFO) - - try: - generate_classes( - owner=args.owner, - repo=args.repo, - ref=args.ref, - dfnpath=args.dfnpath, - outdir=args.outdir, - developmode=args.developmode, - fmt=not args.no_format, - makedirs=args.makedirs, - existing_only=args.existing_only, - ) - except (EOFError, KeyboardInterrupt): - sys.exit(f"Cancelled '{sys.argv[0]}'") - - -if __name__ == "__main__": - cli_main() diff --git a/flopy4/mf6/utils/codegen/make.py b/flopy4/mf6/utils/codegen/make.py index 558ec87c..52670903 100644 --- a/flopy4/mf6/utils/codegen/make.py +++ b/flopy4/mf6/utils/codegen/make.py @@ -929,13 +929,13 @@ def _format(path: Path) -> None: ) -def make_component( +def make_module( spec: ComponentSpec, env: jinja2.Environment, *, fmt: bool = True, ) -> None: - """Render and write a single component file.""" + """Generate a single component module.""" template = env.get_template(spec.template) rendered = template.render(spec=spec) spec.outpath.write_text(rendered, newline="\n") @@ -947,23 +947,26 @@ def make_component( logger.warning(f"Failed to format {spec.outpath}: {e.stderr.decode().strip()}") -def make_all( +def make_modules( *, - dfndir: PathLike, + dfns: dict[str, "Dfn"] | None = None, + dfndir: PathLike | None = None, outdir: PathLike, developmode: bool = False, fmt: bool = True, skip: set[str] | None = None, makedirs: bool = False, existing_only: bool = False, - v1dfndir: PathLike | None = None, ) -> list[ComponentSpec]: - """Generate Python source files for all DFNs in dfndir. + """Generate Python modules for all components. Parameters ---------- + dfns : + Pre-loaded DFN dict from a registry's spec() call. Takes precedence + over dfndir when both are provided. dfndir : - Directory containing v2 TOML DFN files. + Directory containing DFN files. Used only when dfns is not provided. outdir : Root output directory for generated Python files. developmode : @@ -976,35 +979,29 @@ def make_all( If True, create output subdirectories as needed. existing_only : If True, only (re)generate files that already exist on disk. - v1dfndir : - Optional directory containing v1 DFN files (.dfn). When provided, - numeric_index is read from the v1 DFN to auto-detect cellid columns - in extra_list_blocks without requiring explicit ``cellid=true`` in - dfn_overrides.toml. Returns ------- list[ComponentSpec] Specs for all components that were generated. """ - dfndir = Path(dfndir) + if dfns is None: + if dfndir is None: + raise ValueError("Provide either 'dfns' or 'dfndir'.") + dfns = Dfn.load_all(Path(dfndir), schema_version="2.0.0.dev1") outdir = Path(outdir) skip = skip or set() env = _get_env() - dfns = Dfn.load_all(dfndir, schema_version="2.0.0.dev1") - v1_dfns = Dfn.load_all(v1dfndir, schema_version="2.0.0.dev1") if v1dfndir else {} specs = [] for name, dfn in dfns.items(): if name in skip: continue - spec = build_component_spec( - dfn, root=outdir, developmode=developmode, v1_dfn=v1_dfns.get(name) - ) + spec = build_component_spec(dfn, root=outdir, developmode=developmode, v1_dfn=dfn) if existing_only and not spec.outpath.exists(): logger.info(f"{spec.outpath} does not exist — skipping {name} (existing_only)") continue if makedirs: spec.outpath.parent.mkdir(parents=True, exist_ok=True) - make_component(spec, env, fmt=fmt) + make_module(spec, env, fmt=fmt) specs.append(spec) return specs diff --git a/pixi.lock b/pixi.lock index 874744dc..5721cd2b 100644 --- a/pixi.lock +++ b/pixi.lock @@ -88,7 +88,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -229,7 +229,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -378,7 +378,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz @@ -523,7 +523,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl @@ -687,7 +687,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -957,7 +957,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -1233,7 +1233,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -1505,7 +1505,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -1796,7 +1796,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl @@ -2055,7 +2055,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -2323,7 +2323,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl @@ -2586,7 +2586,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl @@ -2868,7 +2868,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3105,7 +3105,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -3351,7 +3351,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3593,7 +3593,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3853,7 +3853,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -4087,7 +4087,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -4330,7 +4330,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -4569,7 +4569,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -4825,7 +4825,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -5061,7 +5061,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -5306,7 +5306,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -5547,7 +5547,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_37.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - - pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda + - pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 - pypi: git+https://github.com/wpbonelli/xattree#82942ffb8237d56c97451765ac3e2b16d6cb0d2c - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -9648,7 +9648,7 @@ packages: - build ; extra == 'build' - twine ; extra == 'build' requires_python: '>=3.11,<3.14' -- pypi: git+https://github.com/modflow-org/modflow-devtools#5fde75e2b737b698795c30d993340f956350dbda +- pypi: git+https://github.com/modflow-org/modflow-devtools#abec244c96a3595f7774b279dea5d10ab64663d7 name: modflow-devtools version: 1.10.0.dev1 requires_dist: diff --git a/pyproject.toml b/pyproject.toml index 25eaa2ab..a5f80511 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,8 +117,7 @@ ignore = [ ] [project.scripts] -dfn2lark = "flopy4.mf6.codec.reader.dfn2lark:main" -generate-mf6-classes = "flopy4.mf6.utils.codegen.generate_classes:cli_main" +flopy4 = "flopy4.cli:main" [project.entry-points.flopy4] plot = "flopy4.singledispatch.plot_int" @@ -164,12 +163,20 @@ update-ghpages = { cmd = "python scripts/update_ghpages.py", description = "Sync build = { cmd = "python -m build" } [tool.pixi.tasks.generate-classes] -cmd = "generate-mf6-classes --ref develop --existing-only --verbose" -description = "Regenerate existing MF6 classes in flopy4/mf6/ from upstream DFNs" +cmd = "generate-mf6-classes MODFLOW-ORG/modflow6@latest --existing-only --verbose" +description = "Regenerate existing MF6 classes in flopy4/mf6/ from the latest release DFNs" [tool.pixi.tasks.generate-classes-preview] -cmd = "generate-mf6-classes --ref develop --outdir ./tmp/mf6-preview --makedirs --verbose" -description = "Generate MF6 classes into /tmp/mf6-preview (safe preview)" +cmd = "generate-mf6-classes MODFLOW-ORG/modflow6@latest --outdir ./tmp/mf6-preview --makedirs --verbose" +description = "Generate MF6 classes into ./tmp/mf6-preview (safe preview)" + +[tool.pixi.tasks.sync] +cmd = "flopy4 mf6 sync --verbose" +description = "Sync flopy4.mf6 to the discovered or latest MF6 release" + +[tool.pixi.tasks.sync-status] +cmd = "flopy4 mf6 status" +description = "Show flopy4.mf6 sync status vs. discovered MF6 binary" [tool.pixi.feature.test.tasks] test = { cmd = "pytest -v -n auto", env = { MPLBACKEND = "Agg" } } diff --git a/test/test_mf6_codegen.py b/test/test_mf6_codegen.py index c9bb7a32..83382a4d 100644 --- a/test/test_mf6_codegen.py +++ b/test/test_mf6_codegen.py @@ -32,7 +32,7 @@ safe_name, spec_call, ) -from flopy4.mf6.utils.codegen.make import build_component_spec, make_all +from flopy4.mf6.utils.codegen.make import build_component_spec, make_modules # Shared fixtures @@ -600,7 +600,7 @@ def test_simple_tier_generates_importable_files(dfn_path, tmp_path, all_dfns): (tmp_path / "gwf").mkdir() skip = {n for n in all_dfns if n not in SIMPLE_TIER} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) generated = {s.dfn_name: s for s in specs} @@ -638,7 +638,7 @@ def test_solution_tier_generates_importable_files(dfn_path, tmp_path, all_dfns): from flopy4.mf6.solution import Solution skip = {n for n in all_dfns if n not in SOLUTION_TIER} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) generated = {s.dfn_name: s for s in specs} @@ -697,7 +697,7 @@ def test_transport_tier_generates_importable_files(dfn_path, tmp_path, all_dfns) target = {n for n in TRANSPORT_TIER if n in all_dfns} skip = {n for n in all_dfns if n not in target} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) generated = {s.dfn_name: s for s in specs} from flopy4.mf6.package import Package @@ -719,7 +719,7 @@ def test_oc_tier_generates_importable_files(dfn_path, tmp_path, all_dfns): target = {n for n in OC_TIER if n in all_dfns} skip = {n for n in all_dfns if n not in target} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, skip=skip) generated = {s.dfn_name: s for s in specs} from flopy4.mf6.package import Package @@ -743,7 +743,7 @@ def test_utl_tier_generates_importable_files(dfn_path, tmp_path, all_dfns): target = {n for n in UTL_TIER if n in all_dfns} skip = {n for n in all_dfns if n not in target} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, makedirs=True, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, makedirs=True, skip=skip) generated = {s.dfn_name: s for s in specs} from flopy4.mf6.package import Package @@ -764,7 +764,7 @@ def test_exg_tier_generates_importable_files(dfn_path, tmp_path, all_dfns): target = {n for n in EXG_TIER if n in all_dfns} skip = {n for n in all_dfns if n not in target} - specs = make_all(dfndir=dfn_path, outdir=tmp_path, fmt=False, makedirs=True, skip=skip) + specs = make_modules(dfndir=dfn_path, outdir=tmp_path, fmt=False, makedirs=True, skip=skip) generated = {s.dfn_name: s for s in specs} from flopy4.mf6.package import Package