Skip to content

refactor!: generate the Client API models from the OpenAPI spec#2

Merged
jeroenrinzema merged 9 commits into
mainfrom
feat/spec-codegen
Jun 25, 2026
Merged

refactor!: generate the Client API models from the OpenAPI spec#2
jeroenrinzema merged 9 commits into
mainfrom
feat/spec-codegen

Conversation

@jeroenrinzema

@jeroenrinzema jeroenrinzema commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Migrates the Python SDK to the spec-driven pattern shared across the Lunogram SDKs (mirrors go-sdk/js-sdk): the model layer is generated from the platform's OpenAPI spec and a thin hand-written facade keeps the ergonomic, namespaced API.

This PR supersedes and folds in #1 (require project_id + move the Client API under /projects/{project_id}).

What's here

  • Spec-driven modelsspec/client.yaml is vendored from the platform v0.1.0-rc.1 release asset; src/lunogram/gen/models.py is generated from it with datamodel-code-generator (Pydantic v2) via make generate. Python is snake_case end-to-end, matching the spec, so models flow straight to the request body with no key-mapping layer. Every method accepts either a generated model or a plain dict.
  • Required project_idLunogram(api_key, project_id); the UUID is validated and injected into every path under /api/client/projects/{project_id}/....
  • Full Client API surface — beyond users/orgs + events + scheduled, the facade now covers all client endpoints:
    • user & organization inbox — create, query, count, mark_read, mark_archived
    • user device registration (client.user.devices.register)
    • project VAPID public key (client.push.get_vapid_public_key)
    • end-user session token minting (client.sessions.create)
    • organization membership (organization.add_user / remove_user)
  • Transport fixes surfaced by the wider surface:
    • send Authorization: Bearer <token> (was the bare key — would have 401'd)
    • treat an empty 2xx body (202/204) as success instead of reporting a JSON-decode error

Supersedes

Notes

  • Per request, no spec-sync bot workflow is included; bumping the spec is a manual edit to spec/SOURCE.md + make generate.

make generate (no drift), python -m build, and pytest (36 tests) all pass.

…t_id}

Every authenticated Lunogram Client API endpoint is now scoped to a project.
The project UUID is supplied once when constructing the client and is injected
into every Client API path as a `/projects/<project_id>/` segment; callers no
longer pass it per request.

BREAKING CHANGE: `Lunogram(api_key)` becomes `Lunogram(api_key, project_id)`.
Auth is unchanged (same API key / token); only the URL gains the project.

- client.py: add required `project_id`, build a project-scoped reference and
  thread it through `user`/`organization`.
- utils/reference.py: turn the path reference into a project-scoped factory
  (validates non-empty UUID); add inbox/devices/push/auth-method-session paths.
- app/objects.py and app/models/**: accept and use the injected reference.
- README: document construction with the project and the new URLs.
Migrate the SDK to spec-driven code generation with a layered design:
the low-level model layer is generated from the vendored OpenAPI spec,
while the public facade (Lunogram, the project-scoped path factory, UUID
validation, auth header, transport) stays hand-written and unchanged.

- Vendor spec/client.yaml from the platform repo at a pinned commit;
  spec/SOURCE.md records repo, path and ref.
- Generate src/lunogram/gen/models.py with datamodel-code-generator
  (Pydantic v2). scripts/generate.sh + `make generate` are the single
  source of truth for generator flags; the generator is pinned in the
  dev extra.
- Wire the generated models into the facade as request-body types;
  Python is snake_case end-to-end so models flow through with no
  key-mapping layer. Methods accept a model or a plain dict.
- Remove the dead, broken hand-written app/types/** and app/models/**
  duplicates that the generated models replace.
- CI: ci.yml regenerates + drift-checks src/lunogram/gen, builds the
  wheel, import-smoke-tests and runs pytest; spec-sync.yml re-fetches
  the spec weekly and opens a PR on change.
- Add pytest smoke tests for the path factory, UUID validation and
  model serialization.

The public API is unchanged from the project-in-URL PR.
…cade

Wire the remaining Client API endpoints into the hand-written facade on
top of the generated models:

- user & organization inbox (create, query, count, mark_read, mark_archived)
- user device registration for push subscriptions
- project VAPID public key (client.push)
- end-user session token minting (client.sessions)
- organization membership (organization.add_user / remove_user)

Also fix two transport bugs surfaced by the wider surface:
- send the Authorization header as a Bearer token (was the bare key)
- treat an empty 2xx body (202/204) as success instead of a JSON error

Pin the vendored spec to the v0.1.0-rc.1 release asset and drop the
spec-sync workflow.
@jeroenrinzema jeroenrinzema changed the title refactor!: generate the client models from the OpenAPI spec refactor!: generate the Client API models from the OpenAPI spec Jun 25, 2026
@jeroenrinzema jeroenrinzema changed the base branch from feat/project-in-url to main June 25, 2026 10:26
Mirror the JS SDK's e2e coverage (users, organizations, events, scheduled,
membership, cleanup) against a real Client API. The suite is skipped unless
LUNOGRAM_API_KEY + LUNOGRAM_PROJECT_ID are set, so the offline unit run stays
green; the new e2e.yml workflow supplies the secrets on push.

Add an optional `url_endpoint` override (Lunogram(api_key, project_id,
url_endpoint=...)) so the suite can target the configured environment — the
same role as the JS SDK's `urlEndpoint`. Building the base URL from it also
removes the long-standing double slash after the host.
Accept url_endpoint with or without a trailing /api and always re-add it,
matching the JS SDK. Without this, an endpoint lacking /api built paths that
missed the API prefix and hit the frontend catch-all (200 HTML), which the
e2e suite surfaced.
Extend test_facade.py with mocked coverage for the core methods that were
only exercised live (user/org CRUD, events, scheduled, org inbox writes),
plus model-vs-dict body acceptance and the empty-2xx (202/204) success
path so it is not mistaken for an error tuple.
@jeroenrinzema jeroenrinzema merged commit b0058fc into main Jun 25, 2026
3 checks passed
@jeroenrinzema jeroenrinzema deleted the feat/spec-codegen branch June 25, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant