feat: Initial SDK — 117 endpoints across 18 mixins #2

Merged
forgejo_admin merged 3 commits from 1-initial-sdk-generate-from-swagger-json-f into main 2026-03-01 04:45:30 +00:00
Contributor

Summary

  • Complete Python SDK wrapping the Woodpecker CI REST API (117 endpoints, 18 mixin files)
  • Generated from swagger.json, following the forgejo-sdk mixin pattern exactly
  • Bearer token auth via httpx, 33 integration tests passing

Changes

  • src/woodpecker_sdk/client.py: _BaseClient with Bearer token auth + dual httpx clients (api + root), WoodpeckerClient composing all 18 mixins
  • src/woodpecker_sdk/*.py: 18 mixin files — agents, badges, cron_jobs, debug, events, forges, organizations, pipeline_logs, pipeline_queues, pipelines, registries, repo_registries, repo_secrets, repositories, secrets, system, user, users
  • tests/conftest.py: Session-scoped live client fixture loading creds from ~/secrets/woodpecker/credentials.env
  • tests/test_*.py: 18 test files, 33 tests passing, 2 skipped (SSE streaming + missing test data)
  • pyproject.toml: ldraney-woodpecker-sdk v0.1.0, hatchling build, httpx dep
  • swagger.json: Source spec (117 endpoints, 22 tags)

Test Plan

  • pytest tests/ passes against live Woodpecker instance (33 passed, 2 skipped)
  • pip install -e . works
  • Review endpoint coverage against swagger spec
  • Follow-up issue for comprehensive response validation tests: issue-woodpecker-sdk-integration-tests

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • issue-woodpecker-sdk-initial — the issue this PR addresses
  • plan-2026-02-28-woodpecker-sdk-mcp — Phase 1
## Summary - Complete Python SDK wrapping the Woodpecker CI REST API (117 endpoints, 18 mixin files) - Generated from swagger.json, following the forgejo-sdk mixin pattern exactly - Bearer token auth via httpx, 33 integration tests passing ## Changes - `src/woodpecker_sdk/client.py`: `_BaseClient` with Bearer token auth + dual httpx clients (api + root), `WoodpeckerClient` composing all 18 mixins - `src/woodpecker_sdk/*.py`: 18 mixin files — agents, badges, cron_jobs, debug, events, forges, organizations, pipeline_logs, pipeline_queues, pipelines, registries, repo_registries, repo_secrets, repositories, secrets, system, user, users - `tests/conftest.py`: Session-scoped live client fixture loading creds from `~/secrets/woodpecker/credentials.env` - `tests/test_*.py`: 18 test files, 33 tests passing, 2 skipped (SSE streaming + missing test data) - `pyproject.toml`: `ldraney-woodpecker-sdk` v0.1.0, hatchling build, httpx dep - `swagger.json`: Source spec (117 endpoints, 22 tags) ## Test Plan - [x] `pytest tests/` passes against live Woodpecker instance (33 passed, 2 skipped) - [x] `pip install -e .` works - [ ] Review endpoint coverage against swagger spec - [ ] Follow-up issue for comprehensive response validation tests: `issue-woodpecker-sdk-integration-tests` ## Review Checklist - [ ] Passed automated review-fix loop - [ ] No secrets committed - [ ] No unnecessary file changes - [ ] Commit messages are descriptive ## Related Notes - `issue-woodpecker-sdk-initial` — the issue this PR addresses - `plan-2026-02-28-woodpecker-sdk-mcp` — Phase 1
Complete Python SDK wrapping the Woodpecker CI REST API, generated from
swagger.json (22 tags, 117 endpoints). Follows the forgejo-sdk pattern:
_BaseClient with Bearer token auth via httpx, mixin-per-tag composition,
and WoodpeckerClient combining all mixins.

Key differences from forgejo-sdk:
- Bearer token auth (PAT) instead of basic auth
- Base path /api with root-level fallback for /version and /healthz
- Separate root client for endpoints not under /api

Includes 18 test files with 33 passing integration tests against live
Woodpecker instance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR #2 Review: Woodpecker CI Python SDK (Phase 1)

Reviewed all 20 source files, 21 test files, pyproject.toml, and cross-referenced against all 117 swagger.json endpoints.


Blockers (must fix before merge)

B1. debug.py -- 4 methods accept query parameters but silently drop them

The following methods accept keyword arguments from the caller but never forward them to the HTTP request. This is a correctness bug -- callers think they're configuring the request but the params are silently ignored.

  • pprof_goroutine(*, debug: int | None = None) -- debug param never passed
  • pprof_heap(*, gc: str | None = None) -- gc param never passed
  • pprof_profile(*, seconds: int) -- seconds param never passed (this one is required in the signature but still dropped!)
  • pprof_trace(*, seconds: int) -- seconds param never passed (same -- required but dropped)

Fix: each should pass params={"debug": debug}, params={"gc": gc}, params={"seconds": seconds} respectively to self.get_bytes().

pprof_profile and pprof_trace are the worst offenders because seconds is a required positional keyword -- the caller must provide it, but it gets thrown away.


Nits (nice to fix, not blocking)

N1. badges.py imports Any from typing but never uses it

The BadgesMixin methods return str, not Any. The import is dead code.

N2. Duplicate first_repo fixture defined in 6 test files

test_badges.py, test_cron_jobs.py, test_pipelines.py, test_repo_registries.py, test_repo_secrets.py, test_repositories.py each define their own identical first_repo fixture. This should live once in conftest.py.

N3. test_agents.py uses bare return instead of pytest.skip() when no data is available

Lines 12 and 22 do return # No agents to test which silently passes the test. Using pytest.skip("No agents to test") would make the test report more honest about what was actually validated.

N4. from __future__ import annotations is unnecessary

pyproject.toml requires python >= 3.10, where PEP 604 syntax (str | None) is native. The future import is harmless but adds noise to every file.

N5. Test coverage is thin: 33/117 SDK methods are exercised (28%)

84 out of 117 public methods have zero test coverage. This is understandable for a generated SDK with integration tests (CUD operations are destructive), but worth noting. The read-only endpoints that ARE tested are sensible choices.

N6. Some tests would pass regardless of response shape

test_registries.py and test_secrets.py each have a single test that only checks isinstance(result, list). These are extremely weak assertions -- any list response (including an error envelope that happens to be a list) would pass.

N7. org_id type inconsistency across mixins (matches swagger, still confusing)

In agents.py, org-scoped agent methods use org_id: int. In organizations.py, all methods use org_id: str. This matches the swagger spec (which itself is inconsistent), but it creates a confusing DX where the same conceptual parameter has different types depending on which method you call.


Positive Observations

  1. Full endpoint coverage: 117/117 -- Every single swagger endpoint is mapped to an SDK method. Verified by automated cross-reference of HTTP method + normalized path.

  2. Clean mixin architecture -- The 18-mixin + _BaseClient composition pattern is well-organized. Each mixin maps to a logical API domain. The MRO ordering in WoodpeckerClient is correct.

  3. _BaseClient is solid -- Bearer auth via headers, raise_for_status() on every request, _clean_params strips None values, context manager support, separate _root_client for /healthz and /version. The dual-client approach for root vs /api paths is a pragmatic and correct solution.

  4. Correct HTTP methods throughout -- Every endpoint uses the right HTTP verb (GET/POST/PATCH/DELETE) matching the swagger spec. No PUT/POST confusion.

  5. 204 handling -- post(), put(), patch(), and delete() all correctly handle 204 No Content responses by returning None instead of trying to parse empty JSON.

  6. Security -- No hardcoded secrets anywhere. Credentials loaded from env vars or ~/secrets/woodpecker/credentials.env in conftest. Token is optional (some endpoints like healthz don't need it).

  7. pyproject.toml is correct -- hatchling build, proper package path src/woodpecker_sdk, sensible deps (httpx>=0.27), dev deps for testing.

  8. Integration tests are pragmatic -- Tests that hit destructive endpoints (create/delete) are intentionally omitted. SSE endpoints are correctly skipped with a reason. Debug/pprof tests gracefully handle servers without debug mode enabled.


Endpoint Coverage Summary

Category Endpoints Covered
Swagger spec total 117 117/117 (100%)
Test coverage (methods called) 117 33/117 (28%)

Verdict

One blocker (debug.py dropped params), several minor nits. The core architecture, auth model, HTTP method correctness, and endpoint coverage are all solid. Fix the debug.py param forwarding and this is good to merge.

## PR #2 Review: Woodpecker CI Python SDK (Phase 1) Reviewed all 20 source files, 21 test files, pyproject.toml, and cross-referenced against all 117 swagger.json endpoints. --- ### Blockers (must fix before merge) **B1. `debug.py` -- 4 methods accept query parameters but silently drop them** The following methods accept keyword arguments from the caller but never forward them to the HTTP request. This is a correctness bug -- callers think they're configuring the request but the params are silently ignored. - `pprof_goroutine(*, debug: int | None = None)` -- `debug` param never passed - `pprof_heap(*, gc: str | None = None)` -- `gc` param never passed - `pprof_profile(*, seconds: int)` -- `seconds` param never passed (this one is **required** in the signature but still dropped!) - `pprof_trace(*, seconds: int)` -- `seconds` param never passed (same -- required but dropped) Fix: each should pass `params={"debug": debug}`, `params={"gc": gc}`, `params={"seconds": seconds}` respectively to `self.get_bytes()`. `pprof_profile` and `pprof_trace` are the worst offenders because `seconds` is a **required positional keyword** -- the caller *must* provide it, but it gets thrown away. --- ### Nits (nice to fix, not blocking) **N1. `badges.py` imports `Any` from `typing` but never uses it** The `BadgesMixin` methods return `str`, not `Any`. The import is dead code. **N2. Duplicate `first_repo` fixture defined in 6 test files** `test_badges.py`, `test_cron_jobs.py`, `test_pipelines.py`, `test_repo_registries.py`, `test_repo_secrets.py`, `test_repositories.py` each define their own identical `first_repo` fixture. This should live once in `conftest.py`. **N3. `test_agents.py` uses bare `return` instead of `pytest.skip()` when no data is available** Lines 12 and 22 do `return # No agents to test` which silently passes the test. Using `pytest.skip("No agents to test")` would make the test report more honest about what was actually validated. **N4. `from __future__ import annotations` is unnecessary** `pyproject.toml` requires `python >= 3.10`, where PEP 604 syntax (`str | None`) is native. The future import is harmless but adds noise to every file. **N5. Test coverage is thin: 33/117 SDK methods are exercised (28%)** 84 out of 117 public methods have zero test coverage. This is understandable for a generated SDK with integration tests (CUD operations are destructive), but worth noting. The read-only endpoints that ARE tested are sensible choices. **N6. Some tests would pass regardless of response shape** `test_registries.py` and `test_secrets.py` each have a single test that only checks `isinstance(result, list)`. These are extremely weak assertions -- any list response (including an error envelope that happens to be a list) would pass. **N7. `org_id` type inconsistency across mixins (matches swagger, still confusing)** In `agents.py`, org-scoped agent methods use `org_id: int`. In `organizations.py`, all methods use `org_id: str`. This matches the swagger spec (which itself is inconsistent), but it creates a confusing DX where the same conceptual parameter has different types depending on which method you call. --- ### Positive Observations 1. **Full endpoint coverage: 117/117** -- Every single swagger endpoint is mapped to an SDK method. Verified by automated cross-reference of HTTP method + normalized path. 2. **Clean mixin architecture** -- The 18-mixin + `_BaseClient` composition pattern is well-organized. Each mixin maps to a logical API domain. The MRO ordering in `WoodpeckerClient` is correct. 3. **`_BaseClient` is solid** -- Bearer auth via headers, `raise_for_status()` on every request, `_clean_params` strips `None` values, context manager support, separate `_root_client` for `/healthz` and `/version`. The dual-client approach for root vs `/api` paths is a pragmatic and correct solution. 4. **Correct HTTP methods throughout** -- Every endpoint uses the right HTTP verb (GET/POST/PATCH/DELETE) matching the swagger spec. No PUT/POST confusion. 5. **204 handling** -- `post()`, `put()`, `patch()`, and `delete()` all correctly handle 204 No Content responses by returning `None` instead of trying to parse empty JSON. 6. **Security** -- No hardcoded secrets anywhere. Credentials loaded from env vars or `~/secrets/woodpecker/credentials.env` in conftest. Token is optional (some endpoints like healthz don't need it). 7. **`pyproject.toml` is correct** -- hatchling build, proper package path `src/woodpecker_sdk`, sensible deps (`httpx>=0.27`), dev deps for testing. 8. **Integration tests are pragmatic** -- Tests that hit destructive endpoints (create/delete) are intentionally omitted. SSE endpoints are correctly skipped with a reason. Debug/pprof tests gracefully handle servers without debug mode enabled. --- ### Endpoint Coverage Summary | Category | Endpoints | Covered | |---|---|---| | Swagger spec total | 117 | 117/117 (100%) | | Test coverage (methods called) | 117 | 33/117 (28%) | --- ### Verdict **One blocker** (debug.py dropped params), several minor nits. The core architecture, auth model, HTTP method correctness, and endpoint coverage are all solid. Fix the debug.py param forwarding and this is good to merge.
B1: Forward query parameters in pprof_goroutine (debug), pprof_heap (gc),
pprof_profile (seconds), and pprof_trace (seconds) to the underlying
get_bytes() calls instead of silently dropping them.

N1: Remove unused `Any` import from badges.py.
N2: Deduplicate `first_repo` fixture into tests/conftest.py (was copy-pasted
in 6 test files).
N3: Replace bare `return` with `pytest.skip()` in test_agents.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
forgejo_admin deleted branch 1-initial-sdk-generate-from-swagger-json-f 2026-03-01 04:45:31 +00:00
Sign in to join this conversation.
No description provided.