Skip to content
Draft
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
43 changes: 40 additions & 3 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,46 @@ jobs:
ruff check . --output-format=github
ruff format --check .

complexity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
# wily needs history to compare against the base; the gate itself
# only needs the working tree.
fetch-depth: 0
- name: Set up Python 3.14
uses: actions/setup-python@v6
with:
python-version: "3.14"
cache: "pip"
- name: Install metrics tooling
# Versions pinned in the [metrics] extra so CI and the local
# pre-commit hook grade identically.
run: pip install -e .[metrics]
- name: Complexity gate
# Ratchet, not an aspiration: these are the tightest thresholds the
# package currently passes. A change that regresses complexity fails
# here with the offending block named. Mirrors the xenon pre-commit
# hook, so a contributor sees the same verdict before pushing.
run: xenon --max-absolute C --max-modules B --max-average A dataretrieval
- name: Complexity trend vs base
# Advisory: reports which files moved and by how much, so a reviewer
# can see direction rather than a pass/fail. Never fails the build --
# the gate above is what blocks.
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
base="${{ github.event.pull_request.base.sha }}"
git fetch --quiet origin "$base" || true
wily build dataretrieval --max-revisions 40 >/dev/null 2>&1 || true
echo '### Complexity trend' >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
wily diff dataretrieval --revision "$base" 2>&1 | tail -40 \
>> "$GITHUB_STEP_SUMMARY" || echo 'no comparable revision' \
>> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"

package-artifact:
runs-on: ubuntu-latest
steps:
Expand All @@ -47,7 +87,6 @@ jobs:
"$venv/bin/python" -m pip install "$GITHUB_WORKSPACE"/wheelhouse/*.whl
cd "$RUNNER_TEMP"
"$venv/bin/python" -I - <<'PY'
import importlib.util
import os
from importlib.resources import files
from pathlib import Path
Expand All @@ -60,8 +99,6 @@ jobs:
checkout = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
installed = Path(dataretrieval.__file__).resolve()
assert not installed.is_relative_to(checkout), (installed, checkout)
assert importlib.util.find_spec("dataretrieval.waterdata.api") is not None
assert importlib.util.find_spec("dataretrieval.ogc.engine") is not None
assert files("dataretrieval").joinpath("py.typed").is_file()
assert waterdata.get_daily
assert ngwmn.get_sites
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,6 @@ ENV/

# macOS
*.DS_Store

# wily metrics cache (rebuildable: `wily build dataretrieval`)
.wily/
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ repos:
pass_filenames: false
additional_dependencies: [httpx, anyio]

# Complexity ratchet. Thresholds are the tightest the package currently
# passes, so this holds the line rather than demanding a refactor: no block
# above rank C (cyclomatic complexity 20), no module below B, average at A.
# Tightening a letter is a deliberate commit, and the failure names the
# offending block, so the fix is local. Applied to the package only --
# tests legitimately contain long, branchy fixtures.
- repo: https://github.com/rubik/xenon
rev: v0.9.3
hooks:
- id: xenon
# The package path is in args, not passed by pre-commit: --max-average
# and --max-modules are whole-package judgements, and computing them
# over only the changed files would let the average drift upward one
# commit at a time. ``files`` decides whether to run, not what to scan.
args:
- "--max-absolute"
- "C"
- "--max-modules"
- "B"
- "--max-average"
- "A"
- "dataretrieval"
files: ^dataretrieval/.*\.py$
pass_filenames: false

# Strip cell outputs + execution_count from notebooks on commit so the
# diff is the source, not the rendered run. Demos still execute fine
# locally; clean commits keep PRs reviewable and avoid quota/timestamp
Expand Down
22 changes: 22 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,30 @@ ruff format --check .
mypy
coverage run -m pytest tests/
coverage report -m
xenon --max-absolute C --max-modules B --max-average A dataretrieval
```

The last one is a complexity ratchet: those thresholds are the tightest the
package passes today, so it fails only when a change makes complexity worse. It
names the offending block, so the fix is local -- usually extracting a branch
rather than restructuring. Install it with `pip install -e .[metrics]`; the
`xenon` pre-commit hook runs the identical check, so a clean pre-commit run means
CI agrees.

To see the *trend* rather than a pass/fail, that extra also installs
[`wily`](https://github.com/tonybaloney/wily), which indexes metrics across git
history:

```bash
wily build dataretrieval --max-revisions 50 # index recent commits (slow, once)
wily report dataretrieval # how the package moved over time
wily diff dataretrieval --revision main # what your branch changed
wily rank dataretrieval maintainability.mi # worst-maintained files today
```

`wily` is advisory and is never a merge gate -- rising complexity in a file that
gained a genuinely complex feature is information, not a failure.

For documentation changes, install `.[doc,nldi]` and run `make html` from
`docs/`. The broader `make docs` target also runs doctests and network-dependent
link checking.
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ dataretrieval = ["py.typed"]
type-check = [
"mypy",
]
# Complexity gates and history. ``xenon`` fails a build when complexity
# regresses; ``wily`` tracks the trend across commits so a review can say
# whether a change moved the codebase, not just whether it passed. Kept out of
# ``test`` so the test job stays lean -- the metrics job installs this instead.
metrics = [
"xenon==0.9.3",
"wily==1.25.0",
]
test = [
"pytest > 5.0.0",
"pytest-cov[all]",
Expand Down