Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
560f753
Initial code
Aug 14, 2025
e250f00
Create .gitignore
knoxsp Aug 14, 2025
ca681ce
test: add regression tests for the model_runner lazy-import fix, wire…
Mearman Aug 4, 2026
97fdd57
test: declare pytest/pytest-cov as a dev extra, add explicit pytest c…
Mearman Aug 4, 2026
6fb5b56
ci: report coverage via GitHub's native Code Quality feature
Mearman Aug 4, 2026
4311438
ci: don't fail the build on the coverage upload until Code Quality is…
Mearman Aug 4, 2026
f7c321e
ci: write a native coverage summary regardless of Code Quality's admi…
Mearman Aug 4, 2026
c6f2e05
fix: annotate model_creation.create_parameters, add mypy config
Mearman Aug 4, 2026
e2e24bc
ci: add a mypy typecheck job
Mearman Aug 4, 2026
1e9cc4e
ci: trigger on pull requests regardless of target branch
Mearman Aug 5, 2026
3928bff
ci: rename workflow to CI
Mearman Aug 5, 2026
bde100d
fix: make model_runner import lazy and declare its pandas/pywr deps
Mearman Aug 4, 2026
eb2bea9
ci: install the run extra now that it's declared, exercise the real fix
Mearman Aug 4, 2026
16cd78c
chore: add Dependabot version updates with a 7-day cooldown and group…
Mearman Aug 6, 2026
07472a3
chore: ratchet test coverage at today's baseline
Mearman Aug 6, 2026
3eeeda9
chore: add gitleaks secret scanning, matching cluster-api-v2's setup
Mearman Aug 6, 2026
fc4c0b9
fix: make load_pywr_model_from_file's solver parameter actually take …
Mearman Aug 6, 2026
65b8988
chore: add vulture dead-code scanning to CI
Mearman Aug 6, 2026
a40d009
chore: add a pre-push gitleaks hook via pre-commit
Mearman Aug 6, 2026
45bbd09
chore: add bandit and pip-audit to CI
Mearman Aug 6, 2026
a90d20b
test: cover synthetic model construction, summary and save
Mearman Aug 6, 2026
d587c28
test: cover terminal chart rendering and line drawing
Mearman Aug 6, 2026
36669cd
test: solve real models through PywrFileRunner and the size sweep
Mearman Aug 6, 2026
52843fe
test: drive both CLI commands through the Click entrypoint
Mearman Aug 6, 2026
c5f21da
test: cover sweep cleanup and summary-writing failure paths
Mearman Aug 6, 2026
873d2f2
test: cover the __main__ guard and a doubly-failing sweep model
Mearman Aug 6, 2026
788cc6a
chore: raise coverage ratchet to 97% after the model_runner test pass
Mearman Aug 6, 2026
b8cea42
chore: add a ruff-check --fix pre-commit hook
Mearman Aug 10, 2026
bf969a1
ci: enforce ruff check in CI
Mearman Aug 10, 2026
9680561
chore: pin ruff's lint selection explicitly
Mearman Aug 10, 2026
90146fa
chore: fix-and-warn instead of fix-and-block in the local ruff hook
Mearman Aug 10, 2026
dad7d9b
style: apply ruff's safe autofixes
Mearman Aug 10, 2026
ba50d9d
style: apply ruff's remaining safe autofix to cli.py
Mearman Aug 10, 2026
1ba4da6
chore: widen ruff to its real default ruleset and pin ruff's exact ve…
Mearman Aug 10, 2026
b0ae995
fix: drop the stale tempfile entry from vulture_whitelist.py
Mearman Aug 10, 2026
aada7b0
fix: chain click.Abort() to the original exception in CLI error handlers
Mearman Aug 10, 2026
bbc50f8
fix: log per-model sweep failures through the module logger instead o…
Mearman Aug 10, 2026
fa92508
fix: drop unused results_df binding from the incremental-sizes CLI co…
Mearman Aug 10, 2026
af485dc
style: modernise model_creation.py type hints to builtin generics
Mearman Aug 10, 2026
5f785cb
style: simplify redundant list slicing and nested conditional in mode…
Mearman Aug 10, 2026
b4b047e
fix: log PYWR model run failures through the module's own logger
Mearman Aug 10, 2026
560cc0c
chore: enforce ruff findings locally instead of just warning
Mearman Aug 10, 2026
0906b6a
ci: extract CI steps into composite actions and fix self-hosted runne…
Mearman Sep 17, 2026
1238a96
chore: untrack compiled __pycache__ artifacts
Mearman Sep 17, 2026
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
39 changes: 39 additions & 0 deletions .github/actions/gitleaks/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Gitleaks scan
description: Downloads a checksum-verified gitleaks release and scans the commits introduced by the current push or pull request for secrets. Extracted so this repo's own CI and monorepo's synced copy of this repo can call the identical steps locally -- see the parent engagement README's "Sharing CI with the monorepo" pattern, established for infrastructure and applied here.

inputs:
working-directory:
description: Directory whose git history and .gitleaks.toml config to scan
required: false
default: "."

runs:
using: composite
steps:
- name: Install gitleaks
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
set -euo pipefail
VER=8.21.2
curl -sSfL --max-time 60 -o gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${VER}/gitleaks_${VER}_linux_x64.tar.gz"
echo "5bc41815076e6ed6ef8fbecc9d9b75bcae31f39029ceb55da08086315316e3ba gitleaks.tar.gz" | sha256sum -c -
tar -xzf gitleaks.tar.gz gitleaks
rm gitleaks.tar.gz
./gitleaks version

