From cfd57b072011d82322e74722bd56b1abbaca6af6 Mon Sep 17 00:00:00 2001 From: James McCreight Date: Mon, 24 Aug 2026 16:34:11 -0600 Subject: [PATCH 1/4] maintenance: conda-forge 3.0.0 build 1 published, feedstock item done The feedstock PR #14 upload landed (pyprms >=0.10.0 replaces packaging <26.3), so the last packaging <26.3 pin anywhere is gone. Move the item from Open to Done. --- MAINTENANCE.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index d9b9f2a9..5b6d7bf2 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -4,7 +4,6 @@ - [Maintenance ledger](#maintenance-ledger) - [Open](#open) - [3.0.1 hotfix release (flopy pin unwind + pyPRMS floor)](#301-hotfix-release-flopy-pin-unwind--pyprms-floor) - - [conda-forge feedstock: pyprms floor replaces packaging pin](#conda-forge-feedstock-pyprms-floor-replaces-packaging-pin) - [pyPRMS upstream: close PR #64, port its regression test](#pyprms-upstream-close-pr-64-port-its-regression-test) - [Reconcile check_version.yaml with release_preflight.sh](#reconcile-check_versionyaml-with-release_preflightsh) - [Deliver CI-usage findings to org admins](#deliver-ci-usage-findings-to-org-admins) @@ -51,16 +50,6 @@ check it), **Action** (what to do once unblocked), and optional released flopy until then; flopy imports lazily so this is not an import-time problem. -### conda-forge feedstock: pyprms floor replaces packaging pin - -- **Blocked on:** feedstock PR #14 (pyprms >=0.10.0 floor, build 1) - MERGED 2026-08-19; awaiting the main-branch CI upload. Check: - `https://api.anaconda.org/package/conda-forge/pywatershed` shows a - 3.0.0 build with build number 1. -- **Action:** verify the new build's run deps carry `pyprms >=0.10.0` - and no `packaging <26.3`, then move to Done. This was the last - `packaging <26.3` pin anywhere. - ### pyPRMS upstream: close PR #64, port its regression test - **Blocked on:** nothing — #64 was closed unmerged 2026-08-17; the @@ -92,6 +81,11 @@ check it), **Action** (what to do once unblocked), and optional ## Done +- 2026-08-19: conda-forge pywatershed 3.0.0 build 1 published (feedstock + PR #14: `pyprms >=0.10.0` replaces `packaging <26.3`; verified on + anaconda.org, uploaded 19:35 UTC). The last `packaging <26.3` pin + anywhere is gone. Also: PR #407's sagehen ubuntu OOM resolved by + serial pytest (`-n=1`) on Linux. - 2026-08-19: gh-pages v3.0.0 extended release notes are live upstream (`doi-usgs.github.io/pywatershed`, 2026-07-13 post) and all 3.0.0 release-body links resolve — step 9 done, the 3.0.0 release is From 66fa246dd079953029041423830d446780102b61 Mon Sep 17 00:00:00 2001 From: James McCreight Date: Mon, 24 Aug 2026 17:01:30 -0600 Subject: [PATCH 2/4] Lint Jupyter notebooks; fix three broken cells [tool.ruff] include covered only *.py, so `ruff check .` and CI never linted notebooks. But .github/scripts/ruff_check.sh greps for .ipynb and passes those paths explicitly, which overrides include, so the pre-commit hook did lint them. Notebook problems were invisible until someone edited a notebook and got a wall of errors they had not caused. Notebooks now go in include, with two rules exempted: E501 - they are narrative; wrapping cells to 79 columns hurts readability and churns diffs for no correctness gain. I001 - several notebooks import a module purely for its side effect (hvplot.xarray registers a .hvplot accessor) and must do so after the module it extends. Sorting moves those imports above their dependency, which was verified against 06. For the same line-length reason notebooks are linted but not auto-formatted: [tool.ruff.format] excludes them, and ruff_check.sh passes --force-exclude so the hook honors that despite naming paths explicitly. This surfaced three broken cells that no test covers, all in notebooks CI does not run: - a stray ")" in prms_5.2.1_performance.ipynb - a string opened with " and closed with ' in sagehen-postprocess-maps.ipynb - pl.Path used with no `import pathlib` in plot_4_performance_domains.ipynb Four malformed # noqa directives are also repaired, including a "# noaq" typo. Ruff rejects "# noqa, after xr" as malformed and silently ignores it, so the unused-import fixer would have deleted the very hvplot imports those comments existed to protect -- which would have broken 06_flow_graph_starfit.ipynb, one of the notebooks CI runs. Per review, the explanatory comments now sit above the import and the directives are spelled `# noqa: F401`. The remaining 32 unused imports were audited individually rather than blind-fixed; none are side-effect imports. Every notebook referencing hvplot still imports it. The notebooks were not executed as part of this change. Co-Authored-By: Claude Opus 5 (1M context) --- .github/scripts/ruff_check.sh | 4 +++- doc/whats-new.rst | 16 ++++++++++++++ .../performance/input_performance.ipynb | 1 - .../nhm_pynhm_vs_prms_performance.ipynb | 1 - .../plot_4_performance_domains.ipynb | 2 ++ evaluation/prms/panel_errors.ipynb | 3 --- examples/01_multi-process_models.ipynb | 3 --- examples/02_prms_legacy_models.ipynb | 4 ---- examples/03_compare_pws_prms.ipynb | 4 ---- examples/04_preprocess_atm.ipynb | 5 +---- examples/06_flow_graph_starfit.ipynb | 10 +++++---- examples/07_mmr_to_mf6_chf_dfw.ipynb | 2 -- examples/09_model_output.ipynb | 15 ++++++++----- examples/developer/01_automated_testing.ipynb | 1 - examples/developer/02_process_models.ipynb | 4 ++-- examples/model_loop_custom_output.ipynb | 2 -- examples/sagehen/sagehen-api-pynhm.ipynb | 7 +++--- .../sagehen/sagehen-postprocess-graphs.ipynb | 13 +++++------ .../sagehen/sagehen-postprocess-maps.ipynb | 22 +++++++++---------- pyproject.toml | 20 +++++++++++++++++ 20 files changed, 78 insertions(+), 61 deletions(-) diff --git a/.github/scripts/ruff_check.sh b/.github/scripts/ruff_check.sh index 66d5df3d..41fb5581 100644 --- a/.github/scripts/ruff_check.sh +++ b/.github/scripts/ruff_check.sh @@ -22,7 +22,9 @@ if [ $? -ne 0 ]; then fi echo "Running ruff format on staged files..." -echo "$STAGED_FILES" | xargs ruff format +# --force-exclude so the [tool.ruff.format] exclude is honored even +# though paths are passed explicitly (notebooks are linted, not formatted) +echo "$STAGED_FILES" | xargs ruff format --force-exclude if [ $? -ne 0 ]; then echo "ruff format failed. Please fix errors before committing." exit 1 diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 4b076da2..b52d4d5d 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -27,6 +27,22 @@ Bug fixes Internal changes ~~~~~~~~~~~~~~~~ +- Lint Jupyter notebooks. ``[tool.ruff] include`` covered only ``*.py``, so + ``ruff check .`` and CI never saw notebooks, while the pre-commit hook + passed ``*.ipynb`` paths explicitly (which overrides ``include``) and did. + Notebook problems were therefore invisible until someone edited a notebook + and was met with errors they had not caused. Notebooks are now in + ``include``, with ``E501`` and ``I001`` exempted: they are narrative, and + several import a module purely for its side effect (``hvplot.xarray`` + registers a ``.hvplot`` accessor) and must do so after the module they + extend, which sorting undoes. Notebooks are linted but not auto-formatted, + for the same line-length reason. This surfaced three broken cells that no + test covered: a stray ``)``, a string opened with ``"`` and closed with + ``'``, and a use of ``pl.Path`` with no ``import pathlib``. Also repairs + four malformed ``# noqa`` directives (including a ``# noaq`` typo) that + ruff silently ignored, which meant the unused-import fixer would have + deleted the ``hvplot`` imports they were meant to protect. + (:pull:`XXX`) By `James McCreight `_. - Add ``MAINTENANCE.md``, a ledger of maintenance todos blocked on external events (dependency releases, cross-repo work), each with a mechanically checkable unblock condition; the ``/maintenance`` Claude skill checks them diff --git a/evaluation/performance/input_performance.ipynb b/evaluation/performance/input_performance.ipynb index c4b8c126..6429e1db 100644 --- a/evaluation/performance/input_performance.ipynb +++ b/evaluation/performance/input_performance.ipynb @@ -15,7 +15,6 @@ "metadata": {}, "outputs": [], "source": [ - "import pathlib as pl\n", "import pywatershed\n", "\n", "repo_root = pywatershed.constants.__pywatershed_root__.parent" diff --git a/evaluation/performance/nhm_pynhm_vs_prms_performance.ipynb b/evaluation/performance/nhm_pynhm_vs_prms_performance.ipynb index 583c0df7..4fed00c7 100644 --- a/evaluation/performance/nhm_pynhm_vs_prms_performance.ipynb +++ b/evaluation/performance/nhm_pynhm_vs_prms_performance.ipynb @@ -77,7 +77,6 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas\n", "import pandas as pd\n", "pd.options.plotting.backend = 'holoviews'\n", "results_df = pd.DataFrame(results_post).T\n", diff --git a/evaluation/performance/plot_4_performance_domains.ipynb b/evaluation/performance/plot_4_performance_domains.ipynb index 24e01645..2625c42d 100644 --- a/evaluation/performance/plot_4_performance_domains.ipynb +++ b/evaluation/performance/plot_4_performance_domains.ipynb @@ -19,6 +19,8 @@ "source": [ "%matplotlib inline\n", "\n", + "import pathlib as pl\n", + "\n", "import matplotlib.pyplot as plt\n", "import geopandas as gpd\n", "import contextily as cx" diff --git a/evaluation/prms/panel_errors.ipynb b/evaluation/prms/panel_errors.ipynb index 4affd795..b78e3040 100644 --- a/evaluation/prms/panel_errors.ipynb +++ b/evaluation/prms/panel_errors.ipynb @@ -17,11 +17,8 @@ "metadata": {}, "outputs": [], "source": [ - "import pathlib as pl\n", "import numpy as np\n", - "import param\n", "import pywatershed\n", - "import xarray as xr\n", "\n", "import panel_errors_helpers" ] diff --git a/examples/01_multi-process_models.ipynb b/examples/01_multi-process_models.ipynb index 84be670b..aa61a5d3 100644 --- a/examples/01_multi-process_models.ipynb +++ b/examples/01_multi-process_models.ipynb @@ -28,9 +28,7 @@ "source": [ "from copy import deepcopy\n", "import pathlib as pl\n", - "from platform import processor\n", "from pprint import pprint\n", - "from sys import platform\n", "import yaml\n", "\n", "import pydoc\n", @@ -39,7 +37,6 @@ "import jupyter_black\n", "import numpy as np\n", "import pywatershed as pws\n", - "from pywatershed.utils import gis_files\n", "from pywatershed.utils.path import dict_pl_to_str\n", "\n", "import xarray as xr\n", diff --git a/examples/02_prms_legacy_models.ipynb b/examples/02_prms_legacy_models.ipynb index 28d82f81..e3ec2001 100644 --- a/examples/02_prms_legacy_models.ipynb +++ b/examples/02_prms_legacy_models.ipynb @@ -32,11 +32,7 @@ "outputs": [], "source": [ "import pathlib as pl\n", - "from platform import processor\n", - "from pprint import pprint\n", "from shutil import rmtree\n", - "from sys import platform\n", - "import warnings\n", "\n", "import pydoc\n", "\n", diff --git a/examples/03_compare_pws_prms.ipynb b/examples/03_compare_pws_prms.ipynb index c6183a1d..d66a4cb1 100644 --- a/examples/03_compare_pws_prms.ipynb +++ b/examples/03_compare_pws_prms.ipynb @@ -45,18 +45,14 @@ "outputs": [], "source": [ "import pathlib as pl\n", - "from platform import processor\n", - "from pprint import pprint\n", "from shutil import rmtree\n", "import subprocess\n", - "from sys import platform\n", "import warnings\n", "\n", "import hvplot.pandas # noqa\n", "import hvplot.xarray # noqa\n", "import jupyter_black\n", "import numpy as np\n", - "import pandas as pd\n", "import pywatershed as pws\n", "import xarray as xr\n", "\n", diff --git a/examples/04_preprocess_atm.ipynb b/examples/04_preprocess_atm.ipynb index 3f508948..02789b0f 100644 --- a/examples/04_preprocess_atm.ipynb +++ b/examples/04_preprocess_atm.ipynb @@ -23,13 +23,10 @@ "metadata": {}, "outputs": [], "source": [ - "from copy import deepcopy\n", "import pathlib as pl\n", - "from pprint import pprint\n", "import shutil\n", "\n", "import jupyter_black\n", - "import numpy as np\n", "import pywatershed as pws\n", "import xarray as xr\n", "\n", @@ -103,7 +100,7 @@ " \"tmin\": dom_dir / \"tmin.cbh\",\n", "}\n", "\n", - "cbh_dir = nb_output_dir / f\"cbh\"\n", + "cbh_dir = nb_output_dir / \"cbh\"\n", "cbh_dir.mkdir(exist_ok=True)\n", "\n", "for kk, vv in cbh_files.items():\n", diff --git a/examples/06_flow_graph_starfit.ipynb b/examples/06_flow_graph_starfit.ipynb index acb5f453..b4160f30 100644 --- a/examples/06_flow_graph_starfit.ipynb +++ b/examples/06_flow_graph_starfit.ipynb @@ -36,17 +36,19 @@ "import geopandas as gpd\n", "import jupyter_black\n", "import numpy as np\n", - "import pandas as pd\n", "from tqdm.auto import tqdm\n", "import xarray as xr\n", "\n", - "import hvplot.xarray # noqa, after xr\n", - "import hvplot.pandas # noqa, after pandas\n", + "# hvplot.xarray and hvplot.pandas are imported for their side effect:\n", + "# each registers a .hvplot accessor, on xarray and pandas objects\n", + "# respectively. Neither is referenced by name, so ruff sees them as\n", + "# unused. Import them after xarray and pandas.\n", + "import hvplot.xarray # noqa: F401\n", + "import hvplot.pandas # noqa: F401\n", "\n", "import pyPRMS\n", "import pywatershed as pws\n", "from pywatershed.plot import DomainPlot\n", - "from pywatershed.constants import zero\n", "\n", "ndays_run = 365 * 2\n", "plot_height = 600\n", diff --git a/examples/07_mmr_to_mf6_chf_dfw.ipynb b/examples/07_mmr_to_mf6_chf_dfw.ipynb index beb886d2..6f23fc20 100644 --- a/examples/07_mmr_to_mf6_chf_dfw.ipynb +++ b/examples/07_mmr_to_mf6_chf_dfw.ipynb @@ -86,14 +86,12 @@ "import geopandas as gpd\n", "import jupyter_black\n", "import numpy as np\n", - "import pandas as pd\n", "import pint\n", "import pywatershed as pws\n", "import xarray as xr\n", "\n", "import hvplot.xarray # noqa\n", "\n", - "import pywatershed as pws\n", "from pywatershed.utils.mmr_to_mf6_dfw import MmrToMf6Dfw\n", "\n", "jupyter_black.load()\n", diff --git a/examples/09_model_output.ipynb b/examples/09_model_output.ipynb index c8e635bc..328d9a5d 100644 --- a/examples/09_model_output.ipynb +++ b/examples/09_model_output.ipynb @@ -41,7 +41,6 @@ "source": [ "import inspect\n", "import pathlib as pl\n", - "from pprint import pprint\n", "\n", "import jupyter_black\n", "import numpy as np\n", @@ -682,6 +681,9 @@ "metadata": {}, "outputs": [], "source": [ + "# some excessive hvplot internal warnings to avoid here\n", + "import warnings\n", + "\n", "plot_data = xr.merge(\n", " [\n", " output2.noi_arrays[\"node_outflows\"]\n", @@ -699,14 +701,15 @@ " }\n", ")\n", "\n", - "# some excessive hvplot internal warnings to avoid here\n", - "import warnings\n", - "\n", "with warnings.catch_warnings():\n", " warnings.simplefilter(\"ignore\") # Temporarily suppress all warnings\n", " import pandas as pd\n", - " import hvplot.xarray # noqa, after xr\n", - " import hvplot.pandas # noaq, after pandas\n", + " # imported for their side effect: each registers a .hvplot\n", + " # accessor, on xarray and pandas objects respectively. Neither is\n", + " # referenced by name, so ruff sees them as unused. Import them\n", + " # after xarray and pandas.\n", + " import hvplot.xarray # noqa: F401\n", + " import hvplot.pandas # noqa: F401\n", "\n", " display(\n", " plot_data.drop_vars(\n", diff --git a/examples/developer/01_automated_testing.ipynb b/examples/developer/01_automated_testing.ipynb index 27fc8f9d..f9a8c748 100644 --- a/examples/developer/01_automated_testing.ipynb +++ b/examples/developer/01_automated_testing.ipynb @@ -67,7 +67,6 @@ "outputs": [], "source": [ "import pywatershed\n", - "import os\n", "import shutil\n", "import subprocess\n", "import pathlib as pl\n", diff --git a/examples/developer/02_process_models.ipynb b/examples/developer/02_process_models.ipynb index 330d86e5..2ce21b26 100644 --- a/examples/developer/02_process_models.ipynb +++ b/examples/developer/02_process_models.ipynb @@ -283,7 +283,7 @@ " pywatershed.analysis.ModelGraph(\n", " multi_proc_model, process_colors=palette, hide_variables=False\n", " ).SVG(verbose=True)\n", - "except:\n", + "except Exception:\n", " print(\"Sorry, this fails for no good reason on Mac ARM architecture\")" ] }, @@ -412,7 +412,7 @@ " pywatershed.analysis.ModelGraph(\n", " nhm, process_colors=palette, hide_variables=False\n", " ).SVG(verbose=True)\n", - "except:\n", + "except Exception:\n", " print(\"Sorry, this fails for no good reason on Mac ARM architecture\")" ] }, diff --git a/examples/model_loop_custom_output.ipynb b/examples/model_loop_custom_output.ipynb index 4163e58f..d5b40a69 100644 --- a/examples/model_loop_custom_output.ipynb +++ b/examples/model_loop_custom_output.ipynb @@ -30,14 +30,12 @@ "outputs": [], "source": [ "from itertools import product\n", - "import math\n", "import pathlib as pl\n", "import shutil\n", "import time\n", "\n", "import numpy as np\n", "import pywatershed as pws\n", - "from tqdm import tqdm\n", "import xarray as xr" ] }, diff --git a/examples/sagehen/sagehen-api-pynhm.ipynb b/examples/sagehen/sagehen-api-pynhm.ipynb index 07955464..89c0f503 100644 --- a/examples/sagehen/sagehen-api-pynhm.ipynb +++ b/examples/sagehen/sagehen-api-pynhm.ipynb @@ -36,7 +36,6 @@ "import sys\n", "\n", "import numpy as np\n", - "import netCDF4 as nc\n", "from modflowapi import ModflowApi\n", "import pywatershed\n", "\n", @@ -194,6 +193,8 @@ "# PRMS forcings need converted to netcdf for pynhm\n", "\n", "import pickle\n", + "\n", + "from pywatershed.utils.cbh_utils import cbh_files_to_netcdf\n", "with open(param_file, \"rb\") as input_file:\n", " param_dict = pickle.load(input_file)\n", "\n", @@ -209,8 +210,6 @@ " src_dir / 'tmin.day': tgt_dir / 'tmin.nc',\n", "}\n", "\n", - "from pywatershed.utils.cbh_utils import cbh_files_to_netcdf\n", - "\n", "for src, tgt in cbh_files.items():\n", " var_name = tgt.with_suffix('').name\n", " if tgt.exists():\n", @@ -475,7 +474,7 @@ " mf6.finalize()\n", " prms.finalize()\n", " success = True\n", - "except:\n", + "except Exception:\n", " raise RuntimeError" ] }, diff --git a/examples/sagehen/sagehen-postprocess-graphs.ipynb b/examples/sagehen/sagehen-postprocess-graphs.ipynb index 07aed0b6..8967ecd6 100644 --- a/examples/sagehen/sagehen-postprocess-graphs.ipynb +++ b/examples/sagehen/sagehen-postprocess-graphs.ipynb @@ -41,15 +41,10 @@ "import datetime\n", "import pathlib as pl\n", "import numpy as np\n", - "import matplotlib as mpl\n", - "from matplotlib import gridspec\n", - "import matplotlib.ticker as mticker\n", "import matplotlib.dates as mdates\n", "import matplotlib.pyplot as plt\n", "import flopy\n", "import geopandas as gpd\n", - "import fiona\n", - "import netCDF4 as nc\n", "import pandas as pd\n", "import hydrofunctions as hf\n", "%matplotlib inline" @@ -375,7 +370,7 @@ "ax.set_xlim(plt_times[0], plt_times[-1])\n", "# ax.set_ylim(0, 10)\n", "\n", - "ax.set_xlabel(\"Date\");\n", + "ax.set_xlabel(\"Date\")\n", "ax.set_ylabel(r\"Streamflow (m$^3$/s)\");\n", "\n", "# fpth = os.path.join(\"..\", \"..\", \"doc\", \"figures\", \"sagehen_streamflow_pywatershed.png\")\n", @@ -427,11 +422,13 @@ "metadata": {}, "outputs": [], "source": [ - "param_file = 'sagehen_params.pkl'\n", "import pickle\n", + "\n", + "import pywatershed\n", + "\n", + "param_file = 'sagehen_params.pkl'\n", "with open(param_file, \"rb\") as input_file:\n", " param_dict = pickle.load(input_file)\n", - "import pywatershed\n", "params = pywatershed.PrmsParameters(param_dict)\n", "\n", "hru_area = params.parameters[\"hru_area\"][:] # m2\n", diff --git a/examples/sagehen/sagehen-postprocess-maps.ipynb b/examples/sagehen/sagehen-postprocess-maps.ipynb index a6d7d208..3ce0dc7a 100644 --- a/examples/sagehen/sagehen-postprocess-maps.ipynb +++ b/examples/sagehen/sagehen-postprocess-maps.ipynb @@ -39,14 +39,10 @@ "import pathlib as pl\n", "\n", "import numpy as np\n", - "import matplotlib as mpl\n", - "from matplotlib import gridspec\n", "import matplotlib.ticker as mticker\n", "import matplotlib.pyplot as plt\n", "import flopy\n", - "import geopandas as gpd\n", - "import fiona\n", - "import netCDF4 as nc" + "import geopandas as gpd" ] }, { @@ -56,11 +52,13 @@ "outputs": [], "source": [ "# Set this to your location for the mf6bmipaper repo\n", - "root_dir = pl.Path(\"../../../mf6bmipaper/models/ModflowPynhm/')\n", + "root_dir = pl.Path(\"../../../mf6bmipaper/models/ModflowPynhm/\")\n", "os.chdir(root_dir)\n", "\n", "sys.path.append(os.path.join(\"..\", \"common\"))\n", - "from figspecs import USGSFigure" + "# figspecs lives in the 'common' dir appended to sys.path just above,\n", + "# so this import has to stay here.\n", + "from figspecs import USGSFigure # noqa: E402" ] }, { @@ -311,11 +309,13 @@ "metadata": {}, "outputs": [], "source": [ - "param_file = 'sagehen_params.pkl'\n", "import pickle\n", + "\n", + "import pywatershed\n", + "\n", + "param_file = 'sagehen_params.pkl'\n", "with open(param_file, \"rb\") as input_file:\n", " param_dict = pickle.load(input_file)\n", - "import pywatershed\n", "params = pywatershed.PrmsParameters(param_dict)\n", "\n", "hru_area = params.parameters[\"hru_area\"][:] # m2\n", @@ -454,7 +454,7 @@ "mm.plot_inactive(zorder=70)\n", "mm.plot_grid(lw=0.25, color=\"0.5\", zorder=10)\n", "river.plot(ax=mm.ax, color=\"cyan\", lw=1, zorder=101)\n", - "hru.plot(ax=mm.ax, edgecolor=\"green\", facecolor=\"none\", lw=0.5, zorder=100);\n", + "hru.plot(ax=mm.ax, edgecolor=\"green\", facecolor=\"none\", lw=0.5, zorder=100)\n", "fpth = os.path.join(\"..\", \"..\", \"doc\", \"figures\", \"sagehen_pynhm_all_discretization.png\")\n", "plt.savefig(fpth, dpi=600);" ] @@ -466,7 +466,7 @@ "outputs": [], "source": [ "mm = flopy.plot.PlotMapView(model=gwf)\n", - "hru.plot(ax=mm.ax, edgecolor=\"green\", facecolor=\"none\", lw=0.5, zorder=100);\n", + "hru.plot(ax=mm.ax, edgecolor=\"green\", facecolor=\"none\", lw=0.5, zorder=100)\n", "fpth = os.path.join(\"..\", \"..\", \"doc\", \"figures\", \"sagehen_pynhm_hrus.png\")\n", "plt.savefig(fpth, dpi=600);" ] diff --git a/pyproject.toml b/pyproject.toml index fb0a2daa..21d8f8f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,6 +118,7 @@ include = [ "examples/**/*.py", "test_data/**/*.py", "docs/**/*.py", + "**/*.ipynb", ] [tool.ruff.lint] @@ -126,6 +127,25 @@ ignore = [ # "F401", # unused import ] +[tool.ruff.lint.per-file-ignores] +# Notebooks are narrative, so two rules are relaxed for them: +# E501 - wrapping cells to 79 columns hurts readability and churns +# diffs for no correctness gain. +# I001 - several notebooks import a module purely for its side effect +# (e.g. hvplot.xarray registers a .hvplot accessor) and must do +# so after the module it extends. Sorting moves those imports +# above their dependency. +# Everything else applies, including F401; annotate a deliberate +# side-effect import with `# noqa: F401` and a comment saying why. +"**/*.ipynb" = ["E501", "I001"] + +[tool.ruff.format] +# Notebooks are linted but not auto-formatted. The formatter wraps to +# line-length regardless of the E501 exemption above, which would reflow +# every cell in every notebook. Excluding them keeps `ruff format --check .` +# green without that churn. +exclude = ["**/*.ipynb"] + # this is maintained for some editors # that dont yet support ruff [tool.black] From ee51f68bc34ecbf4504da917db655bea443b3bff Mon Sep 17 00:00:00 2001 From: James McCreight Date: Tue, 25 Aug 2026 21:01:20 -0600 Subject: [PATCH 3/4] Fix lint in the three notebooks the first pass missed A stray ) in prms_5.2.1_performance.ipynb was a syntax error that masked the rest of its cell. With it gone: a semicolon-joined statement, unused imports and assignments, and bare except clauses narrowed to except AssertionError. Co-Authored-By: Claude Opus 5 (1M context) --- doc/whats-new.rst | 9 ++++++--- evaluation/performance/prms_5.2.1_performance.ipynb | 4 ++-- examples/runoff_errors.ipynb | 11 ++++------- examples/snow_errors.ipynb | 8 +++----- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index b52d4d5d..7e9f8d95 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -36,9 +36,12 @@ Internal changes several import a module purely for its side effect (``hvplot.xarray`` registers a ``.hvplot`` accessor) and must do so after the module they extend, which sorting undoes. Notebooks are linted but not auto-formatted, - for the same line-length reason. This surfaced three broken cells that no - test covered: a stray ``)``, a string opened with ``"`` and closed with - ``'``, and a use of ``pl.Path`` with no ``import pathlib``. Also repairs + for the same line-length reason. This surfaced four broken cells that no + test covered: two stray ``)``, a string opened with ``"`` and closed with + ``'``, and a use of ``pl.Path`` with no ``import pathlib``. Three notebooks + needed further cleanup once the syntax errors stopped masking it: unused + imports, unused assignments, a semicolon-joined statement, and bare + ``except`` clauses narrowed to ``except AssertionError``. Also repairs four malformed ``# noqa`` directives (including a ``# noaq`` typo) that ruff silently ignored, which meant the unused-import fixer would have deleted the ``hvplot`` imports they were meant to protect. diff --git a/evaluation/performance/prms_5.2.1_performance.ipynb b/evaluation/performance/prms_5.2.1_performance.ipynb index d566a33a..976e7e41 100644 --- a/evaluation/performance/prms_5.2.1_performance.ipynb +++ b/evaluation/performance/prms_5.2.1_performance.ipynb @@ -40,7 +40,7 @@ " else:\n", " raise FileExistsError(f\"working_dir exists and overwrite=False: {working_dir}\")\n", "\n", - " control_dir = data_dir / 'pynhm/performance_runs/PRMS/control_files')\n", + " control_dir = data_dir / 'pynhm/performance_runs/PRMS/control_files'\n", " n_hru_desc = 'multi'\n", " if domain_dir.name == 'hru_1':\n", " n_hru_desc = 'single'\n", @@ -173,7 +173,7 @@ "metadata": {}, "outputs": [], "source": [ - "import os; os.getcwd()" + "os.getcwd()" ] }, { diff --git a/examples/runoff_errors.ipynb b/examples/runoff_errors.ipynb index 71863130..2f78fc1b 100644 --- a/examples/runoff_errors.ipynb +++ b/examples/runoff_errors.ipynb @@ -29,8 +29,7 @@ "outputs": [], "source": [ "import pathlib as pl\n", - "from pprint import pprint\n", - "from shutil import rmtree, copy2\n", + "from shutil import copy2\n", "\n", "import hvplot.xarray # noqa\n", "from IPython.display import display\n", @@ -99,11 +98,9 @@ "outputs": [], "source": [ "def run_prms(binary: pl.Path, run_dir: pl.Path, skip_if_exists=False):\n", - " import shlex\n", " import subprocess\n", "\n", - " from pywatershed import CsvFile, Soltab\n", - " from pywatershed.parameters import PrmsParameters\n", + " from pywatershed import CsvFile\n", "\n", " if skip_if_exists and run_dir.exists():\n", " print(\n", @@ -127,7 +124,7 @@ " output_dir.mkdir()\n", "\n", " exe_command = f\"time ./{binary.name} nhm.control -MAXDATALNLEN 60000 2>&1 | tee run.log\"\n", - " result = subprocess.run(\n", + " subprocess.run(\n", " exe_command,\n", " shell=True,\n", " # stdout = subprocess.PIPE,\n", @@ -373,7 +370,7 @@ " assert (output_dir / f\"{vv}.nc\").exists()\n", " try:\n", " assert (input_dir_cp / f\"{vv}.nc\").exists()\n", - " except:\n", + " except AssertionError:\n", " print(f\"********** {vv} not in input_dir_cp\")" ] }, diff --git a/examples/snow_errors.ipynb b/examples/snow_errors.ipynb index 642309dc..cf8594e0 100644 --- a/examples/snow_errors.ipynb +++ b/examples/snow_errors.ipynb @@ -29,8 +29,7 @@ "outputs": [], "source": [ "import pathlib as pl\n", - "from pprint import pprint\n", - "from shutil import rmtree, copy2\n", + "from shutil import copy2\n", "\n", "import hvplot.xarray # noqa\n", "import jupyter_black\n", @@ -102,7 +101,6 @@ "outputs": [], "source": [ "def run_prms(binary: pl.Path, run_dir: pl.Path, skip_if_exists=False):\n", - " import shlex\n", " import subprocess\n", "\n", " from pywatershed import CsvFile, Soltab\n", @@ -131,7 +129,7 @@ " output_dir.mkdir()\n", "\n", " exe_command = f\"time ./{binary.name} nhm.control -MAXDATALNLEN 60000 2>&1 | tee run.log\"\n", - " result = subprocess.run(\n", + " subprocess.run(\n", " exe_command,\n", " shell=True,\n", " # stdout = subprocess.PIPE,\n", @@ -347,7 +345,7 @@ " assert (output_dir / f\"{vv}.nc\").exists()\n", " try:\n", " assert (input_dir / f\"{vv}.nc\").exists()\n", - " except:\n", + " except AssertionError:\n", " print(f\"********** {vv} not in input_dir\")" ] }, From 95e19b55b6a191bf8449a10149329ef4a302c25c Mon Sep 17 00:00:00 2001 From: James McCreight Date: Wed, 26 Aug 2026 08:05:24 -0600 Subject: [PATCH 4/4] update whatsnew --- doc/whats-new.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 7e9f8d95..000a6fb4 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -45,12 +45,12 @@ Internal changes four malformed ``# noqa`` directives (including a ``# noaq`` typo) that ruff silently ignored, which meant the unused-import fixer would have deleted the ``hvplot`` imports they were meant to protect. - (:pull:`XXX`) By `James McCreight `_. + (:pull:`412`) By `James McCreight `_. - Add ``MAINTENANCE.md``, a ledger of maintenance todos blocked on external events (dependency releases, cross-repo work), each with a mechanically checkable unblock condition; the ``/maintenance`` Claude skill checks them live and reports what is actionable. - (:pull:`XXX`) By `James McCreight `_. + (:pull:`409`) By `James McCreight `_. - Reduce CI footprint with a skeleton/full split: pushes to any branch (in this repository or on forks) run a skeleton — installs, linting, domainless tests, the docs build, and example notebooks on ubuntu only — while the