From 1a732d2fd8dde3cb0cbaec3eedb92dc0bcb0d2e5 Mon Sep 17 00:00:00 2001 From: pesap Date: Sun, 15 Mar 2026 19:52:47 -0600 Subject: [PATCH 1/4] fix(ci): harden all workflows per zizmor audit - Pin all action references to commit SHAs (31 unpinned-uses resolved) - Add explicit least-privilege permissions at workflow and job level - Set persist-credentials: false on every checkout step - Scope release.yaml permissions per job instead of workflow-level - Delete legacy publish_pypi.yaml (redundant with release.yaml trusted publishing) - Suppress pull_request_target warning in labeler.yaml (correct usage) Reduces zizmor findings from 75 (36 high) to 1 acceptable warning (codecov secret outside dedicated environment). --- .github/workflows/CI.yaml | 41 +- .github/workflows/commit.yaml | 11 +- .github/workflows/docs.yaml | 11 +- .github/workflows/labeler.yaml | 8 +- .github/workflows/publish_pypi.yaml | 27 -- .github/workflows/release.yaml | 41 +- docs/plans/2026-03-15-zizmor-ci-hardening.md | 476 +++++++++++++++++++ 7 files changed, 545 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/publish_pypi.yaml create mode 100644 docs/plans/2026-03-15-zizmor-ci-hardening.md diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 2d2c544..5c4b19d 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -10,22 +10,27 @@ concurrency: env: UV_VERSION: "0.9.4" +permissions: {} + jobs: pre-commit: runs-on: ubuntu-latest name: Pre-commit hooks (lint/format/spell/type, all files) + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 with: enable-cache: true version: ${{ env.UV_VERSION }} @@ -41,6 +46,8 @@ jobs: name: Tests ${{ matrix.os }} / py${{ matrix.python }} needs: pre-commit runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: fail-fast: false matrix: @@ -50,10 +57,12 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 with: enable-cache: true version: ${{ env.UV_VERSION }} @@ -69,7 +78,7 @@ jobs: uv run pytest --cov --cov-report=xml - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -82,10 +91,12 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 with: enable-cache: true version: ${{ env.UV_VERSION }} @@ -100,7 +111,7 @@ jobs: run: uv run pytest benchmarks/ -k "not xlarge_300k" --benchmark-only --benchmark-json=benchmark-results.json --no-cov - name: Download previous benchmark data - uses: actions/cache@v5 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: ./cache key: ${{ runner.os }}-benchmark-${{ github.head_ref || github.ref_name }}-${{ github.run_id }} @@ -112,7 +123,7 @@ jobs: run: mkdir -p cache && test -f cache/benchmark-data.json || echo '[]' > cache/benchmark-data.json - name: Publish benchmark results - uses: benchmark-action/github-action-benchmark@v1 + uses: benchmark-action/github-action-benchmark@a7bc2366eda11037936ea57d811a43b3418d3073 # v1 with: tool: pytest output-file-path: benchmark-results.json @@ -127,17 +138,21 @@ jobs: name: Package smoke test needs: pytest runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 with: version: ${{ env.UV_VERSION }} enable-cache: true diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 941a51f..73fc81f 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -4,18 +4,23 @@ on: pull_request: types: [opened, reopened, synchronize] +permissions: {} + jobs: lint-commit-messages: name: lint commit message runs-on: [ubuntu-latest] + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: + persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 - name: Commitizen check run: | uvx --from commitizen cz check --rev-range HEAD^! @@ -26,6 +31,6 @@ jobs: permissions: pull-requests: read steps: - - uses: amannn/action-semantic-pull-request@v6.1.1 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 601da5a..cde29f9 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -10,13 +10,18 @@ on: workflow_dispatch: +permissions: + contents: write + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 with: version: "latest" @@ -30,7 +35,7 @@ jobs: run: uv run sphinx-build docs/source/ docs/_build/ - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@e9c66a37f080288a11235e32cbe2dc5fb3a679cc # v4 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} with: publish_branch: gh-pages diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml index 46e6446..a867d60 100644 --- a/.github/workflows/labeler.yaml +++ b/.github/workflows/labeler.yaml @@ -1,7 +1,7 @@ name: labeler on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] types: [opened, reopened, synchronize] jobs: @@ -13,8 +13,10 @@ jobs: issues: write runs-on: [ubuntu-latest] steps: - - uses: actions/checkout@v6 - - uses: actions/labeler@v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} configuration-path: .github/labeler.yaml diff --git a/.github/workflows/publish_pypi.yaml b/.github/workflows/publish_pypi.yaml deleted file mode 100644 index df2826b..0000000 --- a/.github/workflows/publish_pypi.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: Upload to PyPi - -on: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: 3.11 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install build - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} - TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - run: | - python -m build - twine upload dist/* diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 70bd63c..e05c39d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,16 +8,16 @@ concurrency: group: release-please cancel-in-progress: true -permissions: - contents: write - pull-requests: write - id-token: write +permissions: {} env: UV_VERSION: "0.9.4" jobs: release-please: + permissions: + contents: write + pull-requests: write outputs: release_created: ${{ steps.release.outputs.release_created }} release_tag: ${{ steps.release.outputs.tag_name }} @@ -25,7 +25,7 @@ jobs: steps: - name: Run release-please id: release - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4 with: token: ${{ secrets.GITHUB_TOKEN }} config-file: .release-please-config.json @@ -34,26 +34,24 @@ jobs: build: name: Build + permissions: + contents: read needs: release-please if: needs.release-please.outputs.release_created runs-on: ubuntu-latest steps: - name: Checkout release commit - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ needs.release-please.outputs.release_tag }} fetch-depth: 0 + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version-file: "pyproject.toml" + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: ${{ env.UV_VERSION }} - enable-cache: true + uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 - name: Install dependencies run: uv sync --all-groups @@ -62,12 +60,14 @@ jobs: run: uv build - name: Store the distribution packages - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: python-package-distributions path: dist/ publish-testpypi: + permissions: + id-token: write runs-on: ubuntu-latest needs: build environment: @@ -75,17 +75,16 @@ jobs: url: https://test.pypi.org/p/plexosdb steps: - name: Download all the dists - uses: actions/download-artifact@v8 - with: - name: python-package-distributions - path: dist/ + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - name: Publish package to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 with: repository-url: https://test.pypi.org/legacy/ publish-pypi: + permissions: + id-token: write needs: - build - publish-testpypi @@ -95,9 +94,9 @@ jobs: url: https://pypi.org/p/plexosdb steps: - name: Download all the dists - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 diff --git a/docs/plans/2026-03-15-zizmor-ci-hardening.md b/docs/plans/2026-03-15-zizmor-ci-hardening.md new file mode 100644 index 0000000..eab9846 --- /dev/null +++ b/docs/plans/2026-03-15-zizmor-ci-hardening.md @@ -0,0 +1,476 @@ +# CI Workflow Security Hardening (zizmor findings) + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Resolve all 75 zizmor findings (36 high, 11 medium, 9 low, 1 info) across 6 workflow files. + +**Architecture:** Apply defense-in-depth to GitHub Actions: pin all actions to commit SHAs with version comments, set explicit least-privilege permissions at workflow and job level, disable credential persistence on every checkout, and migrate publish_pypi.yaml to trusted publishing. + +**Tech Stack:** GitHub Actions, zizmor 1.23.1 + +--- + +## Findings Summary + +| Audit | Severity | Count | Affected Workflows | +|-------|----------|-------|--------------------| +| `unpinned-uses` | error (high) | 31 | all 6 | +| `excessive-permissions` | error+warning | 11 | CI, commit, docs, publish_pypi, release | +| `artipacked` | help (low) | 9 | all 6 | +| `dangerous-triggers` | error (medium) | 1 | labeler | +| `secrets-outside-env` | warning (high) | 3 | CI, publish_pypi | +| `use-trusted-publishing` | info | 1 | publish_pypi | + +## SHA Pin Reference + +Use these exact SHAs (resolved 2026-03-15): + +| Action | Version | SHA | +|--------|---------|-----| +| `actions/checkout` | v6 | `de0fac2e4500dabe0009e67214ff5f5447ce83dd` | +| `actions/setup-python` | v6 | `a309ff8b426b58ec0e2a45f0f869d46889d02405` | +| `astral-sh/setup-uv` | v7 | `b75dde52aef63a238519e7aecbbe79a4a52e4315` | +| `codecov/codecov-action` | v5 | `671740ac38dd9b0130fbe1cec585b89eea48d3de` | +| `actions/cache` | v5 | `cdf6c1fa76f9f475f3d7449005a359c84ca0f306` | +| `benchmark-action/github-action-benchmark` | v1.21.0 | `a7bc2366eda11037936ea57d811a43b3418d3073` | +| `peaceiris/actions-gh-pages` | v4 | `e9c66a37f080288a11235e32cbe2dc5fb3a679cc` | +| `amannn/action-semantic-pull-request` | v6.1.1 | `48f256284bd46cdaab1048c3721360e808335d50` | +| `googleapis/release-please-action` | v4 | `c3fc4de07084f75a2b61a5b933069bda6edf3d5c` | +| `actions/upload-artifact` | v7 | `bbbca2ddaa5d8feaa63e36b76fdaad77386f024f` | +| `actions/download-artifact` | v8 | `3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c` | +| `pypa/gh-action-pypi-publish` | v1.13.0 | `106e0b0b7c337fa67ed433972f777c6357f78598` | +| `actions/labeler` | v6.0.1 | `634933edcd8ababfe52f92936142cc22ac488b1b` | + +--- + +## Task 1: CI.yaml — permissions, pins, and persist-credentials + +**Files:** +- Modify: `.github/workflows/CI.yaml` + +**Step 1: Add top-level permissions block** + +Add after `env:` block (line 11): + +```yaml +permissions: {} +``` + +This sets default to no permissions. The `benchmarks` job already has its own block. + +**Step 2: Add job-level permissions to `pre-commit` and `package` jobs** + +Both need only `contents: read`: + +```yaml + pre-commit: + permissions: + contents: read + ... + + package: + permissions: + contents: read + ... +``` + +**Step 3: Add job-level permissions to `pytest` job** + +Needs `contents: read` (checkout) only. Codecov uses its own token so no extra permission needed: + +```yaml + pytest: + permissions: + contents: read + ... +``` + +**Step 4: Pin all actions to SHAs** + +Replace every `uses:` line with SHA-pinned version (keep version as comment): + +```yaml +# In pre-commit job: +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 + +# In pytest job: +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 + +# In benchmarks job: +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 +- uses: benchmark-action/github-action-benchmark@a7bc2366eda11037936ea57d811a43b3418d3073 # v1 + +# In package job: +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +``` + +**Step 5: Add `persist-credentials: false` to every checkout** + +All 4 checkout steps need: + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false +``` + +For the `pre-commit` checkout that already has `with: fetch-depth: 0`, just add `persist-credentials: false` alongside it. + +**Step 6: Run zizmor on CI.yaml to verify** + +Run: `zizmor .github/workflows/CI.yaml` +Expected: Only `secrets-outside-env` for `CODECOV_TOKEN` remains (acceptable, see Decision Notes below). + +**Step 7: Commit** + +``` +git add .github/workflows/CI.yaml +git commit -m "fix(ci): harden CI.yaml — pin actions, scope permissions, disable credential persistence" +``` + +--- + +## Task 2: commit.yaml — permissions, pins, and persist-credentials + +**Files:** +- Modify: `.github/workflows/commit.yaml` + +**Step 1: Add top-level permissions block** + +```yaml +permissions: {} +``` + +**Step 2: Add permissions to `lint-commit-messages` job** + +Only needs to read the repo: + +```yaml + lint-commit-messages: + permissions: + contents: read + ... +``` + +The `lint-pr-title` job already has `permissions: pull-requests: read`. + +**Step 3: Pin all actions to SHAs** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 +``` + +**Step 4: Add `persist-credentials: false` to checkout** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false +``` + +**Step 5: Run zizmor on commit.yaml to verify** + +Run: `zizmor .github/workflows/commit.yaml` +Expected: 0 findings. + +**Step 6: Commit** + +``` +git add .github/workflows/commit.yaml +git commit -m "fix(ci): harden commit.yaml — pin actions, scope permissions, disable credential persistence" +``` + +--- + +## Task 3: docs.yaml — permissions, pins, and persist-credentials + +**Files:** +- Modify: `.github/workflows/docs.yaml` + +**Step 1: Add top-level permissions block** + +The `build` job needs `contents: write` only for the gh-pages deploy step (on push to main). However, the deploy step uses `github_token` explicitly, so we can use a more targeted approach: + +```yaml +permissions: + contents: write +``` + +Note: `peaceiris/actions-gh-pages` needs `contents: write` to push to the gh-pages branch. This is a workflow-level permission because there's only one job. + +**Step 2: Pin all actions to SHAs** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +- uses: peaceiris/actions-gh-pages@e9c66a37f080288a11235e32cbe2dc5fb3a679cc # v4 +``` + +**Step 3: Add `persist-credentials: false` to checkout** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false +``` + +**Step 4: Run zizmor on docs.yaml to verify** + +Run: `zizmor .github/workflows/docs.yaml` +Expected: 0 findings. + +**Step 5: Commit** + +``` +git add .github/workflows/docs.yaml +git commit -m "fix(ci): harden docs.yaml — pin actions, scope permissions, disable credential persistence" +``` + +--- + +## Task 4: labeler.yaml — dangerous trigger, pins, and persist-credentials + +**Files:** +- Modify: `.github/workflows/labeler.yaml` + +**Step 1: Pin all actions to SHAs** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 +``` + +**Step 2: Add `persist-credentials: false` to checkout** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false +``` + +**Step 3: Address `dangerous-triggers` (pull_request_target)** + +`pull_request_target` runs in the context of the base branch, which is inherently risky. However, the labeler workflow: +- Does NOT checkout PR code for execution (it only reads labels/file paths) +- Has minimal permissions (contents: read, pull-requests: write, issues: write) +- Uses the trusted `actions/labeler` action + +This is the standard, documented use case for `pull_request_target`. The risk is acceptable. To suppress the zizmor warning, add an inline annotation: + +```yaml +on: + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: [opened, reopened, synchronize] +``` + +**Step 4: Run zizmor on labeler.yaml to verify** + +Run: `zizmor .github/workflows/labeler.yaml` +Expected: 0 findings (dangerous-triggers suppressed by inline annotation). + +**Step 5: Commit** + +``` +git add .github/workflows/labeler.yaml +git commit -m "fix(ci): harden labeler.yaml — pin actions, disable credential persistence, annotate pull_request_target" +``` + +--- + +## Task 5: publish_pypi.yaml — trusted publishing, permissions, pins + +**Files:** +- Modify: `.github/workflows/publish_pypi.yaml` + +This workflow is the most outdated and has the most issues. It uses `twine` with username/password secrets instead of trusted publishing. + +**Decision point:** The `release.yaml` workflow already handles publishing via trusted publishing (`pypa/gh-action-pypi-publish@release/v1` with OIDC). This `publish_pypi.yaml` appears to be a legacy duplicate. Check with the team whether it should be deleted entirely or migrated. + +**Step 1: If keeping the workflow, migrate to trusted publishing** + +Replace the entire file: + +```yaml +name: Upload to PyPi + +on: + release: + types: [published] + +permissions: {} + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + environment: + name: pypi + url: https://pypi.org/p/plexosdb + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: 3.11 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build + - name: Build package + run: python -m build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 +``` + +This eliminates: +- `TWINE_USERNAME` / `TWINE_PASSWORD` secrets (no more `secrets-outside-env`) +- `twine` dependency +- Token-based auth in favor of OIDC trusted publishing + +**Step 1 (alternative): If deleting the workflow** + +Since `release.yaml` already publishes to both TestPyPI and PyPI with trusted publishing, this workflow is redundant. Delete it: + +``` +git rm .github/workflows/publish_pypi.yaml +``` + +**Step 2: Run zizmor to verify** + +Run: `zizmor .github/workflows/publish_pypi.yaml` (if kept) +Expected: 0 findings. + +**Step 3: Commit** + +``` +git add .github/workflows/publish_pypi.yaml +git commit -m "fix(ci): harden publish_pypi.yaml — migrate to trusted publishing, pin actions, scope permissions" +``` + +--- + +## Task 6: release.yaml — permissions, pins, and persist-credentials + +**Files:** +- Modify: `.github/workflows/release.yaml` + +**Step 1: Move permissions from workflow-level to job-level** + +The current workflow has `contents: write`, `pull-requests: write`, `id-token: write` at the top level. Each job should declare only what it needs: + +```yaml +# Remove the top-level permissions block and replace with: +permissions: {} + +# release-please job needs: + release-please: + permissions: + contents: write + pull-requests: write + ... + +# build job needs: + build: + permissions: + contents: read + ... + +# publish-testpypi job needs: + publish-testpypi: + permissions: + id-token: write + ... + +# publish-pypi job needs: + publish-pypi: + permissions: + id-token: write + ... +``` + +**Step 2: Pin all actions to SHAs** + +```yaml +# release-please job: +- uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4 + +# build job: +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 +- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 +- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 +- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + +# publish-testpypi job: +- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 +- uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 + +# publish-pypi job: +- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 +- uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 +``` + +**Step 3: Add `persist-credentials: false` to checkout** + +```yaml +- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ needs.release-please.outputs.release_tag }} + fetch-depth: 0 + persist-credentials: false +``` + +**Step 4: Run zizmor on release.yaml to verify** + +Run: `zizmor .github/workflows/release.yaml` +Expected: 0 findings. + +**Step 5: Commit** + +``` +git add .github/workflows/release.yaml +git commit -m "fix(ci): harden release.yaml — scope permissions per job, pin actions, disable credential persistence" +``` + +--- + +## Decision Notes + +### `secrets-outside-env` for CODECOV_TOKEN (CI.yaml) + +zizmor flags `secrets.CODECOV_TOKEN` because it's not accessed within a GitHub environment. Creating a dedicated environment just for codecov adds friction with no real security gain (the token is already scoped to codecov uploads). This finding is acceptable to leave as-is or suppress with `# zizmor: ignore[secrets-outside-env]`. + +### `dangerous-triggers` for labeler.yaml + +`pull_request_target` is the correct trigger for the labeler use case. The workflow does not execute untrusted PR code. Suppressed with inline annotation. + +### publish_pypi.yaml vs release.yaml + +These two workflows appear to overlap. `release.yaml` is the modern one (trusted publishing, TestPyPI + PyPI). `publish_pypi.yaml` is legacy (twine + secrets). Recommend deleting `publish_pypi.yaml` if `release.yaml` is the active publish path. + +--- + +## Verification + +After all tasks, run full scan: + +```bash +zizmor .github/workflows/ +``` + +Expected: 0 errors, 0 warnings. Only acceptable suppressions remain. From b5e2780b60d5eaec4ae66302da825f5befeb91df Mon Sep 17 00:00:00 2001 From: pesap Date: Sun, 15 Mar 2026 19:56:16 -0600 Subject: [PATCH 2/4] fix: remove docs/plans from repo and add to gitignore --- .gitignore | 3 + docs/plans/2026-03-15-zizmor-ci-hardening.md | 476 ------------------- 2 files changed, 3 insertions(+), 476 deletions(-) delete mode 100644 docs/plans/2026-03-15-zizmor-ci-hardening.md diff --git a/.gitignore b/.gitignore index ff77ed1..d604692 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,9 @@ instance/ # Sphinx documentation docs/_build/ +# Implementation plans (local only) +docs/plans/ + # PyBuilder .pybuilder/ target/ diff --git a/docs/plans/2026-03-15-zizmor-ci-hardening.md b/docs/plans/2026-03-15-zizmor-ci-hardening.md deleted file mode 100644 index eab9846..0000000 --- a/docs/plans/2026-03-15-zizmor-ci-hardening.md +++ /dev/null @@ -1,476 +0,0 @@ -# CI Workflow Security Hardening (zizmor findings) - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Resolve all 75 zizmor findings (36 high, 11 medium, 9 low, 1 info) across 6 workflow files. - -**Architecture:** Apply defense-in-depth to GitHub Actions: pin all actions to commit SHAs with version comments, set explicit least-privilege permissions at workflow and job level, disable credential persistence on every checkout, and migrate publish_pypi.yaml to trusted publishing. - -**Tech Stack:** GitHub Actions, zizmor 1.23.1 - ---- - -## Findings Summary - -| Audit | Severity | Count | Affected Workflows | -|-------|----------|-------|--------------------| -| `unpinned-uses` | error (high) | 31 | all 6 | -| `excessive-permissions` | error+warning | 11 | CI, commit, docs, publish_pypi, release | -| `artipacked` | help (low) | 9 | all 6 | -| `dangerous-triggers` | error (medium) | 1 | labeler | -| `secrets-outside-env` | warning (high) | 3 | CI, publish_pypi | -| `use-trusted-publishing` | info | 1 | publish_pypi | - -## SHA Pin Reference - -Use these exact SHAs (resolved 2026-03-15): - -| Action | Version | SHA | -|--------|---------|-----| -| `actions/checkout` | v6 | `de0fac2e4500dabe0009e67214ff5f5447ce83dd` | -| `actions/setup-python` | v6 | `a309ff8b426b58ec0e2a45f0f869d46889d02405` | -| `astral-sh/setup-uv` | v7 | `b75dde52aef63a238519e7aecbbe79a4a52e4315` | -| `codecov/codecov-action` | v5 | `671740ac38dd9b0130fbe1cec585b89eea48d3de` | -| `actions/cache` | v5 | `cdf6c1fa76f9f475f3d7449005a359c84ca0f306` | -| `benchmark-action/github-action-benchmark` | v1.21.0 | `a7bc2366eda11037936ea57d811a43b3418d3073` | -| `peaceiris/actions-gh-pages` | v4 | `e9c66a37f080288a11235e32cbe2dc5fb3a679cc` | -| `amannn/action-semantic-pull-request` | v6.1.1 | `48f256284bd46cdaab1048c3721360e808335d50` | -| `googleapis/release-please-action` | v4 | `c3fc4de07084f75a2b61a5b933069bda6edf3d5c` | -| `actions/upload-artifact` | v7 | `bbbca2ddaa5d8feaa63e36b76fdaad77386f024f` | -| `actions/download-artifact` | v8 | `3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c` | -| `pypa/gh-action-pypi-publish` | v1.13.0 | `106e0b0b7c337fa67ed433972f777c6357f78598` | -| `actions/labeler` | v6.0.1 | `634933edcd8ababfe52f92936142cc22ac488b1b` | - ---- - -## Task 1: CI.yaml — permissions, pins, and persist-credentials - -**Files:** -- Modify: `.github/workflows/CI.yaml` - -**Step 1: Add top-level permissions block** - -Add after `env:` block (line 11): - -```yaml -permissions: {} -``` - -This sets default to no permissions. The `benchmarks` job already has its own block. - -**Step 2: Add job-level permissions to `pre-commit` and `package` jobs** - -Both need only `contents: read`: - -```yaml - pre-commit: - permissions: - contents: read - ... - - package: - permissions: - contents: read - ... -``` - -**Step 3: Add job-level permissions to `pytest` job** - -Needs `contents: read` (checkout) only. Codecov uses its own token so no extra permission needed: - -```yaml - pytest: - permissions: - contents: read - ... -``` - -**Step 4: Pin all actions to SHAs** - -Replace every `uses:` line with SHA-pinned version (keep version as comment): - -```yaml -# In pre-commit job: -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 - -# In pytest job: -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 - -# In benchmarks job: -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 -- uses: benchmark-action/github-action-benchmark@a7bc2366eda11037936ea57d811a43b3418d3073 # v1 - -# In package job: -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -``` - -**Step 5: Add `persist-credentials: false` to every checkout** - -All 4 checkout steps need: - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false -``` - -For the `pre-commit` checkout that already has `with: fetch-depth: 0`, just add `persist-credentials: false` alongside it. - -**Step 6: Run zizmor on CI.yaml to verify** - -Run: `zizmor .github/workflows/CI.yaml` -Expected: Only `secrets-outside-env` for `CODECOV_TOKEN` remains (acceptable, see Decision Notes below). - -**Step 7: Commit** - -``` -git add .github/workflows/CI.yaml -git commit -m "fix(ci): harden CI.yaml — pin actions, scope permissions, disable credential persistence" -``` - ---- - -## Task 2: commit.yaml — permissions, pins, and persist-credentials - -**Files:** -- Modify: `.github/workflows/commit.yaml` - -**Step 1: Add top-level permissions block** - -```yaml -permissions: {} -``` - -**Step 2: Add permissions to `lint-commit-messages` job** - -Only needs to read the repo: - -```yaml - lint-commit-messages: - permissions: - contents: read - ... -``` - -The `lint-pr-title` job already has `permissions: pull-requests: read`. - -**Step 3: Pin all actions to SHAs** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 -``` - -**Step 4: Add `persist-credentials: false` to checkout** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - persist-credentials: false -``` - -**Step 5: Run zizmor on commit.yaml to verify** - -Run: `zizmor .github/workflows/commit.yaml` -Expected: 0 findings. - -**Step 6: Commit** - -``` -git add .github/workflows/commit.yaml -git commit -m "fix(ci): harden commit.yaml — pin actions, scope permissions, disable credential persistence" -``` - ---- - -## Task 3: docs.yaml — permissions, pins, and persist-credentials - -**Files:** -- Modify: `.github/workflows/docs.yaml` - -**Step 1: Add top-level permissions block** - -The `build` job needs `contents: write` only for the gh-pages deploy step (on push to main). However, the deploy step uses `github_token` explicitly, so we can use a more targeted approach: - -```yaml -permissions: - contents: write -``` - -Note: `peaceiris/actions-gh-pages` needs `contents: write` to push to the gh-pages branch. This is a workflow-level permission because there's only one job. - -**Step 2: Pin all actions to SHAs** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -- uses: peaceiris/actions-gh-pages@e9c66a37f080288a11235e32cbe2dc5fb3a679cc # v4 -``` - -**Step 3: Add `persist-credentials: false` to checkout** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false -``` - -**Step 4: Run zizmor on docs.yaml to verify** - -Run: `zizmor .github/workflows/docs.yaml` -Expected: 0 findings. - -**Step 5: Commit** - -``` -git add .github/workflows/docs.yaml -git commit -m "fix(ci): harden docs.yaml — pin actions, scope permissions, disable credential persistence" -``` - ---- - -## Task 4: labeler.yaml — dangerous trigger, pins, and persist-credentials - -**Files:** -- Modify: `.github/workflows/labeler.yaml` - -**Step 1: Pin all actions to SHAs** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 -``` - -**Step 2: Add `persist-credentials: false` to checkout** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false -``` - -**Step 3: Address `dangerous-triggers` (pull_request_target)** - -`pull_request_target` runs in the context of the base branch, which is inherently risky. However, the labeler workflow: -- Does NOT checkout PR code for execution (it only reads labels/file paths) -- Has minimal permissions (contents: read, pull-requests: write, issues: write) -- Uses the trusted `actions/labeler` action - -This is the standard, documented use case for `pull_request_target`. The risk is acceptable. To suppress the zizmor warning, add an inline annotation: - -```yaml -on: - pull_request_target: # zizmor: ignore[dangerous-triggers] - types: [opened, reopened, synchronize] -``` - -**Step 4: Run zizmor on labeler.yaml to verify** - -Run: `zizmor .github/workflows/labeler.yaml` -Expected: 0 findings (dangerous-triggers suppressed by inline annotation). - -**Step 5: Commit** - -``` -git add .github/workflows/labeler.yaml -git commit -m "fix(ci): harden labeler.yaml — pin actions, disable credential persistence, annotate pull_request_target" -``` - ---- - -## Task 5: publish_pypi.yaml — trusted publishing, permissions, pins - -**Files:** -- Modify: `.github/workflows/publish_pypi.yaml` - -This workflow is the most outdated and has the most issues. It uses `twine` with username/password secrets instead of trusted publishing. - -**Decision point:** The `release.yaml` workflow already handles publishing via trusted publishing (`pypa/gh-action-pypi-publish@release/v1` with OIDC). This `publish_pypi.yaml` appears to be a legacy duplicate. Check with the team whether it should be deleted entirely or migrated. - -**Step 1: If keeping the workflow, migrate to trusted publishing** - -Replace the entire file: - -```yaml -name: Upload to PyPi - -on: - release: - types: [published] - -permissions: {} - -jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - environment: - name: pypi - url: https://pypi.org/p/plexosdb - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - with: - python-version: 3.11 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install build - - name: Build package - run: python -m build - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 -``` - -This eliminates: -- `TWINE_USERNAME` / `TWINE_PASSWORD` secrets (no more `secrets-outside-env`) -- `twine` dependency -- Token-based auth in favor of OIDC trusted publishing - -**Step 1 (alternative): If deleting the workflow** - -Since `release.yaml` already publishes to both TestPyPI and PyPI with trusted publishing, this workflow is redundant. Delete it: - -``` -git rm .github/workflows/publish_pypi.yaml -``` - -**Step 2: Run zizmor to verify** - -Run: `zizmor .github/workflows/publish_pypi.yaml` (if kept) -Expected: 0 findings. - -**Step 3: Commit** - -``` -git add .github/workflows/publish_pypi.yaml -git commit -m "fix(ci): harden publish_pypi.yaml — migrate to trusted publishing, pin actions, scope permissions" -``` - ---- - -## Task 6: release.yaml — permissions, pins, and persist-credentials - -**Files:** -- Modify: `.github/workflows/release.yaml` - -**Step 1: Move permissions from workflow-level to job-level** - -The current workflow has `contents: write`, `pull-requests: write`, `id-token: write` at the top level. Each job should declare only what it needs: - -```yaml -# Remove the top-level permissions block and replace with: -permissions: {} - -# release-please job needs: - release-please: - permissions: - contents: write - pull-requests: write - ... - -# build job needs: - build: - permissions: - contents: read - ... - -# publish-testpypi job needs: - publish-testpypi: - permissions: - id-token: write - ... - -# publish-pypi job needs: - publish-pypi: - permissions: - id-token: write - ... -``` - -**Step 2: Pin all actions to SHAs** - -```yaml -# release-please job: -- uses: googleapis/release-please-action@c3fc4de07084f75a2b61a5b933069bda6edf3d5c # v4 - -# build job: -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 -- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 -- uses: astral-sh/setup-uv@b75dde52aef63a238519e7aecbbe79a4a52e4315 # v7 -- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - -# publish-testpypi job: -- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 -- uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 - -# publish-pypi job: -- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 -- uses: pypa/gh-action-pypi-publish@106e0b0b7c337fa67ed433972f777c6357f78598 # v1.13.0 -``` - -**Step 3: Add `persist-credentials: false` to checkout** - -```yaml -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ needs.release-please.outputs.release_tag }} - fetch-depth: 0 - persist-credentials: false -``` - -**Step 4: Run zizmor on release.yaml to verify** - -Run: `zizmor .github/workflows/release.yaml` -Expected: 0 findings. - -**Step 5: Commit** - -``` -git add .github/workflows/release.yaml -git commit -m "fix(ci): harden release.yaml — scope permissions per job, pin actions, disable credential persistence" -``` - ---- - -## Decision Notes - -### `secrets-outside-env` for CODECOV_TOKEN (CI.yaml) - -zizmor flags `secrets.CODECOV_TOKEN` because it's not accessed within a GitHub environment. Creating a dedicated environment just for codecov adds friction with no real security gain (the token is already scoped to codecov uploads). This finding is acceptable to leave as-is or suppress with `# zizmor: ignore[secrets-outside-env]`. - -### `dangerous-triggers` for labeler.yaml - -`pull_request_target` is the correct trigger for the labeler use case. The workflow does not execute untrusted PR code. Suppressed with inline annotation. - -### publish_pypi.yaml vs release.yaml - -These two workflows appear to overlap. `release.yaml` is the modern one (trusted publishing, TestPyPI + PyPI). `publish_pypi.yaml` is legacy (twine + secrets). Recommend deleting `publish_pypi.yaml` if `release.yaml` is the active publish path. - ---- - -## Verification - -After all tasks, run full scan: - -```bash -zizmor .github/workflows/ -``` - -Expected: 0 errors, 0 warnings. Only acceptable suppressions remain. From e2c1212f71fb7a74987d2c7c5029fa52fe618fd5 Mon Sep 17 00:00:00 2001 From: pesap Date: Sun, 15 Mar 2026 20:14:14 -0600 Subject: [PATCH 3/4] docs: modernize README, add prettier, migrate pre-commit to prek - Rewrite README with proper badges, description, quick start, and uv dev setup - Update all URLs from NREL/plexosdb to NatLabRockies/plexosdb (README + pyproject.toml) - Add .prettierrc.yaml (printWidth: 80, proseWrap: always) - Add prettier hook for markdown in pre-commit config - Migrate pre-commit-hooks from GitHub repo to builtin - Add extra builtin hooks (detect-private-key, no-commit-to-branch, check-xml, etc.) - Reformat all markdown docs with prettier --- .pre-commit-config.yaml | 31 +++++-- .prettierrc.yaml | 2 + CHANGELOG.md | 103 ++++++++++++++------- README.md | 100 ++++++++++++++++---- docs/source/CHANGELOG.md | 13 +-- docs/source/api/index.md | 6 +- docs/source/howtos/add_attributes.md | 1 + docs/source/howtos/add_objects.md | 3 +- docs/source/howtos/add_properties.md | 6 +- docs/source/howtos/add_reports.md | 10 +- docs/source/howtos/bulk_operations.md | 40 +++++--- docs/source/howtos/copy_objects.md | 6 +- docs/source/howtos/create_db.md | 3 +- docs/source/howtos/delete_objects.md | 31 +++++-- docs/source/howtos/import_export.md | 3 +- docs/source/howtos/manage_relationships.md | 3 +- docs/source/howtos/query_database.md | 3 +- docs/source/howtos/work_with_scenarios.md | 1 - docs/source/index.md | 38 +++++--- docs/source/installation.md | 6 +- docs/source/tutorial.md | 16 +++- pyproject.toml | 8 +- 22 files changed, 306 insertions(+), 127 deletions(-) create mode 100644 .prettierrc.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a42f6f2..02e3fb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,17 +21,32 @@ repos: language: system types_or: [python, pyi] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 + - repo: local + hooks: + - id: prettier + name: prettier + entry: prettier --write --ignore-unknown + language: node + additional_dependencies: ["prettier@3"] + types: [markdown] + + - repo: builtin hooks: - - id: end-of-file-fixer - id: trailing-whitespace - id: check-added-large-files - - id: check-merge-conflict - - id: check-yaml - - id: check-toml - - id: check-json + - id: end-of-file-fixer - id: check-case-conflict + - id: fix-byte-order-marker + - id: check-json + - id: check-toml + - id: check-yaml + - id: check-xml + - id: mixed-line-ending + - id: check-symlinks + - id: check-merge-conflict + - id: detect-private-key + - id: no-commit-to-branch + - id: check-executables-have-shebangs - repo: https://github.com/commitizen-tools/commitizen rev: v4.10.0 @@ -50,9 +65,7 @@ repos: pass_filenames: false stages: [pre-push] - - repo: https://github.com/astral-sh/uv-pre-commit - # uv version. rev: 0.9.4 hooks: - id: uv-lock diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..ed01e55 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,2 @@ +printWidth: 80 +proseWrap: "always" diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a401d..2687f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,87 +4,122 @@ All notable changes to this project will be documented in this file. ## [1.3.2](https://github.com/NatLabRockies/plexosdb/compare/v1.3.1...v1.3.2) (2026-02-12) - ### 🐛 Bug Fixes -* Add capability of having a system object name different than system ([#96](https://github.com/NatLabRockies/plexosdb/issues/96)) ([6f3e408](https://github.com/NatLabRockies/plexosdb/commit/6f3e40827b2cc39445761a0822d31a58e4e7f126)) -* Propagate `parent_class_enum` when it is not the system get_object_properties() and iterate_properties() reject valid properties when parent_class_enum is not System. ([#100](https://github.com/NatLabRockies/plexosdb/issues/100)) ([7200897](https://github.com/NatLabRockies/plexosdb/commit/72008973493d086ba1901c6a4e88f3a68da135dd)) +- Add capability of having a system object name different than system + ([#96](https://github.com/NatLabRockies/plexosdb/issues/96)) + ([6f3e408](https://github.com/NatLabRockies/plexosdb/commit/6f3e40827b2cc39445761a0822d31a58e4e7f126)) +- Propagate `parent_class_enum` when it is not the system + get_object_properties() and iterate_properties() reject valid properties when + parent_class_enum is not System. + ([#100](https://github.com/NatLabRockies/plexosdb/issues/100)) + ([7200897](https://github.com/NatLabRockies/plexosdb/commit/72008973493d086ba1901c6a4e88f3a68da135dd)) ## [1.3.1](https://github.com/NatLabRockies/plexosdb/compare/v1.3.0...v1.3.1) (2026-02-10) - ### 🐛 Bug Fixes -* copy_object and list_objects_by_class ([#90](https://github.com/NatLabRockies/plexosdb/issues/90)) ([a11783e](https://github.com/NatLabRockies/plexosdb/commit/a11783edda469f5ad8f0dc6a5185139ce51fc1b7)) - +- copy_object and list_objects_by_class + ([#90](https://github.com/NatLabRockies/plexosdb/issues/90)) + ([a11783e](https://github.com/NatLabRockies/plexosdb/commit/a11783edda469f5ad8f0dc6a5185139ce51fc1b7)) ### 📦 Build -* **deps-dev:** bump ipython from 9.7.0 to 9.8.0 ([#86](https://github.com/NatLabRockies/plexosdb/issues/86)) ([50c97c5](https://github.com/NatLabRockies/plexosdb/commit/50c97c59e169860e9eace86dfaf0aeb62f795de2)) -* **deps-dev:** bump pytest from 9.0.1 to 9.0.2 ([#87](https://github.com/NatLabRockies/plexosdb/issues/87)) ([07e697b](https://github.com/NatLabRockies/plexosdb/commit/07e697b6e142521e84394901e8360cf28d8b95d1)) -* **deps:** bump actions/download-artifact from 6 to 7 ([#89](https://github.com/NatLabRockies/plexosdb/issues/89)) ([41ab2b4](https://github.com/NatLabRockies/plexosdb/commit/41ab2b47bbee8371aab0ca8a88a7dfcb5fa544d6)) -* **deps:** bump actions/upload-artifact from 5 to 6 ([#88](https://github.com/NatLabRockies/plexosdb/issues/88)) ([fc840f8](https://github.com/NatLabRockies/plexosdb/commit/fc840f85991f803817a7910a09ca0ff06c6f4713)) +- **deps-dev:** bump ipython from 9.7.0 to 9.8.0 + ([#86](https://github.com/NatLabRockies/plexosdb/issues/86)) + ([50c97c5](https://github.com/NatLabRockies/plexosdb/commit/50c97c59e169860e9eace86dfaf0aeb62f795de2)) +- **deps-dev:** bump pytest from 9.0.1 to 9.0.2 + ([#87](https://github.com/NatLabRockies/plexosdb/issues/87)) + ([07e697b](https://github.com/NatLabRockies/plexosdb/commit/07e697b6e142521e84394901e8360cf28d8b95d1)) +- **deps:** bump actions/download-artifact from 6 to 7 + ([#89](https://github.com/NatLabRockies/plexosdb/issues/89)) + ([41ab2b4](https://github.com/NatLabRockies/plexosdb/commit/41ab2b47bbee8371aab0ca8a88a7dfcb5fa544d6)) +- **deps:** bump actions/upload-artifact from 5 to 6 + ([#88](https://github.com/NatLabRockies/plexosdb/issues/88)) + ([fc840f8](https://github.com/NatLabRockies/plexosdb/commit/fc840f85991f803817a7910a09ca0ff06c6f4713)) ## [1.3.0](https://github.com/NREL/plexosdb/compare/v1.2.2...v1.3.0) (2025-12-11) - ### 🚀 Features -* Making add_from_records more robust ([#85](https://github.com/NREL/plexosdb/issues/85)) ([827b2dd](https://github.com/NREL/plexosdb/commit/827b2ddaa24cd5c9531d4f78fab8f4e1cb441ff2)) - +- Making add_from_records more robust + ([#85](https://github.com/NREL/plexosdb/issues/85)) + ([827b2dd](https://github.com/NREL/plexosdb/commit/827b2ddaa24cd5c9531d4f78fab8f4e1cb441ff2)) ### 📦 Build -* **deps:** bump pre-commit from 4.2.0 to 4.5.0 ([#82](https://github.com/NREL/plexosdb/issues/82)) ([a590ce9](https://github.com/NREL/plexosdb/commit/a590ce949bef17e8bfa81fe70bf75293d2e88aa8)) -* **deps:** bump ruff from 0.14.7 to 0.14.8 ([#83](https://github.com/NREL/plexosdb/issues/83)) ([c4123e1](https://github.com/NREL/plexosdb/commit/c4123e13f38d43586e1ba306b09b7b73c04b7b59)) +- **deps:** bump pre-commit from 4.2.0 to 4.5.0 + ([#82](https://github.com/NREL/plexosdb/issues/82)) + ([a590ce9](https://github.com/NREL/plexosdb/commit/a590ce949bef17e8bfa81fe70bf75293d2e88aa8)) +- **deps:** bump ruff from 0.14.7 to 0.14.8 + ([#83](https://github.com/NREL/plexosdb/issues/83)) + ([c4123e1](https://github.com/NREL/plexosdb/commit/c4123e13f38d43586e1ba306b09b7b73c04b7b59)) ## [1.2.2](https://github.com/NREL/plexosdb/compare/v1.2.1...v1.2.2) (2025-12-06) - ### 🐛 Bug Fixes -* Update battery collection enum naming and add increment to rank for same class enum ([#80](https://github.com/NREL/plexosdb/issues/80)) ([e247e67](https://github.com/NREL/plexosdb/commit/e247e6731f05eeef792cf8de09f0123e6f9d2995)) +- Update battery collection enum naming and add increment to rank for same class + enum ([#80](https://github.com/NREL/plexosdb/issues/80)) + ([e247e67](https://github.com/NREL/plexosdb/commit/e247e6731f05eeef792cf8de09f0123e6f9d2995)) ## [1.2.1](https://github.com/NREL/plexosdb/compare/v1.2.0...v1.2.1) (2025-12-04) - ### 🐛 Bug Fixes -* handle property related attributes on "add_properties_from_records" method ([#78](https://github.com/NREL/plexosdb/issues/78)) ([1776d2a](https://github.com/NREL/plexosdb/commit/1776d2a614facef29d5a2a3df1f3a27dd154e359)) +- handle property related attributes on "add_properties_from_records" method + ([#78](https://github.com/NREL/plexosdb/issues/78)) + ([1776d2a](https://github.com/NREL/plexosdb/commit/1776d2a614facef29d5a2a3df1f3a27dd154e359)) ## [1.2.0](https://github.com/NREL/plexosdb/compare/v1.1.3...v1.2.0) (2025-12-02) - ### 🚀 Features -* Adding method `add_datafile_tag` and refactor add_properties/add_properties_from_records ([#69](https://github.com/NREL/plexosdb/issues/69)) ([1e6e018](https://github.com/NREL/plexosdb/commit/1e6e01852e46fba89b16120c07d472f4c84f94ab)) -* Adding new fixtures for cleaner testing. ([#68](https://github.com/NREL/plexosdb/issues/68)) ([9062baa](https://github.com/NREL/plexosdb/commit/9062baab8db1eb611dcb7364e952bbdb898fe36a)) -* Adding query date_from and date_to to properties ([#67](https://github.com/NREL/plexosdb/issues/67)) ([00d533b](https://github.com/NREL/plexosdb/commit/00d533b4b547822a09984cf59e40738fea330f4a)) - +- Adding method `add_datafile_tag` and refactor + add_properties/add_properties_from_records + ([#69](https://github.com/NREL/plexosdb/issues/69)) + ([1e6e018](https://github.com/NREL/plexosdb/commit/1e6e01852e46fba89b16120c07d472f4c84f94ab)) +- Adding new fixtures for cleaner testing. + ([#68](https://github.com/NREL/plexosdb/issues/68)) + ([9062baa](https://github.com/NREL/plexosdb/commit/9062baab8db1eb611dcb7364e952bbdb898fe36a)) +- Adding query date_from and date_to to properties + ([#67](https://github.com/NREL/plexosdb/issues/67)) + ([00d533b](https://github.com/NREL/plexosdb/commit/00d533b4b547822a09984cf59e40738fea330f4a)) ### 🐛 Bug Fixes -* Adding new release-please workflow ([#71](https://github.com/NREL/plexosdb/issues/71)) ([1f8da38](https://github.com/NREL/plexosdb/commit/1f8da384a9deb3edfa7a343e91999d3d37e07b17)) - +- Adding new release-please workflow + ([#71](https://github.com/NREL/plexosdb/issues/71)) + ([1f8da38](https://github.com/NREL/plexosdb/commit/1f8da384a9deb3edfa7a343e91999d3d37e07b17)) ### 📦 Build -* **deps:** bump actions/checkout from 4 to 6 ([#74](https://github.com/NREL/plexosdb/issues/74)) ([bb7be8d](https://github.com/NREL/plexosdb/commit/bb7be8d0e36c332051ec1a1767b8862f4baec359)) -* **deps:** bump actions/setup-python from 5 to 6 ([#73](https://github.com/NREL/plexosdb/issues/73)) ([18a0d9d](https://github.com/NREL/plexosdb/commit/18a0d9d26db2056d79bbdda6cec168e895abe0e9)) -* **deps:** bump furo from 2024.8.6 to 2025.9.25 ([#77](https://github.com/NREL/plexosdb/issues/77)) ([3dd6463](https://github.com/NREL/plexosdb/commit/3dd64632666e31a38f6ed8f8bb15f9d333e64791)) -* **deps:** bump ipython from 9.4.0 to 9.7.0 ([#76](https://github.com/NREL/plexosdb/issues/76)) ([ca687df](https://github.com/NREL/plexosdb/commit/ca687dfa466990e59c273c26908496c1fd5a8878)) -* **deps:** bump pytest from 8.4.1 to 9.0.1 ([#75](https://github.com/NREL/plexosdb/issues/75)) ([5864d85](https://github.com/NREL/plexosdb/commit/5864d85e69e5e6cc865fc389e6b15f8e63785001)) +- **deps:** bump actions/checkout from 4 to 6 + ([#74](https://github.com/NREL/plexosdb/issues/74)) + ([bb7be8d](https://github.com/NREL/plexosdb/commit/bb7be8d0e36c332051ec1a1767b8862f4baec359)) +- **deps:** bump actions/setup-python from 5 to 6 + ([#73](https://github.com/NREL/plexosdb/issues/73)) + ([18a0d9d](https://github.com/NREL/plexosdb/commit/18a0d9d26db2056d79bbdda6cec168e895abe0e9)) +- **deps:** bump furo from 2024.8.6 to 2025.9.25 + ([#77](https://github.com/NREL/plexosdb/issues/77)) + ([3dd6463](https://github.com/NREL/plexosdb/commit/3dd64632666e31a38f6ed8f8bb15f9d333e64791)) +- **deps:** bump ipython from 9.4.0 to 9.7.0 + ([#76](https://github.com/NREL/plexosdb/issues/76)) + ([ca687df](https://github.com/NREL/plexosdb/commit/ca687dfa466990e59c273c26908496c1fd5a8878)) +- **deps:** bump pytest from 8.4.1 to 9.0.1 + ([#75](https://github.com/NREL/plexosdb/issues/75)) + ([5864d85](https://github.com/NREL/plexosdb/commit/5864d85e69e5e6cc865fc389e6b15f8e63785001)) ## [0.0.1] - 2024-08-21 ### 🐛 Bug Fixes -- *(get_memberships)* Updated `get_membership` function (#6) +- _(get_memberships)_ Updated `get_membership` function (#6) ### ⚙️ Miscellaneous Tasks -- *(actions)* Adding first version of GitHub actions (#5) -- *(actions)* Fixing GitHub Actions and refactoring API (#7) +- _(actions)_ Adding first version of GitHub actions (#5) +- _(actions)_ Fixing GitHub Actions and refactoring API (#7) - Removing trailwhitespace diff --git a/README.md b/README.md index 09f022f..9e12b83 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,103 @@ -### Database Manager for use with PLEXOS XML files -[![image](https://img.shields.io/pypi/v/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) -[![image](https://img.shields.io/pypi/l/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) -[![image](https://img.shields.io/pypi/pyversions/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) -[![CI](https://github.com/NREL/plexosdb/actions/workflows/CI.yaml/badge.svg)](https://github.com/NREL/plexosdb/actions/workflows/CI.yaml) -[![codecov](https://codecov.io/gh/NREL/plexosdb/branch/main/graph/badge.svg)](https://codecov.io/gh/NREL/plexosdb) -[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -
+### plexosdb + +> SQLite-backed Python API for reading, building, and writing PLEXOS XML models +> +> [![image](https://img.shields.io/pypi/v/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) +> [![image](https://img.shields.io/pypi/l/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) +> [![image](https://img.shields.io/pypi/pyversions/plexosdb.svg)](https://pypi.python.org/pypi/plexosdb) +> [![CI](https://github.com/NatLabRockies/plexosdb/actions/workflows/CI.yaml/badge.svg)](https://github.com/NatLabRockies/plexosdb/actions/workflows/CI.yaml) +> [![codecov](https://codecov.io/gh/NatLabRockies/plexosdb/branch/main/graph/badge.svg)](https://codecov.io/gh/NatLabRockies/plexosdb) +> [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +> [![Documentation](https://github.com/NatLabRockies/plexosdb/actions/workflows/docs.yaml/badge.svg?branch=main)](https://natlabrockies.github.io/plexosdb/) + +plexosdb converts PLEXOS XML input files into an in-memory SQLite database, +giving you a fast, typed Python API to query, create, and modify power system +models programmatically, then write them back to XML. ## Installation ```console -python -m pip install plexosdb +pip install plexosdb ``` -## Developer installation +Or with [uv](https://docs.astral.sh/uv/): ```console -$ pip install -e ".[dev]" +uv add plexosdb +``` + +**Python version support:** 3.11, 3.12, 3.13, 3.14 + +## Quick Start + +```python +from plexosdb import PlexosDB, ClassEnum, CollectionEnum + +# Load a PLEXOS XML file into an in-memory SQLite database +db = PlexosDB.from_xml("model.xml") + +# Query existing objects +generators = db.get_object_names(ClassEnum.Generator) + +# Add new objects +db.add_object(ClassEnum.Generator, name="SolarPV_01", category="Renewables") +db.add_object(ClassEnum.Node, name="Bus_1") + +# Create memberships between objects +db.add_membership( + CollectionEnum.GeneratorNodes, + parent_class=ClassEnum.Generator, + parent_name="SolarPV_01", + child_class=ClassEnum.Node, + child_name="Bus_1", +) + +# Export the modified model back to XML +db.to_xml("modified_model.xml") ``` -Please install `pre-commit` so that your code is checked before making commits. +## Documentation + +Full documentation is available at +[natlabrockies.github.io/plexosdb](https://natlabrockies.github.io/plexosdb/). + +## Developer Setup + +plexosdb uses [uv](https://docs.astral.sh/uv/) for dependency management. + +```console +git clone https://github.com/NatLabRockies/plexosdb.git +cd plexosdb +uv sync --all-groups +``` + +Install the pre-commit hooks so your code is checked before making commits: ```console -pre-commit install +uv run pre-commit install +``` + +Run the test suite: + +```console +uv run pytest ``` ## License This software is released under a BSD-3-Clause -[License](https://github.com/NREL/plexosdb/blob/main/LICENSE.txt). - -This software was developed under software record SWR-24-90 at the National Renewable Energy Laboratory -([NREL](https://www.nrel.gov)). +[License](https://github.com/NatLabRockies/plexosdb/blob/main/LICENSE.txt). +This software was developed under software record SWR-24-90 at the National +Renewable Energy Laboratory ([NREL](https://www.nrel.gov)). ## Disclaimer -PLEXOS is a registered trademark of Energy Exemplar Pty Ltd. Energy Exemplar Pty Ltd. has no affiliation to or participation in this software. Reference herein to any specific commercial products, process, or service by trade name, trademark, manufacturer, or otherwise, does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or Alliance for Sustainable Energy, LLC ("Alliance"). The views and opinions of authors expressed in the available or referenced documents do not necessarily state or reflect those of the United States Government or Alliance. +PLEXOS is a registered trademark of Energy Exemplar Pty Ltd. Energy Exemplar Pty +Ltd. has no affiliation to or participation in this software. Reference herein +to any specific commercial products, process, or service by trade name, +trademark, manufacturer, or otherwise, does not necessarily constitute or imply +its endorsement, recommendation, or favoring by the United States Government or +Alliance for Sustainable Energy, LLC ("Alliance"). The views and opinions of +authors expressed in the available or referenced documents do not necessarily +state or reflect those of the United States Government or Alliance. diff --git a/docs/source/CHANGELOG.md b/docs/source/CHANGELOG.md index c0ed571..e5d936e 100644 --- a/docs/source/CHANGELOG.md +++ b/docs/source/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. - Consolidate code structure (#26) - Adding support for adding attributes (#27) -- *(scenarios)* Adding scenario manipulation. (#28) +- _(scenarios)_ Adding scenario manipulation. (#28) - Adding report writing capability (#29) - Adding bulk operations to plexosdb (#30) @@ -48,7 +48,8 @@ All notable changes to this project will be documented in this file. ### 🚀 Features -- Enable capability of find enums with spaces and new methods for easy interaction (#17) +- Enable capability of find enums with spaces and new methods for easy + interaction (#17) ## [0.0.3] - 2024-09-17 @@ -77,7 +78,7 @@ All notable changes to this project will be documented in this file. ### ⚙️ Miscellaneous Tasks -- Fixing pre-commit errors and adding more workflows (#9) +- Fixing pre-commit errors and adding more workflows (#9) - Initial test PR (#10) - Fixing actions and coverage (#11) @@ -85,12 +86,12 @@ All notable changes to this project will be documented in this file. ### 🐛 Bug Fixes -- *(get_memberships)* Updated `get_membership` function (#6) +- _(get_memberships)_ Updated `get_membership` function (#6) ### ⚙️ Miscellaneous Tasks -- *(actions)* Adding first version of GitHub actions (#5) -- *(actions)* Fixing GitHub Actions and refactoring API (#7) +- _(actions)_ Adding first version of GitHub actions (#5) +- _(actions)_ Fixing GitHub Actions and refactoring API (#7) - Removing trailwhitespace diff --git a/docs/source/api/index.md b/docs/source/api/index.md index af2a0df..a885ae3 100644 --- a/docs/source/api/index.md +++ b/docs/source/api/index.md @@ -1,7 +1,11 @@ (api)= + # API Reference -This section provides comprehensive reference documentation for all PlexosDB classes, methods, and modules. Use this section when you need detailed information about specific functions, their parameters, return values, and behavior. +This section provides comprehensive reference documentation for all PlexosDB +classes, methods, and modules. Use this section when you need detailed +information about specific functions, their parameters, return values, and +behavior. ## Core Components diff --git a/docs/source/howtos/add_attributes.md b/docs/source/howtos/add_attributes.md index 8ae2850..d1481c7 100644 --- a/docs/source/howtos/add_attributes.md +++ b/docs/source/howtos/add_attributes.md @@ -6,6 +6,7 @@ table. ## Listing available attributes per `ClassEnum` To see the list of available attributes per `ClassEnum` use: + ```python from plexosdb import PlexosDB, ClassEnum db = PlexosDB.from_xml("/path/to/your/xml") diff --git a/docs/source/howtos/add_objects.md b/docs/source/howtos/add_objects.md index 3c52851..232bbf1 100644 --- a/docs/source/howtos/add_objects.md +++ b/docs/source/howtos/add_objects.md @@ -1,6 +1,7 @@ # Adding Objects to the Database -Objects in PlexosDB represent entities in your PLEXOS model like generators, regions, and nodes. +Objects in PlexosDB represent entities in your PLEXOS model like generators, +regions, and nodes. ## Basic Object Creation diff --git a/docs/source/howtos/add_properties.md b/docs/source/howtos/add_properties.md index b7bd253..c3f60c8 100644 --- a/docs/source/howtos/add_properties.md +++ b/docs/source/howtos/add_properties.md @@ -1,6 +1,7 @@ # Adding Properties to Objects -Properties define attributes of objects in your PLEXOS model, such as a generator's capacity or a node's location. +Properties define attributes of objects in your PLEXOS model, such as a +generator's capacity or a node's location. ## Basic Property Addition @@ -82,7 +83,8 @@ db.add_property( ## Bulk Adding Properties -For efficiency when adding many properties at once (use the flat format; the nested format is accepted but deprecated and will emit a warning): +For efficiency when adding many properties at once (use the flat format; the +nested format is accepted but deprecated and will emit a warning): ```python # Flat format (recommended) diff --git a/docs/source/howtos/add_reports.md b/docs/source/howtos/add_reports.md index 4b8b281..4133b6f 100644 --- a/docs/source/howtos/add_reports.md +++ b/docs/source/howtos/add_reports.md @@ -1,10 +1,13 @@ # How to Add Report Configurations -This guide demonstrates how to add report configurations to your PLEXOS model using the PlexosDB API. +This guide demonstrates how to add report configurations to your PLEXOS model +using the PlexosDB API. ## Basic Report Configuration -Reports in PLEXOS define what data will be available for post-processing after simulation runs. Each report must be associated with a Report object and specify which properties should be reported from which collections. +Reports in PLEXOS define what data will be available for post-processing after +simulation runs. Each report must be associated with a Report object and specify +which properties should be reported from which collections. ```python from plexosdb import PlexosDB @@ -87,7 +90,8 @@ for prop in ["Generation", "Available Capacity"]: ## Understanding Phase IDs -The `phase_id` parameter specifies which simulation phase to create the report for: +The `phase_id` parameter specifies which simulation phase to create the report +for: - `1`: ST (Short Term) - `2`: MT (Medium Term) diff --git a/docs/source/howtos/bulk_operations.md b/docs/source/howtos/bulk_operations.md index 4b08bcc..3ddb074 100644 --- a/docs/source/howtos/bulk_operations.md +++ b/docs/source/howtos/bulk_operations.md @@ -1,10 +1,14 @@ # Bulk Operations with PlexosDB -This guide demonstrates how to efficiently perform bulk operations using PlexosDB, which can significantly improve performance when working with large datasets. +This guide demonstrates how to efficiently perform bulk operations using +PlexosDB, which can significantly improve performance when working with large +datasets. ## Bulk Inserting Properties -When you need to add multiple properties to multiple objects, using individual `add_property` calls can be inefficient. The `add_properties_from_records` method provides a much more efficient approach. +When you need to add multiple properties to multiple objects, using individual +`add_property` calls can be inefficient. The `add_properties_from_records` +method provides a much more efficient approach. ### Basic Usage @@ -54,9 +58,13 @@ db.add_properties_from_records( ### Performance Considerations -The `add_properties_from_records` method processes records in batches (default 10,000 records per batch) and uses SQLite transactions to maximize performance. This makes it much faster than individual property insertions, especially for large datasets. +The `add_properties_from_records` method processes records in batches (default +10,000 records per batch) and uses SQLite transactions to maximize performance. +This makes it much faster than individual property insertions, especially for +large datasets. Key performance features: + - Single transaction for all insertions (atomic operations) - Batch processing to control memory usage - Direct SQL execution with prepared statements @@ -106,13 +114,16 @@ db.add_properties_from_records( ### Data Validation The method automatically validates: + - All objects exist before attempting inserts - All property names are valid for the collection - All required fields are present ## Bulk Inserting Memberships -Creating relationships between many objects can be time-consuming when done individually. The `add_memberships_from_records` method allows you to efficiently create multiple memberships in a single operation. +Creating relationships between many objects can be time-consuming when done +individually. The `add_memberships_from_records` method allows you to +efficiently create multiple memberships in a single operation. ### Basic Usage @@ -155,24 +166,26 @@ membership_records = create_membership_record( db.add_memberships_from_records(membership_records) ``` - -To identify the correct `CollectionEnum` for your relationship, use the `list_collections` method: +To identify the correct `CollectionEnum` for your relationship, use the +`list_collections` method: ```python collection_list = db.list_collections(parent_class=ClassEnum.Region, child_class=ClassEnum.Node) print(collection_list) # Shows available collections for Region-Node relationships ``` -This ensures you're using the exact collection name that exists in your database schema. - +This ensures you're using the exact collection name that exists in your database +schema. ### Performance Benefits -Using `add_memberships_from_records` offers several advantages over individual `add_membership` calls: +Using `add_memberships_from_records` offers several advantages over individual +`add_membership` calls: - Significantly reduced execution time for large datasets - Lower overhead from fewer database operations -- Optional chunking for very large datasets (controlled by the `chunksize` parameter) +- Optional chunking for very large datasets (controlled by the `chunksize` + parameter) - Efficient batch SQL execution ### Manual Record Creation @@ -196,6 +209,7 @@ db.add_memberships_from_records(records) ``` Each record must contain these fields: + - `parent_class_id`: ID of the parent class - `parent_object_id`: ID of the parent object - `collection_id`: ID of the collection @@ -204,10 +218,12 @@ Each record must contain these fields: ## Combined Bulk Operations -For complex model creation, you can combine bulk operations to efficiently build your model: +For complex model creation, you can combine bulk operations to efficiently build +your model: 1. First create all objects using `add_objects` (bulk object creation) 2. Add memberships between objects with `add_memberships_from_records` 3. Add properties to the objects using `add_properties_from_records` -This approach can dramatically improve performance when creating large, complex models. +This approach can dramatically improve performance when creating large, complex +models. diff --git a/docs/source/howtos/copy_objects.md b/docs/source/howtos/copy_objects.md index 948d42a..9a227bf 100644 --- a/docs/source/howtos/copy_objects.md +++ b/docs/source/howtos/copy_objects.md @@ -1,6 +1,7 @@ # Copying Objects -PlexosDB allows you to create copies of existing objects along with their properties, memberships, and related property records. +PlexosDB allows you to create copies of existing objects along with their +properties, memberships, and related property records. ## Basic Object Copying @@ -70,7 +71,8 @@ new_object_id = db.copy_object( ## Copying Memberships -When copying an object, PlexosDB also attempts to copy its memberships (except any that cannot be recreated due to model constraints): +When copying an object, PlexosDB also attempts to copy its memberships (except +any that cannot be recreated due to model constraints): ```python # First create some objects with memberships diff --git a/docs/source/howtos/create_db.md b/docs/source/howtos/create_db.md index ed2148f..f796fae 100644 --- a/docs/source/howtos/create_db.md +++ b/docs/source/howtos/create_db.md @@ -1,6 +1,7 @@ # Creating a database from an existing XML file -PlexosDB allows you to create a database from an existing XML file using a few simple steps. +PlexosDB allows you to create a database from an existing XML file using a few +simple steps. ## Basic Usage diff --git a/docs/source/howtos/delete_objects.md b/docs/source/howtos/delete_objects.md index b2f8062..a7f4d10 100644 --- a/docs/source/howtos/delete_objects.md +++ b/docs/source/howtos/delete_objects.md @@ -1,10 +1,13 @@ # Deleting Objects and Properties from the Database -This guide demonstrates how to delete objects and properties from the PlexosDB database. +This guide demonstrates how to delete objects and properties from the PlexosDB +database. ## Deleting Objects -When you delete an object, all its associated data (properties, memberships, etc.) are automatically removed due to foreign key constraints with cascade deletion. +When you delete an object, all its associated data (properties, memberships, +etc.) are automatically removed due to foreign key constraints with cascade +deletion. ```python from plexosdb import PlexosDB @@ -28,7 +31,8 @@ db.delete_object(ClassEnum.Generator, name="TestGenerator") ## Deleting Properties -You can delete specific properties from objects without removing the object itself. This provides fine-grained control over data management. +You can delete specific properties from objects without removing the object +itself. This provides fine-grained control over data management. ### Basic Property Deletion @@ -52,7 +56,8 @@ db.delete_property(ClassEnum.Generator, "PowerPlant1", property_name="Min Stable ### Scenario-Specific Property Deletion -When properties have scenario-specific values, you can delete only the property data associated with a particular scenario: +When properties have scenario-specific values, you can delete only the property +data associated with a particular scenario: ```python # Add properties with different scenarios @@ -92,9 +97,12 @@ db.delete_property( ### Cascade Deletion Behavior -- **Object deletion**: Removes the object and ALL associated data (properties, memberships, text data, etc.) -- **Property deletion**: Removes only the specified property data, including associated text, tags, and date ranges -- **Scenario-specific deletion**: Removes only property data tagged with the specified scenario +- **Object deletion**: Removes the object and ALL associated data (properties, + memberships, text data, etc.) +- **Property deletion**: Removes only the specified property data, including + associated text, tags, and date ranges +- **Scenario-specific deletion**: Removes only property data tagged with the + specified scenario ### Error Handling @@ -122,9 +130,12 @@ except NameError as e: ## Best Practices 1. **Backup data**: Always backup your database before performing bulk deletions -2. **Verify existence**: Check that objects and properties exist before attempting deletion -3. **Use transactions**: For complex operations, wrap deletions in database transactions -4. **Scenario management**: Be specific about scenarios when deleting scenario-based properties +2. **Verify existence**: Check that objects and properties exist before + attempting deletion +3. **Use transactions**: For complex operations, wrap deletions in database + transactions +4. **Scenario management**: Be specific about scenarios when deleting + scenario-based properties ```python # Example of safe deletion with verification diff --git a/docs/source/howtos/import_export.md b/docs/source/howtos/import_export.md index a203007..aaca443 100644 --- a/docs/source/howtos/import_export.md +++ b/docs/source/howtos/import_export.md @@ -1,6 +1,7 @@ # Importing and Exporting Data -PlexosDB provides methods for importing data from XML files and exporting to XML or CSV formats. +PlexosDB provides methods for importing data from XML files and exporting to XML +or CSV formats. ## Importing from XML diff --git a/docs/source/howtos/manage_relationships.md b/docs/source/howtos/manage_relationships.md index 139609f..4b22b9f 100644 --- a/docs/source/howtos/manage_relationships.md +++ b/docs/source/howtos/manage_relationships.md @@ -1,6 +1,7 @@ # Managing Object Relationships -In PLEXOS, objects can have relationships with each other. These relationships are managed through memberships in PlexosDB. +In PLEXOS, objects can have relationships with each other. These relationships +are managed through memberships in PlexosDB. ## Creating Relationships (Memberships) diff --git a/docs/source/howtos/query_database.md b/docs/source/howtos/query_database.md index 9145971..d92b084 100644 --- a/docs/source/howtos/query_database.md +++ b/docs/source/howtos/query_database.md @@ -1,6 +1,7 @@ # Querying the Database -PlexosDB provides various methods to efficiently retrieve data from your PLEXOS model. +PlexosDB provides various methods to efficiently retrieve data from your PLEXOS +model. ## Getting Object Properties diff --git a/docs/source/howtos/work_with_scenarios.md b/docs/source/howtos/work_with_scenarios.md index 677cd35..6392a7a 100644 --- a/docs/source/howtos/work_with_scenarios.md +++ b/docs/source/howtos/work_with_scenarios.md @@ -17,7 +17,6 @@ db.create_schema() db.add_scenario("TestScenario") ``` - ## Creating Scenario Properties Scenarios are automatically created when adding properties with a scenario name: diff --git a/docs/source/index.md b/docs/source/index.md index 3637026..3fccd3b 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,6 +1,7 @@ # PlexosDB Documentation -PlexosDB is a Python library for working with PLEXOS energy market simulation models. +PlexosDB is a Python library for working with PLEXOS energy market simulation +models. ```{toctree} :maxdepth: 2 @@ -15,29 +16,40 @@ CHANGELOG ## About PlexosDB -PlexosDB provides a Python interface for working with PLEXOS energy market simulation models. The library converts PLEXOS XML files into SQLite databases and offers a comprehensive API for creating, querying, and manipulating energy system models. +PlexosDB provides a Python interface for working with PLEXOS energy market +simulation models. The library converts PLEXOS XML files into SQLite databases +and offers a comprehensive API for creating, querying, and manipulating energy +system models. ### Key Features PlexosDB offers the following capabilities: -- Complete support for PLEXOS model components including generators, regions, lines, and transmission networks -- Optimized SQLite backend with transaction support and bulk operations for handling large datasets efficiently -- Seamless bidirectional conversion between PLEXOS XML format and database representation -- Scenario management system for creating and comparing different model configurations -- Memory-efficient iterators and chunked processing for working with large models +- Complete support for PLEXOS model components including generators, regions, + lines, and transmission networks +- Optimized SQLite backend with transaction support and bulk operations for + handling large datasets efficiently +- Seamless bidirectional conversion between PLEXOS XML format and database + representation +- Scenario management system for creating and comparing different model + configurations +- Memory-efficient iterators and chunked processing for working with large + models ## Getting Started -To begin using PlexosDB, start with the [installation guide](installation) and then follow the step-by-step [tutorial](tutorial). +To begin using PlexosDB, start with the [installation guide](installation) and +then follow the step-by-step [tutorial](tutorial). ## How-to Guides -Task-oriented guides for specific workflows can be found in the [How-to Guides](howtos/index) section. +Task-oriented guides for specific workflows can be found in the +[How-to Guides](howtos/index) section. ## Reference -Complete API documentation is available in the [API Reference](api/index) section. +Complete API documentation is available in the [API Reference](api/index) +section. ## Release Notes @@ -45,6 +57,6 @@ Track changes and updates in the [Release Notes](CHANGELOG). ## Indices and Tables -* {ref}`genindex` -* {ref}`modindex` -* {ref}`search` +- {ref}`genindex` +- {ref}`modindex` +- {ref}`search` diff --git a/docs/source/installation.md b/docs/source/installation.md index 3bf4599..49e2f6a 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -28,7 +28,8 @@ pip install git+https://github.com/NREL/plexosdb.git ## Using uv -[uv](https://github.com/astral-sh/uv) is a new, fast Python package installer and resolver. To install plexosdb using uv: +[uv](https://github.com/astral-sh/uv) is a new, fast Python package installer +and resolver. To install plexosdb using uv: ```bash # Install uv if you haven't already @@ -50,7 +51,8 @@ plexosdb requires: ## Verification -After installation, you can verify that plexosdb was installed correctly by running: +After installation, you can verify that plexosdb was installed correctly by +running: ```python import plexosdb diff --git a/docs/source/tutorial.md b/docs/source/tutorial.md index 22515a2..7db629a 100644 --- a/docs/source/tutorial.md +++ b/docs/source/tutorial.md @@ -1,6 +1,8 @@ # Tutorial -This tutorial provides a step-by-step introduction to PlexosDB, guiding you through the essential concepts and operations for working with PLEXOS energy market simulation models. +This tutorial provides a step-by-step introduction to PlexosDB, guiding you +through the essential concepts and operations for working with PLEXOS energy +market simulation models. ## Prerequisites @@ -38,7 +40,8 @@ This creates an empty database with the PLEXOS schema structure ready for data. ## Working with Objects -Objects represent entities in your energy model such as generators, nodes, and regions. +Objects represent entities in your energy model such as generators, nodes, and +regions. ### Adding Objects @@ -58,7 +61,8 @@ print(f"Generators: {generators}") ### Adding Properties -Properties define characteristics of objects like capacity, cost, or operational parameters: +Properties define characteristics of objects like capacity, cost, or operational +parameters: ```python # Add capacity property to the generator @@ -88,7 +92,8 @@ for prop in properties: ## Working with XML Files -PlexosDB can import existing PLEXOS XML files and export databases back to XML format. +PlexosDB can import existing PLEXOS XML files and export databases back to XML +format. ### Importing from XML @@ -110,7 +115,8 @@ db.to_xml("/path/to/output_model.xml") ## Working with Scenarios -Scenarios allow you to model different operational conditions or future projections: +Scenarios allow you to model different operational conditions or future +projections: ```python # Add a scenario diff --git a/pyproject.toml b/pyproject.toml index d9d9834..0e0b8f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,10 +38,10 @@ dependencies = [ ] [project.urls] -Documentation = "https://github.com/NREL/plexosdb#readme" -Issues = "https://github.com/NREL/plexosdb/issues" -Source = "https://github.com/NREL/plexosdb" -Changelog = "https://github.com/NREL/plexosdb/blob/master/CHANGELOG.md" +Documentation = "https://natlabrockies.github.io/plexosdb/" +Issues = "https://github.com/NatLabRockies/plexosdb/issues" +Source = "https://github.com/NatLabRockies/plexosdb" +Changelog = "https://github.com/NatLabRockies/plexosdb/blob/main/CHANGELOG.md" [build-system] requires = ["uv_build>=0.8.22,<0.9.0"] From eab160245ef6878879d3f1609c6f51134f344fd6 Mon Sep 17 00:00:00 2001 From: pesap Date: Sun, 15 Mar 2026 20:17:24 -0600 Subject: [PATCH 4/4] build: migrate from pre-commit to prek - Replace pre-commit with prek in dev dependencies - Rename CI job from pre-commit to prek, use prek run command - Update README developer setup to use prek install --- .github/workflows/CI.yaml | 12 ++--- README.md | 4 +- pyproject.toml | 2 +- uv.lock | 102 ++++++++------------------------------ 4 files changed, 30 insertions(+), 90 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 5c4b19d..cd8061b 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -13,9 +13,9 @@ env: permissions: {} jobs: - pre-commit: + prek: runs-on: ubuntu-latest - name: Pre-commit hooks (lint/format/spell/type, all files) + name: Prek hooks (lint/format/spell/type, all files) permissions: contents: read steps: @@ -38,13 +38,13 @@ jobs: - name: Install dependencies run: uv sync --all-groups - - name: Run pre-commit - run: uv run pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage push + - name: Run prek + run: uv run prek run --show-diff-on-failure --color=always --all-files --hook-stage pre-push pytest: name: Tests ${{ matrix.os }} / py${{ matrix.python }} - needs: pre-commit + needs: prek runs-on: ${{ matrix.os }} permissions: contents: read @@ -84,7 +84,7 @@ jobs: benchmarks: name: Benchmarks - needs: pre-commit + needs: prek runs-on: ubuntu-latest continue-on-error: true permissions: diff --git a/README.md b/README.md index 9e12b83..6f0c468 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,10 @@ cd plexosdb uv sync --all-groups ``` -Install the pre-commit hooks so your code is checked before making commits: +Install the git hooks so your code is checked before making commits: ```console -uv run pre-commit install +uv run prek install ``` Run the test suite: diff --git a/pyproject.toml b/pyproject.toml index 0e0b8f6..257df7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ docs = [ dev = [ "ipython>=9.2.0", "mypy>=1.15.0", - "pre-commit>=4.2.0", + "prek>=0.3.3", "pytest>=8.3.5", "pytest-benchmark>=5.1.0", "pytest-coverage>=0.0", diff --git a/uv.lock b/uv.lock index 01bcc3c..68e846c 100644 --- a/uv.lock +++ b/uv.lock @@ -77,15 +77,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/f3/80a3f974c8b535d394ff960a11ac20368e06b736da395b551a49ce950cce/certifi-2025.7.9-py3-none-any.whl", hash = "sha256:d842783a14f8fdd646895ac26f719a061408834473cfc10203f6a575beb15d39", size = 159230, upload-time = "2025-07-09T02:13:57.007Z" }, ] -[[package]] -name = "cfgv" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, -] - [[package]] name = "charset-normalizer" version = "3.4.2" @@ -232,15 +223,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] -[[package]] -name = "distlib" -version = "0.3.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, -] - [[package]] name = "docstr-coverage" version = "2.3.2" @@ -273,15 +255,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, ] -[[package]] -name = "filelock" -version = "3.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, -] - [[package]] name = "furo" version = "2025.9.25" @@ -319,15 +292,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "identify" -version = "2.6.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, -] - [[package]] name = "idna" version = "3.10" @@ -647,15 +611,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, ] -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - [[package]] name = "packaging" version = "25.0" @@ -695,15 +650,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, -] - [[package]] name = "plexosdb" version = "1.3.2" @@ -716,7 +662,7 @@ dependencies = [ dev = [ { name = "ipython" }, { name = "mypy" }, - { name = "pre-commit" }, + { name = "prek" }, { name = "pytest" }, { name = "pytest-benchmark" }, { name = "pytest-coverage" }, @@ -743,7 +689,7 @@ requires-dist = [{ name = "loguru" }] dev = [ { name = "ipython", specifier = ">=9.2.0" }, { name = "mypy", specifier = ">=1.15.0" }, - { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "prek", specifier = ">=0.3.3" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-benchmark", specifier = ">=5.1.0" }, { name = "pytest-coverage", specifier = ">=0.0" }, @@ -773,19 +719,27 @@ wheels = [ ] [[package]] -name = "pre-commit" -version = "4.5.0" +name = "prek" +version = "0.3.5" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/d6/277e002e56eeab3a9d48f1ca4cc067d249d6326fc1783b770d70ad5ae2be/prek-0.3.5.tar.gz", hash = "sha256:ca40b6685a4192256bc807f32237af94bf9b8799c0d708b98735738250685642", size = 374806, upload-time = "2026-03-09T10:35:18.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a9/16dd8d3a50362ebccffe58518af1f1f571c96f0695d7fcd8bbd386585f58/prek-0.3.5-py3-none-linux_armv6l.whl", hash = "sha256:44b3e12791805804f286d103682b42a84e0f98a2687faa37045e9d3375d3d73d", size = 5105604, upload-time = "2026-03-09T10:35:00.332Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/bc6036f5bf03860cda66ab040b32737e54802b71a81ec381839deb25df9e/prek-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3cb451cc51ac068974557491beb4c7d2d41dfde29ed559c1694c8ce23bf53e8", size = 5506155, upload-time = "2026-03-09T10:35:17.64Z" }, + { url = "https://files.pythonhosted.org/packages/02/d9/a3745c2a10509c63b6a118ada766614dd705efefd08f275804d5c807aa4a/prek-0.3.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ad8f5f0d8da53dc94d00b76979af312b3dacccc9dcbc6417756c5dca3633c052", size = 5100383, upload-time = "2026-03-09T10:35:13.302Z" }, + { url = "https://files.pythonhosted.org/packages/43/8e/de965fc515d39309a332789cd3778161f7bc80cde15070bedf17f9f8cb93/prek-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4511e15d34072851ac88e4b2006868fbe13655059ad941d7a0ff9ee17138fd9f", size = 5334913, upload-time = "2026-03-09T10:35:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/44f07e8940256059cfd82520e3cbe0764ab06ddb4aa43148465db00b39ad/prek-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc0b63b8337e2046f51267facaac63ba755bc14aad53991840a5eccba3e5c28", size = 5033825, upload-time = "2026-03-09T10:35:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/94/85/3ff0f96881ff2360c212d310ff23c3cf5a15b223d34fcfa8cdcef203be69/prek-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5fc0d78c3896a674aeb8247a83bbda7efec85274dbdfbc978ceff8d37e4ed20", size = 5438586, upload-time = "2026-03-09T10:34:58.779Z" }, + { url = "https://files.pythonhosted.org/packages/79/a5/c6d08d31293400fcb5d427f8e7e6bacfc959988e868ad3a9d97b4d87c4b7/prek-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64cad21cb9072d985179495b77b312f6b81e7b45357d0c68dc1de66e0408eabc", size = 6359714, upload-time = "2026-03-09T10:34:57.454Z" }, + { url = "https://files.pythonhosted.org/packages/ba/18/321dcff9ece8065d42c8c1c7a53a23b45d2b4330aa70993be75dc5f2822f/prek-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45ee84199bb48e013bdfde0c84352c17a44cc42d5792681b86d94e9474aab6f8", size = 5717632, upload-time = "2026-03-09T10:35:08.634Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7f/1288226aa381d0cea403157f4e6b64b356e1a745f2441c31dd9d8a1d63da/prek-0.3.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f43275e5d564e18e52133129ebeb5cb071af7ce4a547766c7f025aa0955dfbb6", size = 5339040, upload-time = "2026-03-09T10:35:03.665Z" }, + { url = "https://files.pythonhosted.org/packages/22/94/cfec83df9c2b8e7ed1608087bcf9538a6a77b4c2e7365123e9e0a3162cd1/prek-0.3.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:abcee520d31522bcbad9311f21326b447694cd5edba33618c25fd023fc9865ec", size = 5162586, upload-time = "2026-03-09T10:35:11.564Z" }, + { url = "https://files.pythonhosted.org/packages/13/b7/741d62132f37a5f7cc0fad1168bd31f20dea9628f482f077f569547e0436/prek-0.3.5-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:499c56a94a155790c75a973d351a33f8065579d9094c93f6d451ada5d1e469be", size = 5002933, upload-time = "2026-03-09T10:35:16.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/83/630a5671df6550fcfa67c54955e8a8174eb9b4d97ac38fb05a362029245b/prek-0.3.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:de1065b59f194624adc9dea269d4ff6b50e98a1b5bb662374a9adaa496b3c1eb", size = 5304934, upload-time = "2026-03-09T10:35:09.975Z" }, + { url = "https://files.pythonhosted.org/packages/de/79/67a7afd0c0b6c436630b7dba6e586a42d21d5d6e5778fbd9eba7bbd3dd26/prek-0.3.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a1c4869e45ee341735d07179da3a79fa2afb5959cef8b3c8a71906eb52dc6933", size = 5829914, upload-time = "2026-03-09T10:35:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/37/47/e2fe13b33e7b5fdd9dd1a312f5440208bfe1be6183e54c5c99c10f27d848/prek-0.3.5-py3-none-win32.whl", hash = "sha256:70b2152ecedc58f3f4f69adc884617b0cf44259f7414c44d6268ea6f107672eb", size = 4836910, upload-time = "2026-03-09T10:35:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ab/dc2a139fd4896d11f39631479ed385e86307af7f54059ebe9414bb0d00c6/prek-0.3.5-py3-none-win_amd64.whl", hash = "sha256:01d031b684f7e1546225393af1268d9b4451a44ef6cb9be4101c85c7862e08db", size = 5234234, upload-time = "2026-03-09T10:35:20.193Z" }, + { url = "https://files.pythonhosted.org/packages/ed/38/f7256b4b7581444f658e909c3b566f51bfabe56c03e80d107a6932d62040/prek-0.3.5-py3-none-win_arm64.whl", hash = "sha256:aa774168e3d868039ff79422bdef2df8d5a016ed804a9914607dcdd3d41da053", size = 5083330, upload-time = "2026-03-09T10:34:55.469Z" }, ] [[package]] @@ -1360,20 +1314,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] -[[package]] -name = "virtualenv" -version = "20.31.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, -] - [[package]] name = "watchfiles" version = "1.1.0"