Skip to content

feat(sdk/python): add python baseline sdk#4711

Open
mark-vass-konghq wants to merge 3 commits into
mainfrom
feat/python-sdk-baseline
Open

feat(sdk/python): add python baseline sdk#4711
mark-vass-konghq wants to merge 3 commits into
mainfrom
feat/python-sdk-baseline

Conversation

@mark-vass-konghq

@mark-vass-konghq mark-vass-konghq commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an idiomatic, typed Python SDK baseline for the OpenMeter v3 API, covering meters and plan add-ons with matching synchronous and asynchronous clients.

What changed

  • Added Python 3.9+ package configuration, lockfile, type information, and documentation.
  • Added sync and async clients with token authentication and configurable transports.
  • Added meter CRUD, JSON/CSV queries, and streaming CSV responses.
  • Added plan add-on CRUD and list operations.
  • Added lazy automatic page-number pagination with API defaults.
  • Added Pydantic request validation and forward-compatible response models.
  • Added structured API and transport errors, bounded response bodies, and retries for idempotent requests.
  • Added runnable sync/async examples and 28 behavioral tests.

This is intentionally a baseline SDK rather than complete v3 API coverage.

Verification

  • uv run --frozen flake8 src tests examples
  • uv run --frozen ruff check .
  • uv run --frozen ruff format --check .
  • uv run --frozen pyright
  • uv run --frozen python -m unittest discover -s tests -v — 28 tests passed
  • uv build
  • Ran both sync and async examples successfully against a local OpenMeter server

Summary by CodeRabbit

  • New Features
    • Added the OpenMeter v3 Python SDK with both synchronous and asynchronous clients.
    • Support for meter and plan add-on CRUD, paginated listing, usage queries, CSV download, and streaming CSV via async/sync helpers.
    • Added typed request/response models and structured error handling, including response size limits and safe pagination.
    • Improved retry behavior for eligible requests, plus lazy loading for async components.
  • Documentation
    • Added full Python SDK README and runnable sync/async examples.
  • Tests
    • Added end-to-end integration tests covering validation, pagination, streaming behavior, errors, and retry policies.
  • Chores
    • Updated packaging and repository hygiene (manifest, ignore rules, lint config, versioning, and typing marker).

Greptile Summary

This PR adds a baseline Python SDK for the OpenMeter v3 API. The main changes are:

  • New sync and async clients for meters and plan add-ons.
  • Typed Pydantic request and response models.
  • Standard-library sync transport and httpx async transport.
  • Structured API, transport, pagination, and response-size errors.
  • Packaging metadata, examples, README, and behavioral tests.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
sdk/python/openmeter/src/openmeter/client.py Adds the synchronous SDK surface for meters and plan add-ons.
sdk/python/openmeter/src/openmeter/aio.py Adds the asynchronous SDK surface that mirrors the sync client.
sdk/python/openmeter/src/openmeter/_transport.py Adds the standard-library HTTP transport for the sync client.
sdk/python/openmeter/src/openmeter/_transport_async.py Adds the httpx-based HTTP transport for the async client.
sdk/python/openmeter/src/openmeter/models.py Adds typed models for requests, responses, pagination, filters, meters, and plan add-ons.

Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/python-sdk..." | Re-trigger Greptile

Context used (3)

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)
  • Context used - api/spec/AGENTS.md (source)

@mark-vass-konghq
mark-vass-konghq requested a review from a team as a code owner July 14, 2026 15:18
@mark-vass-konghq mark-vass-konghq self-assigned this Jul 14, 2026
@mark-vass-konghq mark-vass-konghq added area/api release-note/feature Release note: Exciting New Features labels Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds an OpenMeter Python SDK v3 baseline with typed models, synchronous and asynchronous clients, HTTP transports with retries and bounded responses, meter and plan add-on resources, CSV support, packaging metadata, examples, documentation, and integration tests.

Changes

Python SDK v3

