From cc76fb59446d845a70ef98fcf8b07a9bf4cd8e49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 12:38:38 +0000 Subject: [PATCH 1/7] ci(release): update version to 1.1.0.dev0 --- CITATION.cff | 2 +- modflowapi/version.py | 4 ++-- version.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 147e7bf..442ae3c 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,7 +3,7 @@ message: If you use this software, please cite both the article from preferred-c and the software itself. type: software title: MODFLOW API -version: 1.0.1 +version: 1.1.0.dev0 date-released: '2026-09-06' abstract: An extension to xmipy for the MODFLOW API. repository-artifact: https://pypi.org/project/modflowapi diff --git a/modflowapi/version.py b/modflowapi/version.py index f229cbb..a83b4a5 100644 --- a/modflowapi/version.py +++ b/modflowapi/version.py @@ -1,3 +1,3 @@ # modflowapi version file automatically created using...update_version.py -# created on...September 06, 2026 12:36:39 -__version__ = "1.0.1" +# created on...September 06, 2026 12:38:38 +__version__ = "1.1.0.dev0" diff --git a/version.txt b/version.txt index 7f20734..64b95b0 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.1 \ No newline at end of file +1.1.0.dev0 \ No newline at end of file From 83cd8b4f733fd01615c063f5e7143a49511a82c9 Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Sun, 6 Sep 2026 08:10:51 -0500 Subject: [PATCH 2/7] ci: run the full test matrix against the release branch (#121) A bot pushes the release branch and opens the release pull request, and neither a push nor a pull_request event fires for a bot using the workflow token, so the 1.0.1 pull request carried no checks at all. Preparing the release ran a single job's worth of tests in passing, which is not the matrix that guards develop. Continuous integration is now callable and takes the ref to test, and the release workflow calls it against the branch it just pushed. Preparing the release no longer runs tests itself, and the pull request is drafted in a separate job that waits for the matrix, so a release that fails the tests never reaches review. --- .github/workflows/ci.yml | 22 +++++++++- .github/workflows/release.yml | 79 ++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb93077..d9f04dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,8 +6,18 @@ on: push: pull_request: branches: [main, develop] + # the release workflow calls this to test a release branch, which a bot + # pushes and so does not raise a push or pull_request event of its own + workflow_call: + inputs: + ref: + description: 'Ref to test. Defaults to the ref that triggered the workflow.' + required: false + type: string concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # distinct from the calling workflow's group, so a called run cannot cancel + # the release that started it + group: ${{ github.workflow }}-${{ inputs.ref || github.ref }}-tests cancel-in-progress: true jobs: @@ -23,6 +33,8 @@ jobs: - name: Checkout repo uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} - name: Setup uv uses: astral-sh/setup-uv@v7 @@ -44,6 +56,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} - name: Setup uv uses: astral-sh/setup-uv@v7 @@ -74,6 +88,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} - name: Setup uv uses: astral-sh/setup-uv@v7 @@ -125,6 +141,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} - name: Setup uv uses: astral-sh/setup-uv@v7 @@ -178,6 +196,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref }} - name: Setup uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9701e71..ba84b6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,10 +50,11 @@ jobs: runs-on: ubuntu-latest permissions: contents: write - pull-requests: write defaults: run: shell: bash + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Check release branch @@ -110,34 +111,6 @@ jobs: uvx ruff format --check . uvx codespell - - name: Install modflow executables - if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} - id: install_modflow - continue-on-error: true - uses: modflowpy/install-modflow-action@v1 - with: - path: ${{ github.workspace }}/autotest - repo: modflow6-nightly-build - - # the download reaches out to github and occasionally has its - # connection reset, failing the job before any test runs - - name: Install modflow executables (retry) - if: ${{ (github.event_name != 'workflow_dispatch' || inputs.run_tests) && steps.install_modflow.outcome == 'failure' }} - uses: modflowpy/install-modflow-action@v1 - with: - path: ${{ github.workspace }}/autotest - repo: modflow6-nightly-build - - # temporary. devtools 2.x will autosync, but 1.x needs opt-in. - - name: Sync devtools - if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} - run: uv run mf sync - - - name: Run tests - if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} - working-directory: ./autotest - run: uv run pytest -v -n auto -m "not mf6" - - name: Generate changelog uses: orhun/git-cliff-action@v4 with: @@ -165,14 +138,11 @@ jobs: sed -i '1i # Changelog' $clog - name: Push release branch - env: - GITHUB_TOKEN: ${{ github.token }} run: | ver="${{ steps.version.outputs.version }}" - # the release notes have already been prepended to HISTORY.md, - # keep a copy for the PR body then drop the standalone changelog - notes=$(grep -v "### Version $ver" CHANGELOG.md) + # the notes are already prepended to HISTORY.md; the pr job reads + # them back from there rather than from this standalone file rm -f CHANGELOG.md git config core.sharedRepository true @@ -182,6 +152,47 @@ jobs: git commit -m "ci(release): set version to $ver, update changelog" git push origin "v$ver" + test: + name: Test release branch + # a bot pushes the release branch, so no push or pull_request event fires + # for it and the pull request would otherwise carry no checks at all + needs: prep + if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} + permissions: + contents: read + uses: ./.github/workflows/ci.yml + with: + ref: v${{ needs.prep.outputs.version }} + + pr: + name: Draft release pull request + needs: [prep, test] + # the tests are skipped when a release is dispatched with run_tests off + if: ${{ always() && needs.prep.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') }} + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + defaults: + run: + shell: bash + steps: + + - name: Checkout release branch + uses: actions/checkout@v7 + with: + ref: v${{ needs.prep.outputs.version }} + + - name: Draft pull request + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + ver="${{ needs.prep.outputs.version }}" + notes=$(awk -v hdr="### Version $ver" ' + index($0, hdr) == 1 { flag = 1; next } + /^### Version / { flag = 0 } + flag' HISTORY.md) + body=' # Release '$ver' From 85b7fd85760ccbbf0b093a98795a5a7ffb7ad5a5 Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Sun, 6 Sep 2026 20:18:20 -0500 Subject: [PATCH 3/7] docs(guide-to-publish): record the conda-forge step (#122) The feedstock bot updates the version number and the checksum and nothing else, so a release that changes dependencies needs the recipe corrected by hand. Both releases so far did: 1.0.0 for the move from setuptools to hatchling, 1.0.1 for the pandas lower bound. The guide described the release as finished once develop was reset, and said nothing about conda-forge at all. --- guide-to-publish.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/guide-to-publish.md b/guide-to-publish.md index 70a9a34..73d16c6 100644 --- a/guide-to-publish.md +++ b/guide-to-publish.md @@ -52,6 +52,23 @@ Merging the release pull request into `main` triggers jobs that: Merge the reset pull request to finish the release. +## 4. conda-forge + +A few hours after the upload to PyPI, a bot opens a version pull request on the +[feedstock](https://github.com/conda-forge/modflowapi-feedstock). To start it immediately instead, +open an issue there titled `@conda-forge-admin, please update version`. + +> [!IMPORTANT] +> The bot updates the version number and the checksum, and nothing else. **Check the recipe's +> `host` and `run` requirements against the dependencies the release actually declares**, which are +> the `Requires-Dist` lines of the sdist on PyPI. Both releases so far needed this by hand: 1.0.0 +> because the build backend had moved from setuptools to hatchling, and 1.0.1 because the pandas +> lower bound had been raised. A maintainer can push the correction to the bot's branch. + +Merging the feedstock pull request builds and uploads the package. It does not appear to a solver +until the channel index is regenerated, which takes up to about an hour; the package is visible on +anaconda.org before then. + ## Changelog conventions Release notes are generated from commit messages, so commits merged to `develop` must follow the From 3b93eb7346936cc4d5d1cee1d8791f7b5ba106d3 Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Mon, 2 Nov 2026 10:35:40 -0600 Subject: [PATCH 4/7] fix(sfr): read bedk, manning, and upstream_frac from the input context (#128) MODFLOW 6.8.2 reads SFR packagedata through IDM and keeps HK, ROUGH, and USTRF only as deprecated API aliases, so every SFR model run through modflowapi writes three deprecation warnings to mfsim.lst. The packagedata hk, rough, and ustrf columns are now read from __INPUT__///BEDK, MANNING, and UPSTREAM_FRAC when libmf6 has those addresses, and from the legacy names otherwise; the column names are unchanged. * adv_input_vars in datamodel.py maps a column to its input context name. * test_sfr_input_context_names checks the addresses, values, and a write that persists into the next stress period, against a small SFR model whose input files the test writes as text; it passes against 6.8.1 and current MODFLOW 6 develop. --- autotest/test_interface.py | 152 +++++++++++++++++++++++++++++ modflowapi/extensions/data.py | 4 +- modflowapi/extensions/datamodel.py | 10 ++ modflowapi/extensions/pakbase.py | 12 ++- 4 files changed, 176 insertions(+), 2 deletions(-) diff --git a/autotest/test_interface.py b/autotest/test_interface.py index 79a0fcc..0c6a932 100644 --- a/autotest/test_interface.py +++ b/autotest/test_interface.py @@ -1,6 +1,7 @@ import shutil from pathlib import Path from platform import system +from textwrap import dedent import numpy as np import pytest @@ -341,6 +342,157 @@ def callback(sim, step): raise Exception(e) +def test_sfr_input_context_names(function_tmpdir): + def callback(sim, step): + if step != Callbacks.timestep_start or sim.kstp != 0: + return + model = sim.get_model() + sfr = model.get_package("sfr_0") + if sim.kper == 1: + # the write persists into the next time step + np.testing.assert_allclose(sfr.packagedata.values["hk"], written["hk"], err_msg="hk write did not persist") + written["persisted"] = True + return + if f"__INPUT__/{model.name}/SFR_0/BEDK" in sim.mf6.get_input_var_names(): + path = f"__INPUT__/{model.name}/SFR_0" + names = ("BEDK", "MANNING", "UPSTREAM_FRAC") + else: + path = f"{model.name}/SFR_0" + names = ("HK", "ROUGH", "USTRF") + addrs = [f"{path}/{name}" for name in names] + for addr in addrs: + assert addr in sfr._package_var_addrs, f"{addr} not used" + + pd = sfr.packagedata.values + for column, values in expected.items(): + np.testing.assert_allclose(pd[column], values, err_msg=f"{column} does not match the input") + + pd["hk"] *= 10.0 + sfr.packagedata = pd + np.testing.assert_allclose(sim.mf6.get_value(addrs[0]), pd["hk"], err_msg="hk is not being properly set") + written["hk"] = pd["hk"].copy() + + written = {} + + # three reaches in a row of three cells; reach 1 flows to reach 2 and + # diverts to reach 3 + expected = {"hk": [0.1, 0.2, 0.3], "rough": [0.03, 0.035, 0.04], "ustrf": [1.0, 1.0, 0.0]} + packagedata = "\n".join( + f" {i + 1} 1 1 {i + 1} 100.0 {width} 0.001 {rtp} 1.0 {hk} {rough} {ncon} {ustrf} {ndiv}" + for i, (width, rtp, hk, rough, ncon, ustrf, ndiv) in enumerate( + zip( + [10.0, 10.0, 5.0], + [9.0, 8.9, 8.9], + expected["hk"], + expected["rough"], + [2, 1, 1], + expected["ustrf"], + [1, 0, 0], + ) + ) + ) + files = { + "mfsim.nam": """ + BEGIN timing + TDIS6 sim.tdis + END timing + BEGIN models + gwf6 sfr.nam sfr + END models + BEGIN exchanges + END exchanges + BEGIN solutiongroup 1 + ims6 sim.ims sfr + END solutiongroup + """, + "sim.tdis": """ + BEGIN dimensions + NPER 2 + END dimensions + BEGIN perioddata + 1.0 1 1.0 + 1.0 1 1.0 + END perioddata + """, + "sim.ims": "", + "sfr.nam": """ + BEGIN packages + DIS6 sfr.dis dis + NPF6 sfr.npf npf + IC6 sfr.ic ic + CHD6 sfr.chd chd_0 + SFR6 sfr.sfr sfr_0 + END packages + """, + "sfr.dis": """ + BEGIN dimensions + NLAY 1 + NROW 1 + NCOL 3 + END dimensions + BEGIN griddata + delr + CONSTANT 100.0 + delc + CONSTANT 100.0 + top + CONSTANT 10.0 + botm + CONSTANT 0.0 + END griddata + """, + "sfr.npf": """ + BEGIN griddata + icelltype + CONSTANT 0 + k + CONSTANT 10.0 + END griddata + """, + "sfr.ic": """ + BEGIN griddata + strt + CONSTANT 8.0 + END griddata + """, + "sfr.chd": """ + BEGIN dimensions + MAXBOUND 1 + END dimensions + BEGIN period 1 + 1 1 1 8.0 + END period + """, + "sfr.sfr": """ + BEGIN dimensions + NREACHES 3 + END dimensions + BEGIN packagedata + {packagedata} + END packagedata + BEGIN connectiondata + 1 -2 -3 + 2 1 + 3 1 + END connectiondata + BEGIN diversions + 1 1 3 UPTO + END diversions + BEGIN period 1 + 1 INFLOW 10.0 + 1 DIVERSION 1 2.0 + END period + """, + } + test_pth = function_tmpdir / "sfr_model" + test_pth.mkdir() + for name, text in files.items(): + (test_pth / name).write_text(dedent(text).format(packagedata=packagedata)) + + run_simulation(so, test_pth, callback) + assert written.get("persisted"), "hk was not checked in the second stress period" + + def test_rhs_hcof_advanced(function_tmpdir): def callback(sim, step): model = sim.test_model diff --git a/modflowapi/extensions/data.py b/modflowapi/extensions/data.py index c104da2..80aa0bb 100644 --- a/modflowapi/extensions/data.py +++ b/modflowapi/extensions/data.py @@ -110,7 +110,9 @@ def _set_data(self): else: continue - reduced = var_addr.split("/")[-1].lower() + # keep the column name of a variable read from the input context + column_names = getattr(self.parent, "_column_names", {}) + reduced = column_names.get(var_addr, var_addr.split("/")[-1].lower()) if reduced in ("maxbound", "nbound"): setattr(self, f"_{reduced}", values) if not self._spd and reduced == "maxbound": diff --git a/modflowapi/extensions/datamodel.py b/modflowapi/extensions/datamodel.py index 669a0d5..1b1b1e7 100644 --- a/modflowapi/extensions/datamodel.py +++ b/modflowapi/extensions/datamodel.py @@ -275,6 +275,16 @@ "maw": {"packagedata": ["nmawwells", ("ifno:range:nmawwells", "radius", "bot", "strt", "ngwfnodes")]}, } +# advanced package variables read from the input context when libmf6 has them, +# keyed by column name, with the input context name +adv_input_vars = { + "sfr": { + "hk": "BEDK", + "rough": "MANNING", + "ustrf": "UPSTREAM_FRAC", + }, +} + def get_package_type(pkg_type): from .advpaks import LakPackage, MawPackage, SfrPakage, UzfPackage diff --git a/modflowapi/extensions/pakbase.py b/modflowapi/extensions/pakbase.py index 2a48c9b..29e2464 100644 --- a/modflowapi/extensions/pakbase.py +++ b/modflowapi/extensions/pakbase.py @@ -1,7 +1,7 @@ import numpy as np from .data import AdvancedInput, ArrayInput, ListInput, ScalarInput -from .datamodel import adv_pkgvars, pkgvars +from .datamodel import adv_input_vars, adv_pkgvars, pkgvars class PackageBase: @@ -468,6 +468,10 @@ def __init__(self, model, pkg_type, pkg_name, sim_package=False): self._sp_var_addrs = [] self._package_vars = None self._sp_vars = None + self._input_vars = adv_input_vars.get(pkg_type, {}) + self._column_names = {} + if self._input_vars: + self._input_var_names = set(self.model.mf6.get_input_var_names()) if pkg_type in adv_pkgvars: self._adv_var_dict = adv_pkgvars[pkg_type] @@ -550,6 +554,12 @@ def _get_advanced_variable_addr(self, var_str): ------- var_addr : str """ + var = var_str.lower() + if var in self._input_vars: + s = f"__INPUT__/{self.model.name}/{self.pkg_name}/{self._input_vars[var]}" + if s in self._input_var_names: + self._column_names[s] = var + return s s = f"{self.model.name}/{self.pkg_name}/{var_str.upper()}" return s From 4fda67c8d403506c299b28b1d18dd88aa7b7e36b Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Mon, 2 Nov 2026 11:37:56 -0500 Subject: [PATCH 5/7] chore(dependencies): bump min numpy to 2.2.0, pandas to 2.3.0 (#126) SPEC 0 recommends dropping numpy <=2.1.0 (as of 2026 Q3) and pandas <=2.2.0 (as of 2026 Q1), numpy 2.2.0 and pandas 2.3.0 are now the minimum supported versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 054124b..e58dd79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,8 +33,8 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "numpy>=1.23", - "pandas>=2.2.2", + "numpy>=2.2.0", + "pandas>=2.3.0", "xmipy>=1.5.0", ] From 99f2f2c05bfcdc89077bd64791fb93af3a5004b4 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Mon, 2 Nov 2026 11:39:12 -0500 Subject: [PATCH 6/7] chore(dependencies): bump minimum Python to 3.12 (#125) SPEC 0 recommends dropping Python 3.11 as of 2025 Q4, leaving 3.12 as the minimum supported version --- .github/workflows/ci.yml | 6 +++--- README.md | 2 +- pyproject.toml | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9f04dc..693fe6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - python-version: [ "3.11", "3.12", "3.13", "3.14" ] + python-version: [ "3.12", "3.13", "3.14" ] defaults: run: shell: bash @@ -134,7 +134,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] - python-version: [ "3.11", "3.12", "3.13", "3.14" ] + python-version: [ "3.12", "3.13", "3.14" ] defaults: run: shell: bash @@ -189,7 +189,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - python-version: [ "3.11", "3.12", "3.13", "3.14" ] + python-version: [ "3.12", "3.13", "3.14" ] defaults: run: shell: bash diff --git a/README.md b/README.md index 8e05d20..8d6ce26 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ and additional functionality specific to the MODFLOW API. Currently it is a join ## Installation -`modflowapi` requires Python 3.11+, with: +`modflowapi` requires Python 3.12+, with: ```shell numpy diff --git a/pyproject.toml b/pyproject.toml index e58dd79..c7f0458 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,13 +25,12 @@ classifiers = [ "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering :: Hydrology", ] -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ "numpy>=2.2.0", "pandas>=2.3.0", From 13a166f9d271811f1b2a12376345d31a4a0f6996 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:46:32 +0000 Subject: [PATCH 7/7] ci(release): set version to 1.0.2, update changelog --- CITATION.cff | 4 ++-- HISTORY.md | 6 ++++++ modflowapi/version.py | 4 ++-- version.txt | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 442ae3c..e102c89 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,8 +3,8 @@ message: If you use this software, please cite both the article from preferred-c and the software itself. type: software title: MODFLOW API -version: 1.1.0.dev0 -date-released: '2026-09-06' +version: 1.0.2 +date-released: '2026-11-02' abstract: An extension to xmipy for the MODFLOW API. repository-artifact: https://pypi.org/project/modflowapi repository-code: https://github.com/MODFLOW-ORG/modflowapi diff --git a/HISTORY.md b/HISTORY.md index 10b71ff..6201050 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,10 @@ # Changelog +### Version 1.0.2 + +#### Bug fixes + +* [fix(sfr)](https://github.com/MODFLOW-ORG/modflowapi/commit/3b93eb7346936cc4d5d1cee1d8791f7b5ba106d3): Read bedk, manning, and upstream_frac from the input context (#128). Committed by Joseph Hughes on 2026-11-02. + ### Version 1.0.1 #### Bug fixes diff --git a/modflowapi/version.py b/modflowapi/version.py index a83b4a5..dd36f06 100644 --- a/modflowapi/version.py +++ b/modflowapi/version.py @@ -1,3 +1,3 @@ # modflowapi version file automatically created using...update_version.py -# created on...September 06, 2026 12:38:38 -__version__ = "1.1.0.dev0" +# created on...November 02, 2026 16:46:29 +__version__ = "1.0.2" diff --git a/version.txt b/version.txt index 64b95b0..e6d5cb8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0.dev0 \ No newline at end of file +1.0.2 \ No newline at end of file