python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"Before opening a pull request:
ruff check . && ruff format --check .
mypy
pyright
pytestAll four must pass. They check different things: mypy treats a Literal[...] | str alias as
interchangeable with bare str, so the assertions in tests/typing/ are meaningful only under
pyright.
Four local edits. Nothing in the driver, the signer or the error mapping should need to change — if it does, say so in the pull request, because that is a structural change and not a routine one.
-
A builder in
_requests.pyreturning aRequestSpec. Give it the path and, for a write, a body through_encode. Query parameters go inRequestSpec.query; they are excluded from the signature, as the service excludes them. -
A decoder in
_responses.pywith the shape(Outcome) -> YourEntity. Start from one of three entry points — all three have already raised for a transport fault, a non-2xx and an unreadable body:Response shape Entry point {"data": {...}}— one resourcedata_of(outcome){"data": [...]}— a collectiondata_list_of(outcome)204, or a body worth ignoringno_content— pass it directly, write no decoderneeds a sibling key such as metaenvelope_of(outcome)Read fields with
_required_str,_optional_str,_optional_decimaland_optional_timestamp. Decode fail-open: an unrecognised enum value keeps its raw string.Pagination is deliberately unmodelled. No endpoint returns a collection yet, so its envelope is undecided; read
metayourself rather than inheriting a shape this SDK guessed before the API had one. -
A frozen dataclass in
models.py,slots=True, exported from__init__.py. -
A method on each client, sync and async. Reads go through
_execute(spec, decoder); Verification reads use the_verify(spec)lane. Retry is automatic forGETand must stay off for anything that charges, supersedes, or consumes an attempt.Methods are flat, named
<verb>_<resource>—get_application,create_application,delete_application, with_by_<key>and_rawas suffixes where they apply. Notclient.applications.get(). Resource namespaces read better past roughly five resources, but adopting them later renames every existing method, so the choice was made once, before publication, and applies to everything added since.
tests/test_extension_seams.py holds these guarantees. It decodes an entity that does not
exist in the SDK, through the real driver, to prove a new one needs no driver change.
This SDK signs its requests, and a signing bug is self-consistent: the client and the signer agree with each other and disagree only with the service. A test that compares them therefore passes while every real request fails.
So the signing tests use vectors that come from the service, and the transport tests assert on the built request -- its headers, its raw path, its bytes -- rather than on a round trip. When you add a test in that area, make sure it could actually fail.