From fcfefb0b4587d15beb15e5eecd9b42aade83c9fd Mon Sep 17 00:00:00 2001 From: Joseph Hughes Date: Wed, 21 Oct 2026 10:32:51 -0400 Subject: [PATCH] feat(get-modflow): accept any repository with standard release assets get-modflow only accepted the executables, modflow6, and modflow6-nightly-build repositories, so a build published from another repository could not be installed even with --owner. Any repository can now be named with --repo or the repo argument, provided its release assets are named with the operating system tag like those of the official repositories. A repository without a renamed prefix uses its name as the prefix of the downloaded archive. --- .docs/md/get_modflow.md | 4 ++-- autotest/test_get_modflow.py | 26 ++++++++++++++++++++++++++ flopy/utils/get_modflow.py | 21 +++++++++++---------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/.docs/md/get_modflow.md b/.docs/md/get_modflow.md index 63ab79c6af..31ea146b6b 100644 --- a/.docs/md/get_modflow.md +++ b/.docs/md/get_modflow.md @@ -82,10 +82,10 @@ By default the distribution from the [`MODFLOW-ORG/executables` repository](http - `zbud6` - `libmf6.dylib` -To select a distribution, specify a repository name with the `--repo` command line option or the `repo` function argument. Valid names are: +To select a distribution, specify a repository name with the `--repo` command line option or the `repo` function argument. The official repositories are: - `executables` (default) - `modflow6` - `modflow6-nightly-build` -The repository owner can also be configured with the `--owner` option. This can be useful for installing from unreleased MODFLOW 6 feature branches still in development — the only compatibility requirement is that release assets be named identically to those on the official repositories. +The repository owner can also be configured with the `--owner` option. Together with `--repo`, this can be used to install from any repository, such as one that builds an unreleased MODFLOW 6 feature branch still in development — the only compatibility requirement is that release assets be named like those on the official repositories, with the operating system tag (e.g. `linux.zip`, `mac.zip`, `macarm.zip`, or `win64.zip`). diff --git a/autotest/test_get_modflow.py b/autotest/test_get_modflow.py index a21b1c744c..66a572ed61 100644 --- a/autotest/test_get_modflow.py +++ b/autotest/test_get_modflow.py @@ -290,6 +290,32 @@ def test_cli_omitted_retry_flags_are_none(monkeypatch): assert captured["retry_delay"] is None +def test_cli_accepts_any_repo(monkeypatch): + captured = {} + monkeypatch.setattr( + get_modflow, "run_main", lambda **kwargs: captured.update(kwargs) + ) + monkeypatch.setattr( + sys, + "argv", + ["get_modflow.py", str(Path.home()), "--owner", "me", "--repo", "my-build"], + ) + + get_modflow.cli_main() + + assert captured["owner"] == "me" + assert captured["repo"] == "my-build" + + +def test_run_main_any_repo(tmp_path, monkeypatch): + # a repo that is not an official repository reaches the release assets + release = {"tag_name": "v1", "assets": [], "html_url": "https://example.com"} + monkeypatch.setattr(get_modflow, "get_release", lambda *args, **kwargs: release) + + with pytest.raises(ValueError, match="could not find ostag"): + run_main(tmp_path, owner="me", repo="my-build", ostag="linux", quiet=True) + + @flaky @requires_github @pytest.mark.parametrize("repo", repo_options.keys()) diff --git a/flopy/utils/get_modflow.py b/flopy/utils/get_modflow.py index 2528712da2..5ae47e391c 100755 --- a/flopy/utils/get_modflow.py +++ b/flopy/utils/get_modflow.py @@ -31,7 +31,8 @@ default_owner = "MODFLOW-ORG" default_repo = "executables" -# key is the repo name, value is the renamed file prefix for the download +# key is the repo name, value is the renamed file prefix for the download; +# any other repo uses its name as the prefix renamed_prefix = { "modflow6": "modflow6", "executables": "modflow_executables", @@ -407,8 +408,10 @@ def run_main( owner : str, default "MODFLOW-ORG" Name of GitHub repository owner (user or organization). repo : str, default "executables" - Name of GitHub repository. Choose one of "executables" (default), - "modflow6", or "modflow6-nightly-build". + Name of GitHub repository, such as "executables" (default), + "modflow6", or "modflow6-nightly-build". Any other repository can be + used if its release assets are named like those of these + repositories, with the operating system tag (e.g. "linux.zip"). release_id : str, default "latest" GitHub release ID. ostag : str, optional @@ -504,10 +507,6 @@ def run_main( elif not os.access(bindir, os.W_OK): raise OSError(f"extraction directory '{bindir}' is not writable") - # make sure repo option is valid - if repo not in available_repos: - raise KeyError(f"repo {repo!r} not supported; choose one of {available_repos}") - # get the selected release release = get_release( owner, repo, release_id, quiet, tries=tries, delay=retry_delay @@ -529,7 +528,8 @@ def run_main( dst_fname = "-".join([repo, release["tag_name"], ostag]) + asset_suffix else: # change local download name so it is more unique - dst_fname = "-".join([renamed_prefix[repo], release["tag_name"], asset_name]) + prefix = renamed_prefix.get(repo, repo) + dst_fname = "-".join([prefix, release["tag_name"], asset_name]) tmpdir = None if downloads_dir is None: downloads_dir = Path.home() / "Downloads" @@ -801,9 +801,10 @@ def cli_main(): ) parser.add_argument( "--repo", - choices=available_repos, default=default_repo, - help=f"Name of GitHub repository; default is '{default_repo}'.", + help=f"Name of GitHub repository, such as {', '.join(available_repos)}; " + f"default is '{default_repo}'. Release assets of any other repository " + "must be named like those of these repositories.", ) parser.add_argument( "--release-id",