From 8d1cd00b4f290de2956ee13b075d8b36bba6b3f9 Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 13 Aug 2026 20:50:14 +1200 Subject: [PATCH] chore: use dependency groups (PEP 735) --- .github/workflows/optional.yml | 8 ++--- .github/workflows/rtd.yml | 2 +- DEVELOPER.md | 36 ++++++++++++++----- autotest/conftest.py | 8 ++--- flopy/utils/utl_import.py | 2 +- pyproject.toml | 65 ++++++++++++++++++++-------------- 6 files changed, 76 insertions(+), 45 deletions(-) diff --git a/.github/workflows/optional.yml b/.github/workflows/optional.yml index 0eb50aad99..1e644a47d9 100644 --- a/.github/workflows/optional.yml +++ b/.github/workflows/optional.yml @@ -32,8 +32,8 @@ jobs: with: cache-dependency-glob: "**/pyproject.toml" - - name: Install FloPy - run: uv sync --extra test + - name: Install FloPy without optional dependencies + run: uv sync --only-group test - name: Install other dependencies run: | @@ -41,14 +41,14 @@ jobs: # If matrix.optdeps is "some" remove 3 optional dependencies # selected randomly using the current date as the seed. if [[ ! "${{ matrix.optdeps }}" == *"no"* ]]; then - uv pip install ".[optional]" + uv sync --extra optional fi if [[ "${{ matrix.optdeps }}" == *"some"* ]]; then deps=$(sed '/optional =/,/]/!d' pyproject.toml | sed -e '1d;$d' -e 's/\"//g' -e 's/,//g' | tr -d ' ' | cut -f 1 -d ';') rmvd=$(echo $deps | tr ' ' '\n' | shuf --random-source <(yes date +%d.%m.%y) | head -n 3) echo "Removing optional dependencies: $rmvd" >> removed_dependencies.txt cat removed_dependencies.txt - uv pip uninstall --yes $rmvd + uv pip uninstall $rmvd fi - name: Upload removed dependencies log diff --git a/.github/workflows/rtd.yml b/.github/workflows/rtd.yml index 7ebd0bbe73..3e21502f38 100644 --- a/.github/workflows/rtd.yml +++ b/.github/workflows/rtd.yml @@ -96,7 +96,7 @@ jobs: working-directory: modflow6 run: | pixi run -e rtd install - pixi run -e rtd pip install "../flopy[optional,test]" + pixi run -e rtd pip install "../flopy[optional]" - name: Workaround OpenGL issue on Linux if: runner.os == 'Linux' diff --git a/DEVELOPER.md b/DEVELOPER.md index 0f0943a75e..bc275dd5dc 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -62,21 +62,39 @@ git config blame.ignoreRevsFile .git-blame-ignore-revs This project has historically aimed to support a wide range of [Python versions](https://devguide.python.org/versions/). In current and future development this window may narrow to follow [SPEC 0](https://scientific-python.org/specs/spec-0000/#support-window) instead. -Install Python >=3.10 via [standalone download](https://www.python.org/downloads/) or a distribution like [Anaconda](https://www.anaconda.com/products/individual) or [miniconda](https://docs.conda.io/en/latest/miniconda.html). +Install Python >=3.10 via [standalone download](https://www.python.org/downloads/), [uv](https://docs.astral.sh/uv/), or a conda-like distribution like [Anaconda](https://www.anaconda.com/products/individual), [miniconda](https://docs.conda.io/en/latest/miniconda.html) or [miniforge](https://github.com/conda-forge/miniforge) . -Then install FloPy and core dependencies from the project root: +Then install FloPy and core dependencies from the project root path: ```sh -pip install . +pip install -e . ``` -The FloPy package has a number of [optional dependencies](.docs/optional_dependencies.md), as well as extra dependencies required for linting, testing, and building documentation. Extra dependencies are listed in the `test`, `lint`, `optional`, and `doc` groups under the `[project.optional-dependencies]` section in `pyproject.toml`. Core, linting, testing and optional dependencies are included in the Conda environment in `etc/environment.yml`. Only core dependencies are included in the PyPI package — to install extra dependency groups with pip, use `pip install ".[]"`. For instance, to install all development dependencies: +The `-e` option installs FloPy as an "editable" package to implement and test changes iteratively, and is recommended for developers. + +FloPy has a number of user-facing [optional dependencies](.docs/md/optional_dependencies.md) and developer-facing dependencies needed for linting and testing. These are handled differently, depending on the Python developer environment. + +#### Pip-like + +Pip and related tools (pipenv, uv) declare their optional dependencies under the `[project.optional-dependencies]` section in `pyproject.toml`, and can be installed with: ```sh -pip install ".[dev]" +pip install -e ".[optional]" ``` -Alternatively, with Anaconda or Miniconda: +Developer-facing dependencies used for internal development, and are grouped by the following group labels: `test`, `lint` and `docs`. A `dev` group combines `test` and `lint` with the `optional` and `codegen` extras, and is recommended for developers. These dependencies are listed under the `[dependency-groups]` section in `pyproject.toml` + +Dependency groups can be installed with `uv` or `pip` version 25.1 (2025-04-26), which support the `--group ` option: + +```sh +pip install --group dev +``` + +Note that `uv sync` includes the `dev` group by default. + +#### Conda-like + +Conda environments can be created using a file `etc/environment.yml`, which includes optional dependencies, and dependencies for development. This development environment can be installed and activated with: ```sh conda env create -f etc/environment.yml @@ -180,10 +198,10 @@ To convert a Python example script to an `.ipynb` notebook, run: jupytext --from py --to ipynb path/to/script.py ``` -To work with `.ipynb` notebooks from a browser interface, you will need `jupyter` installed (`jupyter` is included with the `test` optional dependency group in `pyproject.toml`). Some of the notebooks use testing dependencies and [optional dependencies](.docs/optional_dependencies.md) as well. The conda environment provided in `etc/environment.yml` already includes all dependencies needed to run the examples. To install all development dependencies at once using `pip`: +To work with `.ipynb` notebooks from a browser interface, you will need `jupyter` installed (`jupyter` is included with the `test` dependency group in `pyproject.toml`). Some of the notebooks use testing dependencies and [optional dependencies](.docs/md/optional_dependencies.md) as well. The conda environment provided in `etc/environment.yml` already includes all dependencies needed to run the examples. To install all development dependencies at once using `pip`: ```sh -pip install ".[dev]" +pip install --group dev ``` To start a local Jupyter notebook server, run: @@ -242,7 +260,7 @@ Each example should create and (attempt to) dispose of its own isolated temporar ## Tests -To run the tests you will need `pytest` and a few plugins, including [`pytest-xdist`](https://pytest-xdist.readthedocs.io/en/latest/), [`pytest-dotenv`](https://github.com/quiqua/pytest-dotenv), and [`pytest-benchmark`](https://pytest-benchmark.readthedocs.io/en/latest/index.html). Test dependencies are specified in the `test` extras group in `pyproject.toml` (with pip, use `pip install ".[test]"`). Test dependencies are included in the Conda environment `etc/environment`. +To run the tests you will need `pytest` and a few plugins, including [`pytest-xdist`](https://pytest-xdist.readthedocs.io/en/latest/), [`pytest-dotenv`](https://github.com/quiqua/pytest-dotenv), and [`pytest-benchmark`](https://pytest-benchmark.readthedocs.io/en/latest/index.html). See details installing test dependencies for [pip-like](#pip-like) or [Conda-like](#conda-like) environments. **Note:** tests require the [`modflow-devtools`](https://github.com/MODFLOW-ORG/modflow-devtools) package, which is a grab bag of utilities and `pytest` fixtures shared by FloPy, MODFLOW 6, and other related projects. If you see testing errors that don't seem related to the contents of the tests, updating to the latest `modflow-devtools` is recommended as a first troubleshooting step. diff --git a/autotest/conftest.py b/autotest/conftest.py index 036ce9e675..2684820b75 100644 --- a/autotest/conftest.py +++ b/autotest/conftest.py @@ -134,10 +134,10 @@ def pytest_report_header(config): except metadata.PackageNotFoundError: items.append(f"{name} (not found)") lines.append("required packages: " + ", ".join(items)) - for optional in ["optional", "test"]: + for group in ["optional"]: installed = [] not_found = [] - for name in extra[optional]: + for name in extra[group]: if name in processed: continue processed.add(name) @@ -147,7 +147,7 @@ def pytest_report_header(config): except metadata.PackageNotFoundError: not_found.append(name) if installed: - lines.append(f"{optional} packages: {', '.join(installed)}") + lines.append(f"{group} packages: {', '.join(installed)}") if not_found: - lines.append(f"{optional} packages not found: {', '.join(not_found)}") + lines.append(f"{group} packages not found: {', '.join(not_found)}") return "\n".join(lines) diff --git a/flopy/utils/utl_import.py b/flopy/utils/utl_import.py index a936344bfb..688005cfd3 100644 --- a/flopy/utils/utl_import.py +++ b/flopy/utils/utl_import.py @@ -45,7 +45,7 @@ from .parse_version import Version -# Update .docs/optional_dependencies.md when updating versions! +# Update .docs/md/optional_dependencies.md when updating versions! VERSIONS = { "shapefile": "2.0.0", diff --git a/pyproject.toml b/pyproject.toml index eb0b5bb602..55bfb0c58d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ dynamic = ["version", "readme"] [project.optional-dependencies] -dev = ["flopy[codegen,lint,test,optional,doc]", "tach"] codegen = [ "Jinja2>=3.0", "boltons", @@ -41,28 +40,6 @@ codegen = [ "tomli", "tomli-w" ] -lint = ["cffconvert", "codespell[toml] >=2.2.2", "ruff"] -test = [ - "flopy[lint]", - "boltons", - "coverage !=7.6.5", - "flaky", - "filelock", - "jupyter", - "jupyter_client >=8.4.0", # avoid datetime.utcnow() deprecation warning - "jupytext", - "modflow-devtools>=1.7.0,!=1.9.0,<2", - "pytest !=8.1.0", - "pytest-benchmark", - "pytest-cov", - "pytest-dotenv", - "pytest-xdist", - "pyzmq >=25.1.2", - "syrupy <5.0.0", - "tomli", - "tomli-w", - "virtualenv" -] optional = [ "affine", "descartes", @@ -86,9 +63,38 @@ optional = [ "h5py", "scikit-learn" ] -doc = [ - "flopy[optional]", - "ipython[kernel]", + +[dependency-groups] +lint = [ + "cffconvert", + "codespell[toml] >=2.2.2", + "ruff", +] +test = [ + "boltons", + "coverage !=7.6.5", + "filelock", + "flaky", + "jupyter", + "jupyter_client >=8.4.0", # avoid datetime.utcnow() deprecation warning + "jupytext", + "modflow-devtools>=1.7.0,!=1.9.0,<2", + "pytest !=8.1.0", + "pytest-benchmark", + "pytest-cov", + "pytest-dotenv", + "pytest-xdist", + "pyzmq >=25.1.2", + "syrupy <5.0.0", + "tomli", + "tomli-w", + "virtualenv", + {include-group = "lint"}, +] +docs = [ + "flopy[optional]", # self-reference from project.optional-dependencies + "ipython", + "ipykernel", "jupytext", "myst-parser", "nbconvert <7.14.0", @@ -98,6 +104,13 @@ doc = [ "sphinx ==7.1.2", "sphinx-rtd-theme >=1", ] +dev = [ + "flopy[codegen,optional]", # self-reference from project.optional-dependencies + "tach", + {include-group = "lint"}, + {include-group = "test"}, + # {include-group = "docs"}, +] [project.scripts] get-modflow = "flopy.utils.get_modflow:cli_main"