From 00539b4fb228e7bbcd85efa3746bf215b5610baf Mon Sep 17 00:00:00 2001 From: claudi Date: Tue, 7 Apr 2026 10:36:41 +0200 Subject: [PATCH] Add structured workflow result models for video and document uploads; update client methods and examples --- README.md | 1 + ebay_client/media/__init__.py | 18 ++++++++++++-- ebay_client/media/client.py | 44 +++++++++++++++++++++++++++------- examples/media_workflows.py | 16 +++++++------ tests/test_public_wrappers.py | 45 ++++++++++++++++++++++++++++------- 5 files changed, 98 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 94d9910..ebc471a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Currently wired API domains include Notification, Inventory, Fulfillment, Accoun The Media wrapper includes workflow helpers on top of the raw endpoints: +- `VideoWorkflowResult` and `DocumentWorkflowResult` to return structured data from the higher-level workflows - `extract_resource_id()` to pull a media resource ID from a `Location` header - `guess_media_content_type()` to infer a content type from a file name when possible - `wait_for_video()` to poll until a video reaches `LIVE` or a terminal failure state diff --git a/ebay_client/media/__init__.py b/ebay_client/media/__init__.py index 1288b1e..472fb01 100644 --- a/ebay_client/media/__init__.py +++ b/ebay_client/media/__init__.py @@ -1,3 +1,17 @@ -from ebay_client.media.client import CreatedMediaResource, MediaClient, extract_resource_id +from ebay_client.media.client import ( + CreatedMediaResource, + DocumentWorkflowResult, + MediaClient, + VideoWorkflowResult, + extract_resource_id, + guess_media_content_type, +) -__all__ = ["CreatedMediaResource", "MediaClient", "extract_resource_id"] \ No newline at end of file +__all__ = [ + "CreatedMediaResource", + "DocumentWorkflowResult", + "MediaClient", + "VideoWorkflowResult", + "extract_resource_id", + "guess_media_content_type", +] \ No newline at end of file diff --git a/ebay_client/media/client.py b/ebay_client/media/client.py index 3506e63..5d0e003 100644 --- a/ebay_client/media/client.py +++ b/ebay_client/media/client.py @@ -27,6 +27,19 @@ class CreatedMediaResource(BaseModel): resource_id: str | None = None +class VideoWorkflowResult(BaseModel): + created: CreatedMediaResource + video: Video + video_id: str | None = None + + +class DocumentWorkflowResult(BaseModel): + created: CreateDocumentResponse + uploaded: DocumentResponse | None = None + document: DocumentResponse + document_id: str | None = None + + def extract_resource_id(location: str | None) -> str | None: if not location: return None @@ -109,7 +122,7 @@ class MediaClient: content_range: str | None = None, timeout_seconds: float = 30.0, poll_interval_seconds: float = 1.0, - ) -> Video: + ) -> VideoWorkflowResult: created = self.create_video(payload) video_id = self._require_resource_id(created.resource_id, "video resource ID") self.upload_video( @@ -118,11 +131,12 @@ class MediaClient: content_length=content_length if content_length is not None else len(content), content_range=content_range, ) - return self.wait_for_video( + video = self.wait_for_video( video_id, timeout_seconds=timeout_seconds, poll_interval_seconds=poll_interval_seconds, ) + return VideoWorkflowResult(created=created, video=video, video_id=video_id) def create_upload_and_wait_video_from_path( self, @@ -133,7 +147,7 @@ class MediaClient: description: str | None = None, timeout_seconds: float = 30.0, poll_interval_seconds: float = 1.0, - ) -> Video: + ) -> VideoWorkflowResult: path = Path(video_path) content = path.read_bytes() payload = CreateVideoRequest( @@ -198,20 +212,26 @@ class MediaClient: content_type: str = "application/octet-stream", timeout_seconds: float = 30.0, poll_interval_seconds: float = 1.0, - ) -> DocumentResponse: + ) -> DocumentWorkflowResult: created = self.create_document(payload) document_id = self._require_resource_id(created.documentId, "documentId") - self.upload_document( + uploaded = self.upload_document( document_id, file_name=file_name, content=content, content_type=content_type, ) - return self.wait_for_document( + document = self.wait_for_document( document_id, timeout_seconds=timeout_seconds, poll_interval_seconds=poll_interval_seconds, ) + return DocumentWorkflowResult( + created=created, + uploaded=uploaded, + document=document, + document_id=document_id, + ) def create_document_from_url(self, payload: CreateDocumentFromUrlRequest) -> CreateDocumentResponse: return self.transport.request_model( @@ -229,14 +249,20 @@ class MediaClient: *, timeout_seconds: float = 30.0, poll_interval_seconds: float = 1.0, - ) -> DocumentResponse: + ) -> DocumentWorkflowResult: created = self.create_document_from_url(payload) document_id = self._require_resource_id(created.documentId, "documentId") - return self.wait_for_document( + document = self.wait_for_document( document_id, timeout_seconds=timeout_seconds, poll_interval_seconds=poll_interval_seconds, ) + return DocumentWorkflowResult( + created=created, + uploaded=None, + document=document, + document_id=document_id, + ) def get_document(self, document_id: str) -> DocumentResponse: return self.transport.request_model( @@ -278,7 +304,7 @@ class MediaClient: *, timeout_seconds: float = 30.0, poll_interval_seconds: float = 1.0, - ) -> DocumentResponse: + ) -> DocumentWorkflowResult: path = Path(document_path) return self.create_upload_and_wait_document( payload, diff --git a/examples/media_workflows.py b/examples/media_workflows.py index f8c1993..bbc3f0d 100644 --- a/examples/media_workflows.py +++ b/examples/media_workflows.py @@ -32,7 +32,7 @@ def upload_image_from_url(client: EbayClient, image_url: str) -> None: def upload_document_and_wait(client: EbayClient, document_path: Path) -> None: - accepted = client.media.create_upload_and_wait_document( + result = client.media.create_upload_and_wait_document( CreateDocumentRequest( documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"], @@ -42,11 +42,12 @@ def upload_document_and_wait(client: EbayClient, document_path: Path) -> None: content_type="application/pdf", timeout_seconds=60.0, ) - print("document_final_status:", accepted.documentStatus) + print("document_id:", result.document_id) + print("document_final_status:", result.document.documentStatus) def upload_document_and_wait_from_path(client: EbayClient, document_path: Path) -> None: - accepted = client.media.create_upload_and_wait_document_from_path( + result = client.media.create_upload_and_wait_document_from_path( CreateDocumentRequest( documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"], @@ -54,17 +55,18 @@ def upload_document_and_wait_from_path(client: EbayClient, document_path: Path) document_path, timeout_seconds=60.0, ) - print("document_final_status:", accepted.documentStatus) + print("document_id:", result.document_id) + print("document_final_status:", result.document.documentStatus) def upload_video_and_wait(client: EbayClient, video_path: Path) -> None: - live_video = client.media.create_upload_and_wait_video_from_path( + result = client.media.create_upload_and_wait_video_from_path( video_path, description="Example upload from the ebay-rest-client workspace.", timeout_seconds=120.0, ) - print("video_status:", live_video.status) - print("video_id:", live_video.videoId) + print("video_id:", result.video_id) + print("video_status:", result.video.status) def main() -> None: diff --git a/tests/test_public_wrappers.py b/tests/test_public_wrappers.py index 55a755e..6b35509 100644 --- a/tests/test_public_wrappers.py +++ b/tests/test_public_wrappers.py @@ -36,7 +36,14 @@ from ebay_client.generated.notification.models import ( UpdateSubscriptionRequest, ) from ebay_client.inventory.client import InventoryClient -from ebay_client.media.client import CreatedMediaResource, MediaClient, extract_resource_id, guess_media_content_type +from ebay_client.media.client import ( + CreatedMediaResource, + DocumentWorkflowResult, + MediaClient, + VideoWorkflowResult, + extract_resource_id, + guess_media_content_type, +) from ebay_client.notification.client import NotificationClient @@ -593,7 +600,10 @@ def test_media_create_upload_and_wait_video_orchestrates_flow(monkeypatch) -> No poll_interval_seconds=0.0, ) - assert result.videoId == "VIDEO-9" + assert isinstance(result, VideoWorkflowResult) + assert result.video_id == "VIDEO-9" + assert result.video.videoId == "VIDEO-9" + assert result.created.resource_id == "VIDEO-9" assert calls[0][0] == "create_video" assert calls[1] == ( "upload_video", @@ -636,7 +646,11 @@ def test_media_create_upload_and_wait_document_orchestrates_flow(monkeypatch) -> poll_interval_seconds=0.0, ) - assert result.documentStatus == "ACCEPTED" + assert isinstance(result, DocumentWorkflowResult) + assert result.document_id == "DOC-9" + assert result.created.documentId == "DOC-9" + assert result.uploaded is not None and result.uploaded.documentStatus == "SUBMITTED" + assert result.document.documentStatus == "ACCEPTED" assert calls[0][0] == "create_document" assert calls[1] == ( "upload_document", @@ -679,7 +693,11 @@ def test_media_create_document_from_url_and_wait_orchestrates_flow(monkeypatch) poll_interval_seconds=0.0, ) - assert result.documentStatus == "ACCEPTED" + assert isinstance(result, DocumentWorkflowResult) + assert result.document_id == "DOC-10" + assert result.created.documentId == "DOC-10" + assert result.uploaded is None + assert result.document.documentStatus == "ACCEPTED" assert calls[0][0] == "create_document_from_url" assert calls[1] == ( "wait_for_document", @@ -788,7 +806,12 @@ def test_media_create_upload_and_wait_document_from_path_reads_file_and_delegate client, "create_upload_and_wait_document", lambda payload, **kwargs: captured.update({"payload": payload, **kwargs}) - or DocumentResponse(documentId="DOC-77", documentStatus="ACCEPTED"), + or DocumentWorkflowResult( + created=CreateDocumentResponse(documentId="DOC-77", documentStatus="PENDING_UPLOAD"), + uploaded=DocumentResponse(documentId="DOC-77", documentStatus="SUBMITTED"), + document=DocumentResponse(documentId="DOC-77", documentStatus="ACCEPTED"), + document_id="DOC-77", + ), ) result = client.create_upload_and_wait_document_from_path( @@ -797,7 +820,8 @@ def test_media_create_upload_and_wait_document_from_path_reads_file_and_delegate poll_interval_seconds=0.0, ) - assert result.documentStatus == "ACCEPTED" + assert isinstance(result, DocumentWorkflowResult) + assert result.document.documentStatus == "ACCEPTED" assert captured["file_name"] == "guide.pdf" assert captured["content"] == b"%PDF-1.7" assert captured["content_type"] == "application/pdf" @@ -815,7 +839,11 @@ def test_media_create_upload_and_wait_video_from_path_builds_payload_and_delegat client, "create_upload_and_wait_video", lambda payload, **kwargs: captured.update({"payload": payload, **kwargs}) - or Video(videoId="VIDEO-88", status="LIVE"), + or VideoWorkflowResult( + created=CreatedMediaResource(resource_id="VIDEO-88"), + video=Video(videoId="VIDEO-88", status="LIVE"), + video_id="VIDEO-88", + ), ) result = client.create_upload_and_wait_video_from_path( @@ -824,7 +852,8 @@ def test_media_create_upload_and_wait_video_from_path_builds_payload_and_delegat poll_interval_seconds=0.0, ) - assert result.videoId == "VIDEO-88" + assert isinstance(result, VideoWorkflowResult) + assert result.video.videoId == "VIDEO-88" payload = captured["payload"] assert isinstance(payload, CreateVideoRequest) assert payload.title == "demo"