Skip to content

Commit b68f483

Browse files
George-iamgeobon
andauthored
feat: add media upload helpers to Python SDK (#10)
Expose media create/get/finalize helpers with tests and quickstart examples so Track C parity covers the media contract family set in GA clients. Made-with: Cursor Co-authored-by: George-iam <georgeb@gmail.com>
1 parent c542cfa commit b68f483

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ with AxmeClient(config) as client:
6464
idempotency_key="invite-accept-001",
6565
)
6666
print(accepted["public_address"])
67+
media_upload = client.create_media_upload(
68+
{
69+
"owner_agent": "agent://example/receiver",
70+
"filename": "contract.pdf",
71+
"mime_type": "application/pdf",
72+
"size_bytes": 12345,
73+
},
74+
idempotency_key="media-create-001",
75+
)
76+
print(media_upload["upload_id"])
77+
media_state = client.get_media_upload(media_upload["upload_id"])
78+
print(media_state["upload"]["status"])
79+
finalized = client.finalize_media_upload(
80+
{"upload_id": media_upload["upload_id"], "size_bytes": 12345},
81+
idempotency_key="media-finalize-001",
82+
)
83+
print(finalized["status"])
6784
subscription = client.upsert_webhook_subscription(
6885
{
6986
"callback_url": "https://integrator.example/webhooks/axme",

axme_sdk/client.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,41 @@ def accept_invite(
198198
retryable=idempotency_key is not None,
199199
)
200200

201+
def create_media_upload(
202+
self,
203+
payload: dict[str, Any],
204+
*,
205+
idempotency_key: str | None = None,
206+
trace_id: str | None = None,
207+
) -> dict[str, Any]:
208+
return self._request_json(
209+
"POST",
210+
"/v1/media/create-upload",
211+
json_body=payload,
212+
idempotency_key=idempotency_key,
213+
trace_id=trace_id,
214+
retryable=idempotency_key is not None,
215+
)
216+
217+
def get_media_upload(self, upload_id: str, *, trace_id: str | None = None) -> dict[str, Any]:
218+
return self._request_json("GET", f"/v1/media/{upload_id}", trace_id=trace_id, retryable=True)
219+
220+
def finalize_media_upload(
221+
self,
222+
payload: dict[str, Any],
223+
*,
224+
idempotency_key: str | None = None,
225+
trace_id: str | None = None,
226+
) -> dict[str, Any]:
227+
return self._request_json(
228+
"POST",
229+
"/v1/media/finalize-upload",
230+
json_body=payload,
231+
idempotency_key=idempotency_key,
232+
trace_id=trace_id,
233+
retryable=idempotency_key is not None,
234+
)
235+
201236
def upsert_webhook_subscription(
202237
self,
203238
payload: dict[str, Any],

tests/test_client.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,103 @@ def handler(request: httpx.Request) -> httpx.Response:
364364
assert client.accept_invite(token, payload, idempotency_key="invite-accept-1")["status"] == "accepted"
365365

366366

367+
def test_create_media_upload_success() -> None:
368+
upload_id = "77777777-7777-4777-8777-777777777777"
369+
payload = {
370+
"owner_agent": "agent://owner",
371+
"filename": "contract.pdf",
372+
"mime_type": "application/pdf",
373+
"size_bytes": 12345,
374+
}
375+
376+
def handler(request: httpx.Request) -> httpx.Response:
377+
assert request.method == "POST"
378+
assert request.url.path == "/v1/media/create-upload"
379+
assert request.headers["idempotency-key"] == "media-create-1"
380+
body = json.loads(request.read().decode("utf-8"))
381+
assert body == payload
382+
return httpx.Response(
383+
200,
384+
json={
385+
"ok": True,
386+
"upload_id": upload_id,
387+
"owner_agent": "agent://owner",
388+
"bucket": "axme-media",
389+
"object_path": "agent-owner/contract.pdf",
390+
"upload_url": "https://upload.example/media/1",
391+
"status": "pending",
392+
"expires_at": "2026-03-01T00:00:00Z",
393+
"max_size_bytes": 10485760,
394+
},
395+
)
396+
397+
client = _client(handler)
398+
assert client.create_media_upload(payload, idempotency_key="media-create-1")["upload_id"] == upload_id
399+
400+
401+
def test_get_media_upload_success() -> None:
402+
upload_id = "77777777-7777-4777-8777-777777777777"
403+
404+
def handler(request: httpx.Request) -> httpx.Response:
405+
assert request.method == "GET"
406+
assert request.url.path == f"/v1/media/{upload_id}"
407+
return httpx.Response(
408+
200,
409+
json={
410+
"ok": True,
411+
"upload": {
412+
"upload_id": upload_id,
413+
"owner_agent": "agent://owner",
414+
"bucket": "axme-media",
415+
"object_path": "agent-owner/contract.pdf",
416+
"mime_type": "application/pdf",
417+
"filename": "contract.pdf",
418+
"size_bytes": 12345,
419+
"sha256": None,
420+
"status": "pending",
421+
"created_at": "2026-02-28T00:00:00Z",
422+
"expires_at": "2026-03-01T00:00:00Z",
423+
"finalized_at": None,
424+
"download_url": None,
425+
"preview_url": None,
426+
},
427+
},
428+
)
429+
430+
client = _client(handler)
431+
assert client.get_media_upload(upload_id)["upload"]["status"] == "pending"
432+
433+
434+
def test_finalize_media_upload_success() -> None:
435+
upload_id = "77777777-7777-4777-8777-777777777777"
436+
payload = {"upload_id": upload_id, "size_bytes": 12345}
437+
438+
def handler(request: httpx.Request) -> httpx.Response:
439+
assert request.method == "POST"
440+
assert request.url.path == "/v1/media/finalize-upload"
441+
assert request.headers["idempotency-key"] == "media-finalize-1"
442+
body = json.loads(request.read().decode("utf-8"))
443+
assert body == payload
444+
return httpx.Response(
445+
200,
446+
json={
447+
"ok": True,
448+
"upload_id": upload_id,
449+
"owner_agent": "agent://owner",
450+
"bucket": "axme-media",
451+
"object_path": "agent-owner/contract.pdf",
452+
"mime_type": "application/pdf",
453+
"size_bytes": 12345,
454+
"sha256": None,
455+
"status": "ready",
456+
"finalized_at": "2026-02-28T00:00:10Z",
457+
},
458+
)
459+
460+
client = _client(handler)
461+
assert client.finalize_media_upload(payload, idempotency_key="media-finalize-1")["status"] == "ready"
462+
463+
367464
@pytest.mark.parametrize(
368465
("status_code", "expected_exception"),
369466
[

0 commit comments

Comments
 (0)