Layer / File(s) Summary
Package setup and public surface
sdk/python/openmeter/pyproject.toml, sdk/python/openmeter/README.md, sdk/python/openmeter/examples/*, sdk/python/openmeter/src/openmeter/__init__.py, sdk/python/openmeter/src/openmeter/_version.py
Defines package metadata, tooling, documentation, examples, version handling, typed-package support, and lazy async exports.
Models and error contracts
sdk/python/openmeter/src/openmeter/models.py, sdk/python/openmeter/src/openmeter/errors.py
Adds constrained Pydantic request/response models, pagination and filter schemas, problem details, and SDK exception types.
Synchronous and asynchronous HTTP transports
sdk/python/openmeter/src/openmeter/_transport.py, sdk/python/openmeter/src/openmeter/_transport_async.py
Implements request serialization, retries, bounded response handling, error conversion, streaming, and transport lifecycle behavior.
Resource clients and pagination
sdk/python/openmeter/src/openmeter/client.py, sdk/python/openmeter/src/openmeter/aio.py
Adds sync and async clients for meters and plan add-ons, lazy pagination, usage queries, CSV responses, and CSV streaming.
Local API test coverage
sdk/python/openmeter/tests/test_sdk.py, sdk/python/openmeter/tests/__init__.py
Adds a threaded local API server and tests for models, resource operations, buffering, streaming, ownership, redirects, and retry behavior.

Estimated code review effort: 5 (Critical) | ~90 minutes

Suggested reviewers: chrisgacsal

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant Client
  participant Resource
  participant Transport
  participant OpenMeterAPI
  Application->>Client: create SDK client
  Application->>Resource: invoke meter or plan add-on operation
  Resource->>Transport: build and send request
  Transport->>OpenMeterAPI: HTTP request
  OpenMeterAPI-->>Transport: response or problem details
  Transport-->>Resource: parsed model, bytes, stream, or error
  Resource-->>Application: operation result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: introducing a Python SDK baseline under sdk/python.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/python-sdk-baseline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment on lines +112 to +117
return self._transport.request_json(
"PUT",
_resource_path(_METERS_PATH, meter_id),
Meter,
body=request,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Clear Fields Are Omitted

When callers pass UpdateMeterRequest(labels=None) or UpdateMeterRequest(dimensions=None), this path uses the shared serializer that drops None fields before sending the PUT body. That makes the documented clear operation indistinguishable from an omitted field, so a server that preserves absent fields can return success while existing labels or dimensions remain unchanged.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/python/openmeter/src/openmeter/client.py
Line: 112-117

Comment:
**Clear Fields Are Omitted**

When callers pass `UpdateMeterRequest(labels=None)` or `UpdateMeterRequest(dimensions=None)`, this path uses the shared serializer that drops `None` fields before sending the PUT body. That makes the documented clear operation indistinguishable from an omitted field, so a server that preserves absent fields can return success while existing labels or dimensions remain unchanged.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Comment thread sdk/python/openmeter/src/openmeter/models.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
sdk/python/openmeter/src/openmeter/models.py (1)

155-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate "exactly one operator" validator logic.

_StringQueryFilter.validate_operator (Lines 175-183) re-implements the exact same "count non-None fields, require exactly 1" pattern already written in StringFilter.validate_operator (Lines 119-131). Worth extracting a small shared helper (e.g. _require_single_operator(model)) so the two validators can't silently drift apart.

♻️ Suggested extraction
+def _require_single_operator(model: BaseModel, error: str) -> None:
+    values = [getattr(model, name) for name in model.__class__.model_fields]
+    if sum(value is not None for value in values) != 1:
+        raise ValueError(error)
+
+
 class StringFilter(_RequestModel):
     ...
     `@model_validator`(mode="after")
     def validate_operator(self) -> StringFilter:
-        values = [getattr(self, name) for name in self.__class__.model_fields]
-        if sum(value is not None for value in values) != 1:
-            raise ValueError("exactly one string filter operator must be set")
+        _require_single_operator(self, "exactly one string filter operator must be set")
         ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/openmeter/src/openmeter/models.py` around lines 155 - 183, Extract
the shared “exactly one non-None operator” check from
StringFilter.validate_operator and _StringQueryFilter.validate_operator into a
small helper such as _require_single_operator(model). Update both validators to
call that helper while preserving their existing validation error behavior and
return semantics.
sdk/python/openmeter/src/openmeter/_transport.py (1)

21-26: 🩺 Stability & Availability | 🔵 Trivial

Blanket POST-exclusion from retries also blocks retries for read-only query calls.

The rationale here (avoid double-creating resources) is sound for meters.create/plan_addons.create, but meters.query/query_csv/query_csv_stream also use POST purely to carry a filter body — they're read-only and safe to retry on a transient 502/503/504, yet get zero automatic retries under the current method-based classification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/openmeter/src/openmeter/_transport.py` around lines 21 - 26,
Update the retry classification around _RETRYABLE_METHODS so read-only meter
query operations (query, query_csv, and query_csv_stream) can retry transient
502/503/504 responses despite using POST, while preserving no-retry behavior for
resource-creating POST calls such as meters.create and plan_addons.create. Use
the existing transport request context or operation identity to distinguish safe
query calls from mutating POST requests.
sdk/python/openmeter/src/openmeter/errors.py (1)

56-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider exposing invalid_parameters as a convenience property too.

title/detail/type/instance all get a friendly property, but invalid_parameters (a field callers will often want when reporting which request field failed validation) only reachable via error.problem.invalid_parameters. Small addition for symmetry:

`@property`
def invalid_parameters(self) -> list[InvalidParameter]:
    """Per-field validation issues reported by the API, if any."""
    return self.problem.invalid_parameters
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/openmeter/src/openmeter/errors.py` around lines 56 - 78, Expose
invalid_parameters on the same error class as the existing title, detail, type,
and instance properties by adding a typed convenience property that returns
self.problem.invalid_parameters and documents the per-field validation issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/python/openmeter/pyproject.toml`:
- Around line 46-48: Update the setuptools configuration near
[tool.setuptools.packages.find] to explicitly include the py.typed marker in the
openmeter package data, ensuring it is bundled into the wheel for downstream
type checkers.

In `@sdk/python/openmeter/src/openmeter/_transport_async.py`:
- Around line 150-159: Update the async transport request handling around the
status-code check in `_transport_async.py` to configure the underlying client
not to follow redirects, matching `_Transport.__init__` in `_transport.py`.
Preserve the existing 2xx success handling and error processing while ensuring
3xx responses are surfaced consistently with the sync transport.

In `@sdk/python/openmeter/src/openmeter/_transport.py`:
- Around line 31-51: The _Transport initializer currently uses build_opener()
with urllib’s default redirect handling, unlike AsyncClient. Configure the
opener in _Transport.__init__ to disable automatic HTTP redirects so 3xx
responses are surfaced and handled consistently as APIError; add a sync
transport test verifying redirects are not followed.

In `@sdk/python/openmeter/src/openmeter/aio.py`:
- Around line 248-261: Update the query_csv_stream docstring in the async client
to state that the caller must close the returned AsyncByteStream, including
guidance to use its close or async context-manager support. Keep the method’s
streaming behavior unchanged.

---

Nitpick comments:
In `@sdk/python/openmeter/src/openmeter/_transport.py`:
- Around line 21-26: Update the retry classification around _RETRYABLE_METHODS
so read-only meter query operations (query, query_csv, and query_csv_stream) can
retry transient 502/503/504 responses despite using POST, while preserving
no-retry behavior for resource-creating POST calls such as meters.create and
plan_addons.create. Use the existing transport request context or operation
identity to distinguish safe query calls from mutating POST requests.

In `@sdk/python/openmeter/src/openmeter/errors.py`:
- Around line 56-78: Expose invalid_parameters on the same error class as the
existing title, detail, type, and instance properties by adding a typed
convenience property that returns self.problem.invalid_parameters and documents
the per-field validation issues.

In `@sdk/python/openmeter/src/openmeter/models.py`:
- Around line 155-183: Extract the shared “exactly one non-None operator” check
from StringFilter.validate_operator and _StringQueryFilter.validate_operator
into a small helper such as _require_single_operator(model). Update both
validators to call that helper while preserving their existing validation error
behavior and return semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc2242be-ca13-400b-9e86-862179535937

📥 Commits

Reviewing files that changed from the base of the PR and between acbbb03 and a9acb45.

⛔ Files ignored due to path filters (1)
  • sdk/python/openmeter/uv.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (18)
  • sdk/python/openmeter/.flake8
  • sdk/python/openmeter/.gitignore
  • sdk/python/openmeter/MANIFEST.in
  • sdk/python/openmeter/README.md
  • sdk/python/openmeter/examples/async.py
  • sdk/python/openmeter/examples/sync.py
  • sdk/python/openmeter/pyproject.toml
  • sdk/python/openmeter/src/openmeter/__init__.py
  • sdk/python/openmeter/src/openmeter/_transport.py
  • sdk/python/openmeter/src/openmeter/_transport_async.py
  • sdk/python/openmeter/src/openmeter/_version.py
  • sdk/python/openmeter/src/openmeter/aio.py
  • sdk/python/openmeter/src/openmeter/client.py
  • sdk/python/openmeter/src/openmeter/errors.py
  • sdk/python/openmeter/src/openmeter/models.py
  • sdk/python/openmeter/src/openmeter/py.typed
  • sdk/python/openmeter/tests/__init__.py
  • sdk/python/openmeter/tests/test_sdk.py

Comment on lines +46 to +48
[tool.setuptools.packages.find]
where = ["src"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Ensure the py.typed marker gets bundled in the wheel.

Hey! I see we're building a typed SDK (love the Typing :: Typed classifier!), but we need to make sure the py.typed marker file actually makes it into the installed wheel. setuptools won't grab non-Python files automatically unless we explicitly specify them in package-data.

Let's add that so downstream type checkers know the package is fully typed!

📦 Proposed fix to include the marker file
 [tool.setuptools.packages.find]
 where = ["src"]
+
+[tool.setuptools.package-data]
+openmeter = ["py.typed"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
openmeter = ["py.typed"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/openmeter/pyproject.toml` around lines 46 - 48, Update the
setuptools configuration near [tool.setuptools.packages.find] to explicitly
include the py.typed marker in the openmeter package data, ensuring it is
bundled into the wheel for downstream type checkers.

Comment thread sdk/python/openmeter/src/openmeter/_transport_async.py
Comment thread sdk/python/openmeter/src/openmeter/_transport.py Outdated
Comment thread sdk/python/openmeter/src/openmeter/aio.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
sdk/python/openmeter/pyproject.toml (1)

51-53: 📐 Maintainability & Code Quality | 🟠 Major

Include py.typed in the wheel.

This configuration only discovers Python packages. Unless another build-data mechanism covers it, src/openmeter/py.typed may be omitted from the wheel, causing downstream type checkers to miss the typed-package marker. Add explicit package data and verify the built wheel contents.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/openmeter/pyproject.toml` around lines 51 - 53, Update the
setuptools configuration in pyproject.toml to explicitly include the
src/openmeter/py.typed marker as package data, ensuring it is present in the
built wheel. Verify the wheel archive contains openmeter/py.typed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@sdk/python/openmeter/pyproject.toml`:
- Around line 51-53: Update the setuptools configuration in pyproject.toml to
explicitly include the src/openmeter/py.typed marker as package data, ensuring
it is present in the built wheel. Verify the wheel archive contains
openmeter/py.typed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd0198c2-d720-414b-b565-f95d9d73a383

📥 Commits

Reviewing files that changed from the base of the PR and between a9acb45 and ec3b4d1.

⛔ Files ignored due to path filters (1)
  • sdk/python/openmeter/uv.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (8)
  • sdk/python/openmeter/README.md
  • sdk/python/openmeter/pyproject.toml
  • sdk/python/openmeter/src/openmeter/__init__.py
  • sdk/python/openmeter/src/openmeter/_transport.py
  • sdk/python/openmeter/src/openmeter/_transport_async.py
  • sdk/python/openmeter/src/openmeter/aio.py
  • sdk/python/openmeter/src/openmeter/errors.py
  • sdk/python/openmeter/tests/test_sdk.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • sdk/python/openmeter/src/openmeter/errors.py
  • sdk/python/openmeter/README.md
  • sdk/python/openmeter/src/openmeter/init.py
  • sdk/python/openmeter/src/openmeter/aio.py
  • sdk/python/openmeter/src/openmeter/_transport_async.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant