From 00e3465057f19b3483948c35d627538cfa863ef3 Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:43:25 -0400 Subject: [PATCH 1/7] ci: Fix testpypi CD. (#5) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11e06aa..658d82a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -164,7 +164,7 @@ jobs: - uses: astral-sh/setup-uv@v8.2.0 - name: Publish to TestPyPI - run: uv publish --index-url https://test.pypi.org/legacy/ + run: uv publish --index testpypi publish-pypi: name: Publish to PyPI From 0598a34665323d178fdf8c118bc44055b420a408 Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:02:47 -0400 Subject: [PATCH 2/7] Ci GitHub app (#6) * ci: Fix testpypi CD. * ci: Switch to GitHub App. --- .github/CODEOWNERS | 3 +++ .github/workflows/release.yml | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..51a1d77 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Default owner for all files +* @jjeff07 + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 658d82a..7fe2197 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,17 @@ jobs: version: ${{ steps.bump.outputs.version }} steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - uses: actions/checkout@v6 with: fetch-depth: 0 - token: ${{ secrets.RELEASE_TOKEN }} + token: ${{ steps.app-token.outputs.token }} - uses: astral-sh/setup-uv@v8.2.0 with: From 892c1e0cb58a5849ca9b7600ffc8ba1a649b5729 Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:07:37 -0400 Subject: [PATCH 3/7] chore: Update issue templates (#7) --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 8b76746b6fd47c0841bee46ce30385d4137eed2f Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:11:59 -0400 Subject: [PATCH 4/7] Potential fix for code scanning alert no. 5: Workflow does not contain permissions (#8) ci: Potential fix for code scanning alert no. 5: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2f9340..e982925 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main, dev] +permissions: + contents: read + jobs: lint: name: Lint & Format From e49000d6c8e2838d06293c475b72a967a8096240 Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:16:14 -0400 Subject: [PATCH 5/7] ci: Add dry-run for testing release. (#9) --- .github/workflows/release.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7fe2197..43995c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,11 @@ on: - patch - minor - major + dry_run: + description: "Dry run (build only, no publish/push)" + required: false + type: boolean + default: false permissions: contents: write @@ -125,8 +130,12 @@ jobs: name: dist path: dist/ + - name: Show changelog diff + run: git diff CHANGELOG.md + # --- Commit & Tag --- - name: Commit version bump and changelog + if: ${{ inputs.dry_run == false }} run: | git add pyproject.toml uv.lock CHANGELOG.md git commit -m "release: v${{ steps.bump.outputs.version }}" @@ -135,6 +144,7 @@ jobs: # --- GitHub Release --- - name: Create GitHub Release + if: ${{ inputs.dry_run == false }} uses: softprops/action-gh-release@v3 with: tag_name: v${{ steps.bump.outputs.version }} @@ -147,6 +157,7 @@ jobs: # --- Sync dev branch --- - name: Fast-forward dev to main + if: ${{ inputs.dry_run == false }} run: | git fetch origin dev git checkout dev @@ -156,6 +167,7 @@ jobs: publish-testpypi: name: Publish to TestPyPI needs: release + if: ${{ inputs.dry_run == false }} runs-on: ubuntu-latest continue-on-error: true environment: @@ -176,6 +188,7 @@ jobs: publish-pypi: name: Publish to PyPI needs: release + if: ${{ inputs.dry_run == false }} runs-on: ubuntu-latest environment: name: pypi From 9dc7761df32be26cd7575ce774ffd2e9a3c038a1 Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:17:18 -0400 Subject: [PATCH 6/7] ci: Further CI/CD Enhancements. (#13) * ci: Further CI/CD Enhancements. - Autorelease on merge to main. - Dependabot added. - Updated issue templates. - Added Security Policy. - Add contribution information. * ci: Test Python 3.14. --- .github/ISSUE_TEMPLATE/bug_report.md | 37 +++-- .github/ISSUE_TEMPLATE/feature_request.md | 8 +- .github/dependabot.yml | 21 +++ .github/workflows/ci.yml | 54 ++++++- .github/workflows/release.yml | 168 +++++++++++++++++++--- CONTRIBUTING.md | 59 ++++++++ README.md | 21 +++ SECURITY.md | 29 ++++ 8 files changed, 354 insertions(+), 43 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..ce62415 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,8 @@ --- name: Bug report -about: Create a report to help us improve +about: Report an issue with libreclient title: '' -labels: '' +labels: 'bug' assignees: '' --- @@ -11,28 +11,27 @@ assignees: '' A clear and concise description of what the bug is. **To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error +```python +# Minimal code to reproduce the issue +from libreclient import LibreClientSync + +client = LibreClientSync(url="...", token="...") +# ... +``` **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] +**Error output** +``` +Paste full traceback or error message here +``` -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] +**Environment:** +- Python version: [e.g. 3.13] +- libreclient version: [e.g. 0.2.0] +- LibreNMS version: [e.g. 24.12.0] +- OS: [e.g. Ubuntu 24.04] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..1eed0e7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- name: Feature request -about: Suggest an idea for this project +about: Suggest an idea for libreclient title: '' -labels: '' +labels: 'enhancement' assignees: '' --- @@ -13,6 +13,10 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate **Describe the solution you'd like** A clear and concise description of what you want to happen. +**API endpoint(s) involved** +If this relates to a specific LibreNMS API endpoint, link to the relevant docs: +- e.g. https://docs.librenms.org/API/Devices/#get-device + **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..41af3cb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 + +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: ["*"] + + # Python (uv) + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + groups: + python-deps: + patterns: ["*"] + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e982925..91039ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main, dev] +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -13,8 +17,14 @@ jobs: lint: name: Lint & Format runs-on: ubuntu-latest + if: >- + !startsWith(github.event.head_commit.message || '', 'release:') + permissions: + contents: write steps: - uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref || github.ref }} - uses: astral-sh/setup-uv@v8.2.0 with: @@ -23,15 +33,29 @@ jobs: - name: Install dependencies run: uv sync - - name: Ruff check - run: uvx ruff check + - name: Ruff fix & format + if: github.event_name == 'pull_request' + run: | + uvx ruff check --fix + uvx ruff format - - name: Ruff format check - run: uvx ruff format --check + - name: Commit fixes + if: github.event_name == 'pull_request' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "ci(lint): auto-fix lint and format [skip ci]" + git push + + - name: Ruff check (fail on remaining issues) + run: uvx ruff check --output-format github complexity: name: Complexity Check runs-on: ubuntu-latest + if: >- + !startsWith(github.event.head_commit.message || '', 'release:') steps: - uses: actions/checkout@v6 @@ -48,9 +72,11 @@ jobs: test: name: Tests runs-on: ubuntu-latest + if: >- + !startsWith(github.event.head_commit.message || '', 'release:') strategy: matrix: - python-version: ["3.12", "3.13"] + python-version: ["3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v6 @@ -63,3 +89,21 @@ jobs: - name: Run tests run: uv run pytest --tb=short + + # Gate job — add "ci-pass" as a required status check in your ruleset + ci-pass: + name: CI Pass + if: always() + needs: [lint, complexity, test] + runs-on: ubuntu-latest + steps: + - name: Check all jobs passed + run: | + if [ "${{ needs.lint.result }}" = "failure" ] || \ + [ "${{ needs.complexity.result }}" = "failure" ] || \ + [ "${{ needs.test.result }}" = "failure" ]; then + echo "One or more CI jobs failed." + exit 1 + fi + echo "All CI jobs passed." + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43995c9..1ae31af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,16 @@ name: Release on: + push: + branches: [main] workflow_dispatch: inputs: bump: - description: "Version bump type" - required: true + description: "Version bump override (leave empty to auto-detect)" + required: false type: choice options: + - "" - patch - minor - major @@ -15,20 +18,39 @@ on: description: "Dry run (build only, no publish/push)" required: false type: boolean - default: false + default: true permissions: contents: write id-token: write + pull-requests: read + +concurrency: + group: release + cancel-in-progress: false jobs: release: name: Bump, Changelog, Build & Tag runs-on: ubuntu-latest + # Skip release commits pushed by the bot to prevent loops + if: >- + github.event_name == 'workflow_dispatch' || + !startsWith(github.event.head_commit.message || '', 'release:') outputs: version: ${{ steps.bump.outputs.version }} + skipped: ${{ steps.decide.outputs.skip }} + dry_run: ${{ steps.set-dry-run.outputs.dry_run }} steps: + - name: Resolve dry_run flag + id: set-dry-run + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "dry_run=${{ inputs.dry_run }}" >> "$GITHUB_OUTPUT" + else + echo "dry_run=false" >> "$GITHUB_OUTPUT" + fi - name: Generate app token id: app-token uses: actions/create-github-app-token@v3 @@ -53,20 +75,123 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + # --- Determine Bump Type --- + - name: Determine version bump + id: decide + uses: actions/github-script@v9 + with: + script: | + // Manual override takes priority + const manualBump = '${{ inputs.bump }}'; + if (manualBump) { + core.setOutput('bump', manualBump); + core.setOutput('skip', 'false'); + core.info(`Manual override: ${manualBump}`); + return; + } + + // Check PR labels for override + let labels = []; + if (context.eventName === 'push') { + const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha, + }); + if (prs.length > 0) { + labels = prs[0].labels.map(l => l.name); + } + } + + // Label override + if (labels.includes('bump:major')) { + core.setOutput('bump', 'major'); + core.setOutput('skip', 'false'); + core.info('Label override: major'); + return; + } + if (labels.includes('bump:minor')) { + core.setOutput('bump', 'minor'); + core.setOutput('skip', 'false'); + core.info('Label override: minor'); + return; + } + if (labels.includes('bump:patch')) { + core.setOutput('bump', 'patch'); + core.setOutput('skip', 'false'); + core.info('Label override: patch'); + return; + } + if (labels.includes('no-release')) { + core.setOutput('skip', 'true'); + core.info('Skipping release: no-release label'); + return; + } + + // Auto-detect from conventional commits since last tag + const { execSync } = require('child_process'); + let commits; + try { + const prevTag = execSync('git describe --tags --abbrev=0 2>/dev/null', { encoding: 'utf8' }).trim(); + commits = execSync(`git log ${prevTag}..HEAD --pretty=format:"%s" --no-merges`, { encoding: 'utf8' }); + } catch { + commits = execSync('git log --pretty=format:"%s" --no-merges', { encoding: 'utf8' }); + } + + const lines = commits.split('\n').filter(Boolean); + + if (lines.length === 0) { + core.setOutput('skip', 'true'); + core.info('No commits since last tag'); + return; + } + + let bump = 'skip'; + for (const msg of lines) { + if (msg.includes('BREAKING CHANGE') || /^[a-z]+(\(.+\))?!:/.test(msg)) { + bump = 'major'; + break; + } + if (/^feat(\(.+\))?:/.test(msg)) { + bump = 'minor'; + } + if (bump === 'skip' && /^(fix|perf)(\(.+\))?:/.test(msg)) { + bump = 'patch'; + } + } + + if (bump === 'skip') { + core.setOutput('skip', 'true'); + core.info('No releasable commits (chore/docs/ci only)'); + return; + } + + core.setOutput('bump', bump); + core.setOutput('skip', 'false'); + core.info(`Auto-detected bump: ${bump}`); + + # --- Stop here if no release needed --- + - name: Skip if no release + if: steps.decide.outputs.skip == 'true' + run: echo "No releasable changes — skipping release." + # --- Version Bump --- - name: Get current version + if: steps.decide.outputs.skip != 'true' id: current run: echo "version=$(uv version --short)" >> "$GITHUB_OUTPUT" - name: Bump version + if: steps.decide.outputs.skip != 'true' id: bump run: | - uv version --bump ${{ inputs.bump }} + uv version --bump ${{ steps.decide.outputs.bump }} echo "version=$(uv version --short)" >> "$GITHUB_OUTPUT" echo "Bumped version: ${{ steps.current.outputs.version }} → $(uv version --short)" # --- Changelog --- - name: Generate changelog entry + if: steps.decide.outputs.skip != 'true' run: | NEW_VERSION="${{ steps.bump.outputs.version }}" DATE=$(date +%Y-%m-%d) @@ -74,9 +199,9 @@ jobs: # Get commit messages since last tag (or all if no tags) if [ -n "$PREV_TAG" ]; then - COMMITS=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s" --no-merges) + COMMITS=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s" --no-merges | grep -v "^- release:") else - COMMITS=$(git log --pretty=format:"- %s" --no-merges) + COMMITS=$(git log --pretty=format:"- %s" --no-merges | grep -v "^- release:") fi python3 - "$NEW_VERSION" "$DATE" "$PREV_TAG" "$COMMITS" <<'EOF' @@ -117,47 +242,56 @@ jobs: # --- Generate Type Stubs --- - name: Generate .pyi stubs + if: steps.decide.outputs.skip != 'true' run: uv run python generate_stubs.py # --- Build --- - name: Build package + if: steps.decide.outputs.skip != 'true' run: uv build # --- Upload dist for publish jobs --- - name: Upload dist artifacts + if: steps.decide.outputs.skip != 'true' uses: actions/upload-artifact@v7 with: name: dist path: dist/ - name: Show changelog diff + if: steps.decide.outputs.skip != 'true' run: git diff CHANGELOG.md # --- Commit & Tag --- - name: Commit version bump and changelog - if: ${{ inputs.dry_run == false }} + if: steps.decide.outputs.skip != 'true' && steps.set-dry-run.outputs.dry_run != 'true' + id: commit run: | git add pyproject.toml uv.lock CHANGELOG.md - git commit -m "release: v${{ steps.bump.outputs.version }}" + git commit -m "release: v${{ steps.bump.outputs.version }} [skip ci]" git tag "v${{ steps.bump.outputs.version }}" git push origin HEAD --tags + # Extract the changelog entry for this version + VERSION="${{ steps.bump.outputs.version }}" + BODY=$(sed -n "/^## \[${VERSION}\]/,/^## \[/{ /^## \[${VERSION}\]/p; /^## \[${VERSION}\]/!{ /^## \[/!p; } }" CHANGELOG.md) + # Write to file to avoid escaping issues + echo "$BODY" > /tmp/release-body.md + # --- GitHub Release --- - name: Create GitHub Release - if: ${{ inputs.dry_run == false }} + if: steps.decide.outputs.skip != 'true' && steps.set-dry-run.outputs.dry_run != 'true' uses: softprops/action-gh-release@v3 with: tag_name: v${{ steps.bump.outputs.version }} name: v${{ steps.bump.outputs.version }} - body: | - ## Changes - - See [CHANGELOG.md](https://github.com/jjeff07/libreclient/blob/main/CHANGELOG.md) for details. + body_path: /tmp/release-body.md files: dist/* # --- Sync dev branch --- - name: Fast-forward dev to main - if: ${{ inputs.dry_run == false }} + if: steps.decide.outputs.skip != 'true' && steps.set-dry-run.outputs.dry_run != 'true' + continue-on-error: true run: | git fetch origin dev git checkout dev @@ -167,7 +301,7 @@ jobs: publish-testpypi: name: Publish to TestPyPI needs: release - if: ${{ inputs.dry_run == false }} + if: needs.release.outputs.skipped != 'true' && needs.release.outputs.dry_run != 'true' runs-on: ubuntu-latest continue-on-error: true environment: @@ -188,14 +322,14 @@ jobs: publish-pypi: name: Publish to PyPI needs: release - if: ${{ inputs.dry_run == false }} + if: needs.release.outputs.skipped != 'true' && needs.release.outputs.dry_run != 'true' runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/project/libreclient/ steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: dist path: dist/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3f40db9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to libreclient + +Thanks for your interest in contributing! Here's how to get started. + +## Quick Start + +```bash +git clone https://github.com/jjeff07/libreclient.git +cd libreclient +uv sync +git config core.hooksPath .githooks +``` + +## Workflow + +1. **Fork** the repository +2. **Branch** from `dev` (not `main`) +3. **Make changes** — write code, add tests +4. **Commit** using [Conventional Commits](https://www.conventionalcommits.org/) (the hook will validate) +5. **Push** and open a PR against `dev` + +## What We're Looking For + +- **New routes** — Support for future API endpoints as LibreNMS evolves +- **Bug fixes** — Found something broken? Fix it and add a regression test +- **Tests** — Increase coverage, especially functional tests +- **Documentation** — Improve docstrings, README, or examples + +## Code Standards + +- All code is linted and formatted by [Ruff](https://docs.astral.sh/ruff/) (CI will auto-fix minor issues) +- Maximum cognitive complexity of 15 per function (enforced by [complexipy](https://github.com/rohaquinlop/complexipy)) +- Tests are required for new features and bug fixes +- Commit messages must follow Conventional Commits format + +## Adding a New Route + +1. Create `src/libreclient/routes/myroute.py` with an async class +2. Create `src/libreclient/models/myroute.py` with Pydantic response models +3. Add exports to `routes/__init__.py` and `models/__init__.py` +4. Wire up in `client.py` (both sync and async) +5. Run `uv run python generate_stubs.py` +6. Add unit tests in `tests/unit/routes/` and `tests/unit/models/` + +## Running Tests + +```bash +# Unit tests +uv run pytest tests/unit + +# Functional tests (needs .env with LIBRENMS_URL and LIBRENMS_TOKEN) +uv run pytest tests/functional +``` + +## Questions? + +Open an [issue](https://github.com/jjeff07/libreclient/issues) — happy to help you get started. + + diff --git a/README.md b/README.md index 463017d..045f170 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,27 @@ All route namespaces are accessible as properties on the client: --- +## Contributing + +Contributions are welcome! We're actively looking for contributors to help with: + +- 🐛 Bug fixes and edge case handling +- ✨ Support for new routes as LibreNMS adds API endpoints +- 📖 Documentation improvements +- 🧪 Test coverage expansion +- 🔧 Tooling and CI improvements + +**Getting started:** + +1. Fork the repo and create a branch from `dev` +2. Follow the [Development](#development) setup below +3. Make your changes with tests +4. Open a PR against `dev` — CI will lint, test, and auto-fix formatting + +See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines, or just open an issue to discuss your idea first. + +--- + ## Development ### Prerequisites diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f9007b9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,29 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|----------|-----------| +| latest | ✅ | +| < latest | ❌ | + +Only the latest released version receives security updates. + +## Reporting a Vulnerability + +**Please do not open a public issue for security vulnerabilities.** + +Instead, report vulnerabilities privately +via [GitHub Security Advisories](https://github.com/jjeff07/libreclient/security/advisories/new). + +You can expect: + +- Acknowledgement within **7 days** +- A fix or mitigation plan within **14 days** for confirmed issues +- Credit in the release notes (unless you prefer anonymity) + +## Scope + +This policy covers the `libreclient` Python package. Issues related to the LibreNMS API server itself should be reported +to the [LibreNMS project](https://github.com/librenms/librenms/security). + From 0a3a11b0335ed70685956f1d4a281871beeaf4fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:20:13 -0400 Subject: [PATCH 7/7] chore(deps): Bump niquests from 3.19.0 to 3.19.1 in the python-deps group (#15) chore(deps): Bump niquests in the python-deps group Bumps the python-deps group with 1 update: [niquests](https://github.com/jawah/niquests). Updates `niquests` from 3.19.0 to 3.19.1 - [Release notes](https://github.com/jawah/niquests/releases) - [Changelog](https://github.com/jawah/niquests/blob/main/HISTORY.md) - [Commits](https://github.com/jawah/niquests/compare/v3.19.0...v3.19.1) --- updated-dependencies: - dependency-name: niquests dependency-version: 3.19.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 95ad4f1..d75eea3 100644 --- a/uv.lock +++ b/uv.lock @@ -404,16 +404,16 @@ wheels = [ [[package]] name = "niquests" -version = "3.19.0" +version = "3.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "charset-normalizer" }, { name = "urllib3-future" }, { name = "wassima", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/c3/9c37961443c4f5e2bcf2c7b31ede2bbed7667f2d552c98f3eac7c3966c6b/niquests-3.19.0.tar.gz", hash = "sha256:e3c9fc8aa7c77c3d066dd6a0551d15eee1660ac14a497264577115c3296022ff", size = 1034890, upload-time = "2026-06-01T12:25:38.162Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/6a/b3b4d63a2d80a067dd87d6e01e071733df128b95d6806d2eb7852c35e4d2/niquests-3.19.1.tar.gz", hash = "sha256:2c34591744c7ade45f5f3a65a637cf1366d399eb514200005a8823de0d66b91e", size = 1035701, upload-time = "2026-06-08T07:53:15.754Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/78/3ae749f05c392d670d8381302d07e8b5c147bf6bbe20725f69a4adc02cb0/niquests-3.19.0-py3-none-any.whl", hash = "sha256:d75efeedda74be9718b2e3508d8c900bd411a3f554c57e3b4d24d9245fc37a1f", size = 210982, upload-time = "2026-06-01T12:25:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/27/498b676bf5e4824d84c6bae37bf948c164bcad0484b30851a935f165756b/niquests-3.19.1-py3-none-any.whl", hash = "sha256:ed04a8e2813f0b12f2ec82982c500fd115499b88d1abfa689fc9116ecdc68ede", size = 211349, upload-time = "2026-06-08T07:53:13.938Z" }, ] [[package]]