Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,46 @@ jobs:
- name: Sync devtools
run: pixi run mf sync

- name: Show MF6 sync status
run: pixi run flopy4 mf6 status

- name: Run tests
run: |
pixi run python -m ipykernel install --name flopy4 --user
pixi run test

test-mf6-release:
name: Test (latest MF6 release)
needs:
- build
- lint
runs-on: ubuntu-latest
steps:

- name: Checkout repo
uses: actions/checkout@v4

- name: Setup pixi
uses: prefix-dev/setup-pixi@v0.9.6
with:
pixi-version: v0.72.0
environments: test313
activate-environment: true

# temporary. devtools 2.x will autosync, but 1.x needs opt-in.
- name: Sync devtools
run: pixi run mf sync

- name: Sync classes and binary to the latest MF6 release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
pixi run flopy4 mf6 sync latest --install --bindir "$HOME/.local/bin" --verbose
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Show MF6 sync status
run: pixi run flopy4 mf6 status

- name: Run tests
run: |
pixi run python -m ipykernel install --name flopy4 --user
Expand Down
5 changes: 5 additions & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ parts:
- file: examples/circle
title: Circle

- caption: Guide
chapters:
- file: mf6-versions
title: MF6 versions

- caption: Developer Notes
chapters:
- file: dev/srs
Expand Down
40 changes: 40 additions & 0 deletions docs/mf6-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# MF6 versions

`flopy4.mf6`'s classes are generated from MF6's definition (DFN) files, so
each set of classes matches one MF6 version. `flopy4.mf6.MF6_VERSION` says
which, and `flopy4/mf6/_contract.py` also records the DFN commit and the
generated files.

## Supported versions

flopy4 is tested against the latest MF6 release and MF6's `develop` branch
(the nightly build). A flopy4 release ships classes synced to a tagged MF6
release. Older MF6 versions usually work if you sync to them, but aren't
tested.

## Checking

`Simulation.run` warns if the MF6 executable it runs doesn't match the
synced version. To check by hand:

```
flopy4 mf6 status
```

## Syncing

To regenerate the classes for another MF6 version:

```
flopy4 mf6 sync 6.8.1 # a release
flopy4 mf6 sync develop # the develop branch
flopy4 mf6 sync path/to/dfn # a local DFN directory
flopy4 mf6 sync 6.8.1 --install # also install that release's mf6
```

Or from Python, `flopy4.mf6.sync("6.8.1")`.

Sync writes into the installed `flopy4` package, so install flopy4 into a
virtual environment. A failed sync leaves the previous classes in place.
`pip install --upgrade flopy4` restores the release's classes; sync again
afterwards if you need a different MF6 version.
11 changes: 9 additions & 2 deletions flopy4/mf6/_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,15 +410,22 @@ def sync(
f"source must be 'owner/repo@ref', a ref, or a local directory; got: {release_id!r}"
)
registry = RemoteDfnRegistry(release_id=release_id)
owner_repo, ref = release_id.split("@", 1)
if ref == "latest":
# "latest" isn't a git ref: pin the release it points to.
tag = registry.latest_tag()
if not tag or tag == "latest":
raise SyncError(f"Couldn't resolve the latest release of {owner_repo}.")
release_id = f"{owner_repo}@{tag}"
registry = RemoteDfnRegistry(release_id=release_id)
source = release_id
version = mf6_version or registry.latest_tag() or "unknown"
# Branch refs are mutable: always re-fetch unless the version is a
# pinned semver tag (first char is a digit).
is_branch = not version[0].isdigit()
_populate_remote_cache(registry, release_id, force=force or is_branch)
dfn_dir = registry.cache_path
owner_repo, ref = release_id.split("@", 1)
commit = _remote_commit(f"{owner_repo}@{version if ref == 'latest' else ref}")
commit = _remote_commit(release_id)

if version == "unknown":
warnings.warn(
Expand Down
29 changes: 29 additions & 0 deletions test/mf6/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import attrs
import pytest


def _maw_strt_takes_ts() -> bool:
from flopy4.mf6.gwf.maw import Maw

return bool(attrs.fields_dict(Maw.Packagedata)["strt"].metadata.get("time_series"))


# Models that MF6 runs but whose input some released DFNs don't describe,
# with a check of the synced classes for what each model needs. Checked
# against the classes, not the MF6 version, so a model runs again once a
# release's DFNs describe it.
SPEC_GAPS = {
"mf6/test/test020_NevilleTonkinTransient_constantMAW": (
_maw_strt_takes_ts,
"MAW strt is a time series, which DFNs before MF6 #2748 don't declare",
),
}


def pytest_collection_modifyitems(items):
unmet = {model: reason for model, (check, reason) in SPEC_GAPS.items() if not check()}
for item in items:
callspec = getattr(item, "callspec", None)
model = callspec.params.get("model_name") if callspec else None
if model in unmet:
item.add_marker(pytest.mark.skip(reason=unmet[model]))
13 changes: 13 additions & 0 deletions test/mf6/test_mf6_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,16 @@ def test_install_version_mismatch_warns(dfn_path, outdir, monkeypatch, fake_inst
monkeypatch.setattr("flopy4.mf6._compat._query_mf6_version", lambda exe: "6.5.0")
with pytest.warns(UserWarning, match="reports MF6 6.5.0"):
sync("6.6.0", mf6_version="6.6.0", install=True, outdir=outdir)


def test_latest_pins_tag(dfn_path, outdir, monkeypatch, fake_install):
""" "latest" isn't a git ref: sync pins the release it points to."""
from modflow_devtools.dfns import RemoteDfnRegistry

monkeypatch.setattr(RemoteDfnRegistry, "cache_path", property(lambda self: dfn_path))
monkeypatch.setattr(RemoteDfnRegistry, "latest_tag", lambda self: "6.8.1")
monkeypatch.setattr("flopy4.mf6._compat._query_mf6_version", lambda exe: None)
result = sync("latest", install=True, outdir=outdir)
assert result.source == "MODFLOW-ORG/modflow6@6.8.1"
assert result.version == "6.8.1"
assert fake_install[0]["version"] == "6.8.1"
Loading