- name: Scan new commits for secrets
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "pull_request" ]; then
RANGE="${{ github.event.pull_request.base.sha }}..${{ github.sha }}"
elif [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
RANGE="-1 ${{ github.sha }}" # first push of a branch: scan tip commit
else
RANGE="${{ github.event.before }}..${{ github.sha }}"
fi
echo "Scanning commit range: $RANGE"
./gitleaks detect --source . --log-opts="$RANGE" \
--config .gitleaks.toml --redact --verbose
29 changes: 29 additions & 0 deletions .github/actions/test-minimal-install/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test minimal install
description: Regression guard for the model_runner lazy-import bug -- a plain `pip install .`, with neither pandas nor pywr present, must still let create-synthetic-model run. Extracted so this repo's own CI and monorepo's synced copy of this repo can call the identical steps locally -- see the parent engagement README's "Sharing CI with the monorepo" pattern, established for infrastructure and applied here.

inputs:
working-directory:
description: Directory containing the pywr_utils package to test
required: false
default: "."

runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- shell: bash
working-directory: ${{ inputs.working-directory }}
run: pip install .
- shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
pywr-utils create-synthetic-model --zones 2 --transfers 1 --output /tmp/synthetic.json --show-summary
test -f /tmp/synthetic.json
- name: confirm pandas and pywr are genuinely absent
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
! python -c "import pandas" 2>/dev/null
! python -c "import pywr" 2>/dev/null
41 changes: 41 additions & 0 deletions .github/actions/test/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Test
description: Installs the package with its run/dev extras, runs the pytest suite, and uploads a coverage report. Extracted so this repo's own CI and monorepo's synced copy of this repo can call the identical steps locally -- see the parent engagement README's "Sharing CI with the monorepo" pattern, established for infrastructure and applied here.

inputs:
working-directory:
description: Directory containing the pywr_utils package to test
required: false
default: "."

runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- shell: bash
working-directory: ${{ inputs.working-directory }}
# pywr's lpsolve extension needs headers this runner doesn't have; glpk (also needed) is prebaked into the ci-runners image.
env:
PYWR_BUILD_LPSOLVE: "false"
run: pip install -e ".[run,dev]"
- shell: bash
working-directory: ${{ inputs.working-directory }}
run: pytest -v
- name: write coverage summary
# Fully native, no admin-gated feature or third-party action needed: coverage.py's own markdown formatter piped into GitHub Actions' own step-summary mechanism. Shows up on every run's summary page (and one click away from the PR checks list) regardless of whether Code Quality is ever enabled.
if: always()
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
echo '## Coverage' >> "$GITHUB_STEP_SUMMARY"
python -m coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY"
- name: upload coverage report
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: actions/upload-code-coverage@v1
with:
file: ${{ inputs.working-directory }}/coverage.xml
language: Python
label: code-coverage/pytest
# TEMPORARY: Code Quality isn't enabled for this repo yet (Settings > Security > Code quality) — that's an org/repo admin action, and neither CI nor a push-only collaborator can do it. Without this, the upload 403s and fails the whole job. Remove fail-on-error once an admin enables Code Quality and this step succeeds for real.
fail-on-error: false
40 changes: 40 additions & 0 deletions .github/actions/typecheck/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Typecheck and lint
description: Runs mypy, ruff, vulture, bandit, and pip-audit against the Python package. Extracted so this repo's own CI and monorepo's synced copy of this repo can call the identical steps locally -- see the parent engagement README's "Sharing CI with the monorepo" pattern, established for infrastructure and applied here.

inputs:
working-directory:
description: Directory containing the pywr_utils package to check
required: false
default: "."

runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- shell: bash
working-directory: ${{ inputs.working-directory }}
run: pip install -e ".[dev]"
- shell: bash
working-directory: ${{ inputs.working-directory }}
run: mypy pywr_utils/
- name: lint (ruff)
shell: bash
working-directory: ${{ inputs.working-directory }}
run: ruff check .
- name: dead-code scan (vulture)
shell: bash
working-directory: ${{ inputs.working-directory }}
run: vulture pywr_utils vulture_whitelist.py --min-confidence 80
- name: security scan (bandit)
shell: bash
working-directory: ${{ inputs.working-directory }}
run: bandit -r pywr_utils
- name: dependency vulnerability scan (pip-audit)
shell: bash
working-directory: ${{ inputs.working-directory }}
# pip itself is not a declared dependency, just whatever setup-python provisions; keep it current so a stale bundled pip doesn't produce a false CVE finding here.
run: |
pip install -U pip
pip-audit --skip-editable
23 changes: 23 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
python-dependencies:
patterns:
- "*"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
github-actions:
patterns:
- "*"
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [main]
pull_request: {}
workflow_dispatch: {}

permissions:
contents: read
code-quality: write

jobs:
typecheck:
runs-on: gha-ci-runners
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/typecheck

test:
runs-on: gha-ci-runners
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: ./.github/actions/test

test-minimal-install:
# Regression guard for the model_runner lazy-import bug: a plain `pip install .`, with neither pandas nor pywr present, must still let create-synthetic-model run — only the `run` extra needs either.
runs-on: gha-ci-runners
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/test-minimal-install
22 changes: 22 additions & 0 deletions .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: gitleaks

# Scan only the NEW commits of each push / PR for secrets, matching the pattern already established on cluster-api-v2. No known historical-secret backlog exists in this repo, but scanning new commits only (rather than full history on every run) keeps this consistent with the rest of the org and cheap to run.
on:
pull_request:
push:
branches: [main]
workflow_dispatch: {}

permissions:
contents: read

jobs:
gitleaks:
# Self-hosted fleet: the org's billing block stops GitHub-hosted jobs, and the runner container has no sudo -- the binary runs from the workspace by path.
runs-on: gha-ci-runners
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/gitleaks
Loading
Loading