From 19e1538f4bf223fa25cda5480c00d40d81591edd Mon Sep 17 00:00:00 2001 From: Nathan Harmon Date: Thu, 17 Sep 2026 15:57:32 -0400 Subject: [PATCH 1/3] Add Diffio 4.0 playback, mix, and export support --- README.md | 88 ++++++++++++++++++++++ src/diffio/__init__.py | 8 ++ src/diffio/client.py | 126 ++++++++++++++++++++++++++++++- src/diffio/types.py | 48 +++++++++++- tests/test_diffio40.py | 165 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 431 insertions(+), 4 deletions(-) create mode 100644 tests/test_diffio40.py diff --git a/README.md b/README.md index 423f05b..78e64c4 100644 --- a/README.md +++ b/README.md @@ -304,3 +304,91 @@ cd diffio-python python -m pip install -r requirements-dev.txt python -m pytest ``` + +## Diffio 4.0 stems and mixing + +These methods target the Diffio 4.0 API implementation. Use a deployment that +has enabled Diffio 4.0; SDK support does not imply production availability. + +Create a generation using `model="diffio-4.0"`. Existing models and defaults +are unchanged. While processing, fetch bounded pages of the playback manifest: + +```python +generation = client.generations.create( + apiProjectId="proj_123", + model="diffio-4.0", + idempotencyKey="unique-request-id", +) + +page = client.generations.get_playback( + apiProjectId="proj_123", + generationId=generation.generationId, + startChunk=0, + chunkCount=8, +) +manifest = page.manifest +# manifest["chunks"] contains this page with signed audio URLs. +# manifest["chunkCount"] is the TOTAL currently available chunk count, +# not the size of this page. startChunk is a zero-based index. +``` + +`startChunk` must be a nonnegative integer; `chunkCount` defaults to 8 and +accepts 1–16. Refetch pages as processing advances or signed URLs expire. + +Save one background level, from 0 (speech only) to 1, using optimistic concurrency: + +```python +updated = client.generations.update_mix( + apiProjectId="proj_123", + generationId=generation.generationId, + backgroundGain=0.25, + expectedRevision=manifest["mix"]["revision"], +) +print(updated.mix.backgroundGain, updated.mix.revision) +``` + +A stale revision raises `DiffioApiError` with `statusCode == 409`; fetch the +current playback manifest before deciding whether to resubmit. The SDK does +not automatically retry mix updates. + +Request MP3 or FLAC for `artifact="mix"`, `"speech"`, or `"background"`. +MP4 is for a video project's current mix. Exports require completed processing +and the account's download permissions. The optional `backgroundGain` selects +the mix for that export; it does not save the generation's mix setting. + +```python +from diffio import GenerationExportPendingResponse + +result = client.generations.get_download( + apiProjectId="proj_123", + generationId=generation.generationId, + artifact="mix", + format="flac", + backgroundGain=0.25, +) +if isinstance(result, GenerationExportPendingResponse): + print(result.exportId, result.retryAfterSeconds) + # Repeat the same request after this delay to poll the export. +else: + print(result.downloadUrl) + +# Or poll and save the finished export automatically: +client.generations.download( + apiProjectId="proj_123", + generationId=generation.generationId, + artifact="mix", + format="mp3", + backgroundGain=0.25, + downloadFilePath="restored.mp3", + exportTimeout=600, +) +``` + +`generations.wait_for_download(..., timeout=600)` polls without saving a file. +It raises `TimeoutError` when its polling deadline expires. Pending responses +are not download URLs. Polling disables transport retries and bounds numeric +request timeouts by the remaining deadline; an in-flight HTTP operation can +still finish after that deadline. This SDK uses synchronous HTTP calls. + +Flat methods are also available: `get_generation_playback`, +`update_generation_mix`, and `get_generation_download`. diff --git a/src/diffio/__init__.py b/src/diffio/__init__.py index 1a71fe3..a6da394 100644 --- a/src/diffio/__init__.py +++ b/src/diffio/__init__.py @@ -19,6 +19,10 @@ CreateProjectResponse, DownloadType, GenerationDownloadResponse, + GenerationPlaybackResponse, + GenerationMixResponse, + GenerationMix, + GenerationExportPendingResponse, GenerationProgressResponse, GenerationProgressStage, GenerationTranscription, @@ -51,6 +55,10 @@ "DiffioApiError", "DiffioClient", "GenerationDownloadResponse", + "GenerationPlaybackResponse", + "GenerationMixResponse", + "GenerationMix", + "GenerationExportPendingResponse", "GenerationProgressResponse", "GenerationProgressStage", "GenerationTranscription", diff --git a/src/diffio/client.py b/src/diffio/client.py index 9a48da7..7f52a0a 100644 --- a/src/diffio/client.py +++ b/src/diffio/client.py @@ -1,4 +1,5 @@ import json +import math import mimetypes import os import tempfile @@ -19,6 +20,9 @@ CreateProjectResponse, DownloadType, GenerationDownloadResponse, + GenerationExportPendingResponse, + GenerationMixResponse, + GenerationPlaybackResponse, GenerationProgressResponse, GenerationWebhookEvent, ListProjectGenerationsResponse, @@ -38,6 +42,7 @@ "diffio-2-flash": "diffio-2.0-flash-generation", "diffio-3.4": "diffio-3.4-generation", "diffio-3.5": "diffio-3.5-generation", + "diffio-4.0": "diffio-4.0-generation", } DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504] DEFAULT_RETRY_BACKOFF = 0.5 @@ -504,6 +509,9 @@ def get_generation_download( generationId, apiProjectId, downloadType=None, + artifact=None, + format=None, + backgroundGain=None, requestOptions=None, ): """ @@ -547,14 +555,63 @@ def get_generation_download( resolved_download_type, _ = _normalize_download_type(downloadType) payload["downloadType"] = resolved_download_type + if artifact is not None: + if artifact not in ("mix", "speech", "background"): + raise ValueError("artifact must be mix, speech, or background") + payload["artifact"] = artifact + if format is not None: + if format not in ("mp3", "flac", "mp4"): + raise ValueError("format must be mp3, flac, or mp4") + payload["format"] = format + if backgroundGain is not None: + _validate_background_gain(backgroundGain) + payload["backgroundGain"] = backgroundGain + response = self._request( "POST", "get_generation_download", json_payload=payload, requestOptions=requestOptions, ) + if isinstance(response, dict) and response.get("status") == "pending": + try: + return GenerationExportPendingResponse.from_dict(response) + except (KeyError, ValueError, TypeError) as exc: + raise DiffioApiError("Malformed pending export response", responseBody=response) from exc return GenerationDownloadResponse.from_dict(response) + def update_generation_mix( + self, *, apiProjectId, generationId, backgroundGain, expectedRevision, + requestOptions=None, + ): + _validate_background_gain(backgroundGain) + if isinstance(expectedRevision, bool) or not isinstance(expectedRevision, int) or expectedRevision < 0: + raise ValueError("expectedRevision must be a nonnegative integer") + response = self._request( + "POST", "update_generation_mix", + json_payload={ + "apiProjectId": apiProjectId, "generationId": generationId, + "backgroundGain": backgroundGain, "expectedRevision": expectedRevision, + }, + requestOptions=requestOptions, allow_retries=False, + ) + return GenerationMixResponse.from_dict(response) + + def get_generation_playback( + self, *, apiProjectId, generationId, startChunk=0, chunkCount=8, requestOptions=None, + ): + if isinstance(startChunk, bool) or not isinstance(startChunk, int) or startChunk < 0: + raise ValueError("startChunk must be a nonnegative integer") + if isinstance(chunkCount, bool) or not isinstance(chunkCount, int) or not 1 <= chunkCount <= 16: + raise ValueError("chunkCount must be an integer between 1 and 16") + response = self._request( + "POST", "get_generation_playback", + json_payload={"apiProjectId": apiProjectId, "generationId": generationId, + "startChunk": startChunk, "chunkCount": chunkCount}, + requestOptions=requestOptions, + ) + return GenerationPlaybackResponse.from_dict(response) + def send_webhook_test_event( self, *, @@ -797,7 +854,11 @@ def _request(self, method, path, *, json_payload, requestOptions=None, allow_ret attempt += 1 continue - return _raise_for_error(response) + data = _raise_for_error(response) + if path == "get_generation_download" and response.status_code == 202: + if not isinstance(data, dict) or data.get("status") != "pending": + raise DiffioApiError("Malformed pending export response", statusCode=202, responseBody=data) + return data class AccountClient: @@ -886,12 +947,16 @@ def get_download( generationId, apiProjectId, downloadType=None, + artifact=None, + format=None, + backgroundGain=None, requestOptions=None, ): return self._parent.get_generation_download( generationId=generationId, apiProjectId=apiProjectId, downloadType=downloadType, + artifact=artifact, format=format, backgroundGain=backgroundGain, requestOptions=requestOptions, ) @@ -901,7 +966,11 @@ def download( generationId, apiProjectId, downloadFilePath, + exportTimeout=600.0, downloadType=None, + artifact=None, + format=None, + backgroundGain=None, requestOptions=None, ): """ @@ -931,16 +1000,61 @@ def download( else: resolved_download_type = None - download = self._parent.get_generation_download( + download = self.wait_for_download( generationId=generationId, apiProjectId=apiProjectId, downloadType=resolved_download_type, + artifact=artifact, format=format, backgroundGain=backgroundGain, + timeout=exportTimeout, requestOptions=requestOptions, ) _warn_download_extension_mismatch(download, resolved_path) _download_to_file(self._parent, download.downloadUrl, resolved_path, requestOptions=requestOptions) return download + def wait_for_download( + self, *, generationId, apiProjectId, downloadType=None, artifact=None, + format=None, backgroundGain=None, timeout=600.0, requestOptions=None, + ): + """Poll an idempotent export request until ready, or raise TimeoutError.""" + if not isinstance(timeout, (int, float)) or not math.isfinite(timeout) or timeout <= 0: + raise ValueError("timeout must be finite and positive") + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + options = _merge_request_options(self._parent._default_request_options, requestOptions or {}) + options.maxRetries = 0 + if options.timeout is None: + options.timeout = remaining + elif isinstance(options.timeout, (int, float)): + options.timeout = min(options.timeout, remaining) + download = self.get_download( + generationId=generationId, apiProjectId=apiProjectId, + downloadType=downloadType, artifact=artifact, format=format, + backgroundGain=backgroundGain, requestOptions=options, + ) + if not isinstance(download, GenerationExportPendingResponse): + return download + remaining = deadline - time.monotonic() + if remaining > 0: + time.sleep(min(max(0.1, download.retryAfterSeconds), remaining)) + raise TimeoutError("Timed out waiting for generation export") + + def update_mix(self, *, apiProjectId, generationId, backgroundGain, expectedRevision, requestOptions=None): + return self._parent.update_generation_mix( + apiProjectId=apiProjectId, generationId=generationId, + backgroundGain=backgroundGain, expectedRevision=expectedRevision, + requestOptions=requestOptions, + ) + + def get_playback(self, *, apiProjectId, generationId, startChunk=0, chunkCount=8, requestOptions=None): + return self._parent.get_generation_playback( + apiProjectId=apiProjectId, generationId=generationId, + startChunk=startChunk, chunkCount=chunkCount, requestOptions=requestOptions, + ) + def wait_for_complete( self, *, @@ -1231,7 +1345,8 @@ def restore_audio( metadata["stage"] = "download_info" try: - download = self._parent.get_generation_download( + download = self._parent.generations.wait_for_download( + timeout=timeout, generationId=result.generation.generationId, apiProjectId=result.project.apiProjectId, downloadType=downloadType, @@ -1672,3 +1787,8 @@ def _report_progress(progress, *, onProgress, showProgress): onProgress(progress) if showProgress: print(_format_progress(progress)) + + +def _validate_background_gain(value): + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or not 0 <= value <= 1: + raise ValueError("backgroundGain must be a finite number between 0 and 1") diff --git a/src/diffio/types.py b/src/diffio/types.py index 227c7e3..837ad39 100644 --- a/src/diffio/types.py +++ b/src/diffio/types.py @@ -1,3 +1,5 @@ +import math + class CreateProjectResponse: def __init__( self, @@ -451,7 +453,7 @@ def __init__(self, project, generation): self.generation = generation -ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5") +ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5", "diffio-4.0") DownloadType = ("audio", "mp3", "video", "transcript") WebhookMode = ("test", "live") WebhookEventType = ( @@ -462,3 +464,47 @@ def __init__(self, project, generation): ) GenerationWebhookStatus = ("queued", "processing", "error", "complete") TranscriptionStatus = ("pending", "available", "unavailable") + + +class GenerationExportPendingResponse: + def __init__(self, exportId, retryAfterSeconds=2): + if not isinstance(exportId, str) or not exportId.strip(): + raise ValueError("exportId must be a nonempty string") + if (isinstance(retryAfterSeconds, bool) or not isinstance(retryAfterSeconds, (int, float)) + or not math.isfinite(retryAfterSeconds) or retryAfterSeconds < 0): + raise ValueError("retryAfterSeconds must be finite and nonnegative") + self.status = "pending" + self.exportId = exportId + self.retryAfterSeconds = retryAfterSeconds + + @classmethod + def from_dict(cls, data): + return cls(data["exportId"], data.get("retryAfterSeconds", 2)) + + +class GenerationMix: + def __init__(self, backgroundGain, revision): + self.backgroundGain = backgroundGain + self.revision = revision + + +class GenerationMixResponse: + def __init__(self, generationId, mix): + self.generationId = generationId + self.mix = mix + + @classmethod + def from_dict(cls, data): + return cls(data["generationId"], GenerationMix( + data["mix"]["backgroundGain"], data["mix"]["revision"], + )) + + +class GenerationPlaybackResponse: + def __init__(self, generationId, manifest): + self.generationId = generationId + self.manifest = manifest + + @classmethod + def from_dict(cls, data): + return cls(data["generationId"], data["manifest"]) diff --git a/tests/test_diffio40.py b/tests/test_diffio40.py new file mode 100644 index 0000000..c87c1ad --- /dev/null +++ b/tests/test_diffio40.py @@ -0,0 +1,165 @@ +import json + +import httpx +import pytest + +from diffio import DiffioClient, DiffioApiError, GenerationExportPendingResponse, ModelKey + + +def client_for(handler): + return DiffioClient(apiKey="test", httpClient=httpx.Client( + base_url="https://api.test", transport=httpx.MockTransport(handler), + )) + + +def ready(): + return dict(generationId="g", apiProjectId="p", downloadType="audio", + downloadUrl="https://files.test/audio", fileName="mix.flac", + storagePath="mix.flac", bucket="test", mimeType="audio/flac") + + +def test_create_v40(): + def handler(request): + assert request.url.path == "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/v1/diffio-4.0-generation" + assert json.loads(request.content) == {"apiProjectId": "p", "idempotencyKey": "once"} + return httpx.Response(200, json=dict(generationId="g", apiProjectId="p", + modelKey="diffio-4.0", status="queued")) + assert "diffio-4.0" in ModelKey + assert client_for(handler).generations.create(apiProjectId="p", model="diffio-4.0", + idempotencyKey="once").generationId == "g" + + +@pytest.mark.parametrize("format", ["mp3", "flac", "mp4"]) +@pytest.mark.parametrize("artifact", ["mix", "speech", "background"]) +def test_download_options(format, artifact): + def handler(request): + assert json.loads(request.content) == dict( + generationId="g", apiProjectId="p", format=format, artifact=artifact, backgroundGain=0.25) + return httpx.Response(200, json=ready()) + assert client_for(handler).generations.get_download( + generationId="g", apiProjectId="p", format=format, artifact=artifact, + backgroundGain=0.25).downloadUrl == ready()["downloadUrl"] + + +def test_pending_then_ready(monkeypatch): + requests = [] + def handler(request): + requests.append(json.loads(request.content)) + if len(requests) == 1: + return httpx.Response(202, json=dict(status="pending", exportId="e", retryAfterSeconds=2)) + return httpx.Response(200, json=ready()) + monkeypatch.setattr("diffio.client.time.sleep", lambda _: None) + result = client_for(handler).generations.wait_for_download( + generationId="g", apiProjectId="p", artifact="mix", format="flac", backgroundGain=0.2) + assert result.downloadUrl + assert requests[0] == requests[1] + + +def test_pending_response(): + client = client_for(lambda _: httpx.Response(202, json=dict(status="pending", exportId="e", + retryAfterSeconds=2))) + response = client.generations.get_download(generationId="g", apiProjectId="p") + assert isinstance(response, GenerationExportPendingResponse) + assert response.exportId == "e" + + +def test_pending_timeout(monkeypatch): + ticks = iter([0, 0, 0, 2, 2]) + monkeypatch.setattr("diffio.client.time.monotonic", lambda: next(ticks)) + client = client_for(lambda _: httpx.Response(202, json=dict(status="pending", exportId="e"))) + with pytest.raises(TimeoutError): + client.generations.wait_for_download(generationId="g", apiProjectId="p", timeout=1) + + +def test_mix_and_playback(): + def handler(request): + body = json.loads(request.content) + if request.url.path.endswith("update_generation_mix"): + assert body == dict(generationId="g", apiProjectId="p", backgroundGain=0.4, expectedRevision=0) + return httpx.Response(200, json=dict(generationId="g", mix=dict(backgroundGain=0.4, revision=1))) + assert request.url.path.endswith("get_generation_playback") + assert body == dict(generationId="g", apiProjectId="p", startChunk=0, chunkCount=8) + return httpx.Response(200, json=dict(generationId="g", manifest={"chunks": [{"url": "signed"}]})) + client = client_for(handler) + assert client.generations.update_mix(apiProjectId="p", generationId="g", + backgroundGain=0.4, expectedRevision=0).mix.revision == 1 + assert client.generations.get_playback(apiProjectId="p", generationId="g").manifest["chunks"] + + +def test_stale_mix_not_retried(): + calls = [] + def handler(request): + calls.append(request) + return httpx.Response(409, json={"error": "Stale revision"}) + with pytest.raises(DiffioApiError) as error: + client_for(handler).generations.update_mix(apiProjectId="p", generationId="g", + backgroundGain=0, expectedRevision=0, requestOptions={"maxRetries": 3, "retryStatusCodes": [409]}) + assert error.value.statusCode == 409 + assert len(calls) == 1 + + +@pytest.mark.parametrize("gain", [True, float("nan"), float("inf"), -0.1, 1.1, "0.5"]) +def test_invalid_gain(gain): + client = client_for(lambda _: pytest.fail("must not send request")) + with pytest.raises(ValueError): + client.generations.get_download(apiProjectId="p", generationId="g", backgroundGain=gain) + with pytest.raises(ValueError): + client.generations.update_mix(apiProjectId="p", generationId="g", backgroundGain=gain, expectedRevision=0) + + +@pytest.mark.parametrize("body", [ + {}, [], {"status": "pending"}, {"status": "pending", "exportId": ""}, + {"status": "pending", "exportId": "e", "retryAfterSeconds": None}, + {"status": "pending", "exportId": "e", "retryAfterSeconds": "2"}, + {"status": "pending", "exportId": "e", "retryAfterSeconds": -1}, + {"status": "pending", "exportId": "e", "retryAfterSeconds": True}, +]) +def test_malformed_pending(body): + client = client_for(lambda _: httpx.Response(202, json=body)) + with pytest.raises(DiffioApiError): + client.generations.wait_for_download(apiProjectId="p", generationId="g") + + +@pytest.mark.parametrize("delay", ["NaN", "Infinity"]) +def test_nonfinite_pending_delay(delay): + body = '{"status":"pending","exportId":"e","retryAfterSeconds":' + delay + '}' + client = client_for(lambda _: httpx.Response(202, content=body)) + with pytest.raises(DiffioApiError): + client.generations.wait_for_download(apiProjectId="p", generationId="g") + + +def test_playback_pagination(): + def handler(request): + assert json.loads(request.content) == dict( + apiProjectId="p", generationId="g", startChunk=16, chunkCount=4) + return httpx.Response(200, json=dict(generationId="g", + manifest=dict(startChunk=16, chunkCount=30, chunks=[{"index":16}], mix={"revision":0}))) + result = client_for(handler).generations.get_playback( + apiProjectId="p", generationId="g", startChunk=16, chunkCount=4) + assert result.manifest["chunkCount"] == 30 + assert len(result.manifest["chunks"]) == 1 + + +@pytest.mark.parametrize("options", [ + {"startChunk": -1}, {"startChunk": True}, {"startChunk": 0.1}, + {"chunkCount": 0}, {"chunkCount": 17}, {"chunkCount": True}, {"chunkCount": 2.5}, +]) +def test_invalid_playback_page(options): + client = client_for(lambda _: pytest.fail("must not request")) + with pytest.raises(ValueError): + client.get_generation_playback(apiProjectId="p", generationId="g", **options) + + +def test_polling_disables_transport_retries_without_mutating_defaults(): + calls = [] + def handler(request): + calls.append(request) + assert request.extensions["timeout"]["read"] <= 1 + return httpx.Response(503, json={"error": "Unavailable"}) + client = client_for(handler) + client._default_request_options.maxRetries = 3 + with pytest.raises(DiffioApiError): + client.generations.wait_for_download(apiProjectId="p", generationId="g", timeout=1) + assert len(calls) == 1 + assert client._default_request_options.maxRetries == 3 + assert client._default_request_options.timeout is None From 1d8fc7737b496b597686ecc3762316ef2137b610 Mon Sep 17 00:00:00 2001 From: Nathan Harmon Date: Mon, 21 Sep 2026 18:19:32 -0400 Subject: [PATCH 2/3] Revert "Add Diffio 4.0 playback, mix, and export support" This reverts commit 19e1538f4bf223fa25cda5480c00d40d81591edd. --- README.md | 88 ---------------------- src/diffio/__init__.py | 8 -- src/diffio/client.py | 126 +------------------------------ src/diffio/types.py | 48 +----------- tests/test_diffio40.py | 165 ----------------------------------------- 5 files changed, 4 insertions(+), 431 deletions(-) delete mode 100644 tests/test_diffio40.py diff --git a/README.md b/README.md index 78e64c4..423f05b 100644 --- a/README.md +++ b/README.md @@ -304,91 +304,3 @@ cd diffio-python python -m pip install -r requirements-dev.txt python -m pytest ``` - -## Diffio 4.0 stems and mixing - -These methods target the Diffio 4.0 API implementation. Use a deployment that -has enabled Diffio 4.0; SDK support does not imply production availability. - -Create a generation using `model="diffio-4.0"`. Existing models and defaults -are unchanged. While processing, fetch bounded pages of the playback manifest: - -```python -generation = client.generations.create( - apiProjectId="proj_123", - model="diffio-4.0", - idempotencyKey="unique-request-id", -) - -page = client.generations.get_playback( - apiProjectId="proj_123", - generationId=generation.generationId, - startChunk=0, - chunkCount=8, -) -manifest = page.manifest -# manifest["chunks"] contains this page with signed audio URLs. -# manifest["chunkCount"] is the TOTAL currently available chunk count, -# not the size of this page. startChunk is a zero-based index. -``` - -`startChunk` must be a nonnegative integer; `chunkCount` defaults to 8 and -accepts 1–16. Refetch pages as processing advances or signed URLs expire. - -Save one background level, from 0 (speech only) to 1, using optimistic concurrency: - -```python -updated = client.generations.update_mix( - apiProjectId="proj_123", - generationId=generation.generationId, - backgroundGain=0.25, - expectedRevision=manifest["mix"]["revision"], -) -print(updated.mix.backgroundGain, updated.mix.revision) -``` - -A stale revision raises `DiffioApiError` with `statusCode == 409`; fetch the -current playback manifest before deciding whether to resubmit. The SDK does -not automatically retry mix updates. - -Request MP3 or FLAC for `artifact="mix"`, `"speech"`, or `"background"`. -MP4 is for a video project's current mix. Exports require completed processing -and the account's download permissions. The optional `backgroundGain` selects -the mix for that export; it does not save the generation's mix setting. - -```python -from diffio import GenerationExportPendingResponse - -result = client.generations.get_download( - apiProjectId="proj_123", - generationId=generation.generationId, - artifact="mix", - format="flac", - backgroundGain=0.25, -) -if isinstance(result, GenerationExportPendingResponse): - print(result.exportId, result.retryAfterSeconds) - # Repeat the same request after this delay to poll the export. -else: - print(result.downloadUrl) - -# Or poll and save the finished export automatically: -client.generations.download( - apiProjectId="proj_123", - generationId=generation.generationId, - artifact="mix", - format="mp3", - backgroundGain=0.25, - downloadFilePath="restored.mp3", - exportTimeout=600, -) -``` - -`generations.wait_for_download(..., timeout=600)` polls without saving a file. -It raises `TimeoutError` when its polling deadline expires. Pending responses -are not download URLs. Polling disables transport retries and bounds numeric -request timeouts by the remaining deadline; an in-flight HTTP operation can -still finish after that deadline. This SDK uses synchronous HTTP calls. - -Flat methods are also available: `get_generation_playback`, -`update_generation_mix`, and `get_generation_download`. diff --git a/src/diffio/__init__.py b/src/diffio/__init__.py index a6da394..1a71fe3 100644 --- a/src/diffio/__init__.py +++ b/src/diffio/__init__.py @@ -19,10 +19,6 @@ CreateProjectResponse, DownloadType, GenerationDownloadResponse, - GenerationPlaybackResponse, - GenerationMixResponse, - GenerationMix, - GenerationExportPendingResponse, GenerationProgressResponse, GenerationProgressStage, GenerationTranscription, @@ -55,10 +51,6 @@ "DiffioApiError", "DiffioClient", "GenerationDownloadResponse", - "GenerationPlaybackResponse", - "GenerationMixResponse", - "GenerationMix", - "GenerationExportPendingResponse", "GenerationProgressResponse", "GenerationProgressStage", "GenerationTranscription", diff --git a/src/diffio/client.py b/src/diffio/client.py index 7f52a0a..9a48da7 100644 --- a/src/diffio/client.py +++ b/src/diffio/client.py @@ -1,5 +1,4 @@ import json -import math import mimetypes import os import tempfile @@ -20,9 +19,6 @@ CreateProjectResponse, DownloadType, GenerationDownloadResponse, - GenerationExportPendingResponse, - GenerationMixResponse, - GenerationPlaybackResponse, GenerationProgressResponse, GenerationWebhookEvent, ListProjectGenerationsResponse, @@ -42,7 +38,6 @@ "diffio-2-flash": "diffio-2.0-flash-generation", "diffio-3.4": "diffio-3.4-generation", "diffio-3.5": "diffio-3.5-generation", - "diffio-4.0": "diffio-4.0-generation", } DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504] DEFAULT_RETRY_BACKOFF = 0.5 @@ -509,9 +504,6 @@ def get_generation_download( generationId, apiProjectId, downloadType=None, - artifact=None, - format=None, - backgroundGain=None, requestOptions=None, ): """ @@ -555,63 +547,14 @@ def get_generation_download( resolved_download_type, _ = _normalize_download_type(downloadType) payload["downloadType"] = resolved_download_type - if artifact is not None: - if artifact not in ("mix", "speech", "background"): - raise ValueError("artifact must be mix, speech, or background") - payload["artifact"] = artifact - if format is not None: - if format not in ("mp3", "flac", "mp4"): - raise ValueError("format must be mp3, flac, or mp4") - payload["format"] = format - if backgroundGain is not None: - _validate_background_gain(backgroundGain) - payload["backgroundGain"] = backgroundGain - response = self._request( "POST", "get_generation_download", json_payload=payload, requestOptions=requestOptions, ) - if isinstance(response, dict) and response.get("status") == "pending": - try: - return GenerationExportPendingResponse.from_dict(response) - except (KeyError, ValueError, TypeError) as exc: - raise DiffioApiError("Malformed pending export response", responseBody=response) from exc return GenerationDownloadResponse.from_dict(response) - def update_generation_mix( - self, *, apiProjectId, generationId, backgroundGain, expectedRevision, - requestOptions=None, - ): - _validate_background_gain(backgroundGain) - if isinstance(expectedRevision, bool) or not isinstance(expectedRevision, int) or expectedRevision < 0: - raise ValueError("expectedRevision must be a nonnegative integer") - response = self._request( - "POST", "update_generation_mix", - json_payload={ - "apiProjectId": apiProjectId, "generationId": generationId, - "backgroundGain": backgroundGain, "expectedRevision": expectedRevision, - }, - requestOptions=requestOptions, allow_retries=False, - ) - return GenerationMixResponse.from_dict(response) - - def get_generation_playback( - self, *, apiProjectId, generationId, startChunk=0, chunkCount=8, requestOptions=None, - ): - if isinstance(startChunk, bool) or not isinstance(startChunk, int) or startChunk < 0: - raise ValueError("startChunk must be a nonnegative integer") - if isinstance(chunkCount, bool) or not isinstance(chunkCount, int) or not 1 <= chunkCount <= 16: - raise ValueError("chunkCount must be an integer between 1 and 16") - response = self._request( - "POST", "get_generation_playback", - json_payload={"apiProjectId": apiProjectId, "generationId": generationId, - "startChunk": startChunk, "chunkCount": chunkCount}, - requestOptions=requestOptions, - ) - return GenerationPlaybackResponse.from_dict(response) - def send_webhook_test_event( self, *, @@ -854,11 +797,7 @@ def _request(self, method, path, *, json_payload, requestOptions=None, allow_ret attempt += 1 continue - data = _raise_for_error(response) - if path == "get_generation_download" and response.status_code == 202: - if not isinstance(data, dict) or data.get("status") != "pending": - raise DiffioApiError("Malformed pending export response", statusCode=202, responseBody=data) - return data + return _raise_for_error(response) class AccountClient: @@ -947,16 +886,12 @@ def get_download( generationId, apiProjectId, downloadType=None, - artifact=None, - format=None, - backgroundGain=None, requestOptions=None, ): return self._parent.get_generation_download( generationId=generationId, apiProjectId=apiProjectId, downloadType=downloadType, - artifact=artifact, format=format, backgroundGain=backgroundGain, requestOptions=requestOptions, ) @@ -966,11 +901,7 @@ def download( generationId, apiProjectId, downloadFilePath, - exportTimeout=600.0, downloadType=None, - artifact=None, - format=None, - backgroundGain=None, requestOptions=None, ): """ @@ -1000,61 +931,16 @@ def download( else: resolved_download_type = None - download = self.wait_for_download( + download = self._parent.get_generation_download( generationId=generationId, apiProjectId=apiProjectId, downloadType=resolved_download_type, - artifact=artifact, format=format, backgroundGain=backgroundGain, - timeout=exportTimeout, requestOptions=requestOptions, ) _warn_download_extension_mismatch(download, resolved_path) _download_to_file(self._parent, download.downloadUrl, resolved_path, requestOptions=requestOptions) return download - def wait_for_download( - self, *, generationId, apiProjectId, downloadType=None, artifact=None, - format=None, backgroundGain=None, timeout=600.0, requestOptions=None, - ): - """Poll an idempotent export request until ready, or raise TimeoutError.""" - if not isinstance(timeout, (int, float)) or not math.isfinite(timeout) or timeout <= 0: - raise ValueError("timeout must be finite and positive") - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - remaining = deadline - time.monotonic() - if remaining <= 0: - break - options = _merge_request_options(self._parent._default_request_options, requestOptions or {}) - options.maxRetries = 0 - if options.timeout is None: - options.timeout = remaining - elif isinstance(options.timeout, (int, float)): - options.timeout = min(options.timeout, remaining) - download = self.get_download( - generationId=generationId, apiProjectId=apiProjectId, - downloadType=downloadType, artifact=artifact, format=format, - backgroundGain=backgroundGain, requestOptions=options, - ) - if not isinstance(download, GenerationExportPendingResponse): - return download - remaining = deadline - time.monotonic() - if remaining > 0: - time.sleep(min(max(0.1, download.retryAfterSeconds), remaining)) - raise TimeoutError("Timed out waiting for generation export") - - def update_mix(self, *, apiProjectId, generationId, backgroundGain, expectedRevision, requestOptions=None): - return self._parent.update_generation_mix( - apiProjectId=apiProjectId, generationId=generationId, - backgroundGain=backgroundGain, expectedRevision=expectedRevision, - requestOptions=requestOptions, - ) - - def get_playback(self, *, apiProjectId, generationId, startChunk=0, chunkCount=8, requestOptions=None): - return self._parent.get_generation_playback( - apiProjectId=apiProjectId, generationId=generationId, - startChunk=startChunk, chunkCount=chunkCount, requestOptions=requestOptions, - ) - def wait_for_complete( self, *, @@ -1345,8 +1231,7 @@ def restore_audio( metadata["stage"] = "download_info" try: - download = self._parent.generations.wait_for_download( - timeout=timeout, + download = self._parent.get_generation_download( generationId=result.generation.generationId, apiProjectId=result.project.apiProjectId, downloadType=downloadType, @@ -1787,8 +1672,3 @@ def _report_progress(progress, *, onProgress, showProgress): onProgress(progress) if showProgress: print(_format_progress(progress)) - - -def _validate_background_gain(value): - if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or not 0 <= value <= 1: - raise ValueError("backgroundGain must be a finite number between 0 and 1") diff --git a/src/diffio/types.py b/src/diffio/types.py index 837ad39..227c7e3 100644 --- a/src/diffio/types.py +++ b/src/diffio/types.py @@ -1,5 +1,3 @@ -import math - class CreateProjectResponse: def __init__( self, @@ -453,7 +451,7 @@ def __init__(self, project, generation): self.generation = generation -ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5", "diffio-4.0") +ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5") DownloadType = ("audio", "mp3", "video", "transcript") WebhookMode = ("test", "live") WebhookEventType = ( @@ -464,47 +462,3 @@ def __init__(self, project, generation): ) GenerationWebhookStatus = ("queued", "processing", "error", "complete") TranscriptionStatus = ("pending", "available", "unavailable") - - -class GenerationExportPendingResponse: - def __init__(self, exportId, retryAfterSeconds=2): - if not isinstance(exportId, str) or not exportId.strip(): - raise ValueError("exportId must be a nonempty string") - if (isinstance(retryAfterSeconds, bool) or not isinstance(retryAfterSeconds, (int, float)) - or not math.isfinite(retryAfterSeconds) or retryAfterSeconds < 0): - raise ValueError("retryAfterSeconds must be finite and nonnegative") - self.status = "pending" - self.exportId = exportId - self.retryAfterSeconds = retryAfterSeconds - - @classmethod - def from_dict(cls, data): - return cls(data["exportId"], data.get("retryAfterSeconds", 2)) - - -class GenerationMix: - def __init__(self, backgroundGain, revision): - self.backgroundGain = backgroundGain - self.revision = revision - - -class GenerationMixResponse: - def __init__(self, generationId, mix): - self.generationId = generationId - self.mix = mix - - @classmethod - def from_dict(cls, data): - return cls(data["generationId"], GenerationMix( - data["mix"]["backgroundGain"], data["mix"]["revision"], - )) - - -class GenerationPlaybackResponse: - def __init__(self, generationId, manifest): - self.generationId = generationId - self.manifest = manifest - - @classmethod - def from_dict(cls, data): - return cls(data["generationId"], data["manifest"]) diff --git a/tests/test_diffio40.py b/tests/test_diffio40.py deleted file mode 100644 index c87c1ad..0000000 --- a/tests/test_diffio40.py +++ /dev/null @@ -1,165 +0,0 @@ -import json - -import httpx -import pytest - -from diffio import DiffioClient, DiffioApiError, GenerationExportPendingResponse, ModelKey - - -def client_for(handler): - return DiffioClient(apiKey="test", httpClient=httpx.Client( - base_url="https://api.test", transport=httpx.MockTransport(handler), - )) - - -def ready(): - return dict(generationId="g", apiProjectId="p", downloadType="audio", - downloadUrl="https://files.test/audio", fileName="mix.flac", - storagePath="mix.flac", bucket="test", mimeType="audio/flac") - - -def test_create_v40(): - def handler(request): - assert request.url.path == "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/v1/diffio-4.0-generation" - assert json.loads(request.content) == {"apiProjectId": "p", "idempotencyKey": "once"} - return httpx.Response(200, json=dict(generationId="g", apiProjectId="p", - modelKey="diffio-4.0", status="queued")) - assert "diffio-4.0" in ModelKey - assert client_for(handler).generations.create(apiProjectId="p", model="diffio-4.0", - idempotencyKey="once").generationId == "g" - - -@pytest.mark.parametrize("format", ["mp3", "flac", "mp4"]) -@pytest.mark.parametrize("artifact", ["mix", "speech", "background"]) -def test_download_options(format, artifact): - def handler(request): - assert json.loads(request.content) == dict( - generationId="g", apiProjectId="p", format=format, artifact=artifact, backgroundGain=0.25) - return httpx.Response(200, json=ready()) - assert client_for(handler).generations.get_download( - generationId="g", apiProjectId="p", format=format, artifact=artifact, - backgroundGain=0.25).downloadUrl == ready()["downloadUrl"] - - -def test_pending_then_ready(monkeypatch): - requests = [] - def handler(request): - requests.append(json.loads(request.content)) - if len(requests) == 1: - return httpx.Response(202, json=dict(status="pending", exportId="e", retryAfterSeconds=2)) - return httpx.Response(200, json=ready()) - monkeypatch.setattr("diffio.client.time.sleep", lambda _: None) - result = client_for(handler).generations.wait_for_download( - generationId="g", apiProjectId="p", artifact="mix", format="flac", backgroundGain=0.2) - assert result.downloadUrl - assert requests[0] == requests[1] - - -def test_pending_response(): - client = client_for(lambda _: httpx.Response(202, json=dict(status="pending", exportId="e", - retryAfterSeconds=2))) - response = client.generations.get_download(generationId="g", apiProjectId="p") - assert isinstance(response, GenerationExportPendingResponse) - assert response.exportId == "e" - - -def test_pending_timeout(monkeypatch): - ticks = iter([0, 0, 0, 2, 2]) - monkeypatch.setattr("diffio.client.time.monotonic", lambda: next(ticks)) - client = client_for(lambda _: httpx.Response(202, json=dict(status="pending", exportId="e"))) - with pytest.raises(TimeoutError): - client.generations.wait_for_download(generationId="g", apiProjectId="p", timeout=1) - - -def test_mix_and_playback(): - def handler(request): - body = json.loads(request.content) - if request.url.path.endswith("update_generation_mix"): - assert body == dict(generationId="g", apiProjectId="p", backgroundGain=0.4, expectedRevision=0) - return httpx.Response(200, json=dict(generationId="g", mix=dict(backgroundGain=0.4, revision=1))) - assert request.url.path.endswith("get_generation_playback") - assert body == dict(generationId="g", apiProjectId="p", startChunk=0, chunkCount=8) - return httpx.Response(200, json=dict(generationId="g", manifest={"chunks": [{"url": "signed"}]})) - client = client_for(handler) - assert client.generations.update_mix(apiProjectId="p", generationId="g", - backgroundGain=0.4, expectedRevision=0).mix.revision == 1 - assert client.generations.get_playback(apiProjectId="p", generationId="g").manifest["chunks"] - - -def test_stale_mix_not_retried(): - calls = [] - def handler(request): - calls.append(request) - return httpx.Response(409, json={"error": "Stale revision"}) - with pytest.raises(DiffioApiError) as error: - client_for(handler).generations.update_mix(apiProjectId="p", generationId="g", - backgroundGain=0, expectedRevision=0, requestOptions={"maxRetries": 3, "retryStatusCodes": [409]}) - assert error.value.statusCode == 409 - assert len(calls) == 1 - - -@pytest.mark.parametrize("gain", [True, float("nan"), float("inf"), -0.1, 1.1, "0.5"]) -def test_invalid_gain(gain): - client = client_for(lambda _: pytest.fail("must not send request")) - with pytest.raises(ValueError): - client.generations.get_download(apiProjectId="p", generationId="g", backgroundGain=gain) - with pytest.raises(ValueError): - client.generations.update_mix(apiProjectId="p", generationId="g", backgroundGain=gain, expectedRevision=0) - - -@pytest.mark.parametrize("body", [ - {}, [], {"status": "pending"}, {"status": "pending", "exportId": ""}, - {"status": "pending", "exportId": "e", "retryAfterSeconds": None}, - {"status": "pending", "exportId": "e", "retryAfterSeconds": "2"}, - {"status": "pending", "exportId": "e", "retryAfterSeconds": -1}, - {"status": "pending", "exportId": "e", "retryAfterSeconds": True}, -]) -def test_malformed_pending(body): - client = client_for(lambda _: httpx.Response(202, json=body)) - with pytest.raises(DiffioApiError): - client.generations.wait_for_download(apiProjectId="p", generationId="g") - - -@pytest.mark.parametrize("delay", ["NaN", "Infinity"]) -def test_nonfinite_pending_delay(delay): - body = '{"status":"pending","exportId":"e","retryAfterSeconds":' + delay + '}' - client = client_for(lambda _: httpx.Response(202, content=body)) - with pytest.raises(DiffioApiError): - client.generations.wait_for_download(apiProjectId="p", generationId="g") - - -def test_playback_pagination(): - def handler(request): - assert json.loads(request.content) == dict( - apiProjectId="p", generationId="g", startChunk=16, chunkCount=4) - return httpx.Response(200, json=dict(generationId="g", - manifest=dict(startChunk=16, chunkCount=30, chunks=[{"index":16}], mix={"revision":0}))) - result = client_for(handler).generations.get_playback( - apiProjectId="p", generationId="g", startChunk=16, chunkCount=4) - assert result.manifest["chunkCount"] == 30 - assert len(result.manifest["chunks"]) == 1 - - -@pytest.mark.parametrize("options", [ - {"startChunk": -1}, {"startChunk": True}, {"startChunk": 0.1}, - {"chunkCount": 0}, {"chunkCount": 17}, {"chunkCount": True}, {"chunkCount": 2.5}, -]) -def test_invalid_playback_page(options): - client = client_for(lambda _: pytest.fail("must not request")) - with pytest.raises(ValueError): - client.get_generation_playback(apiProjectId="p", generationId="g", **options) - - -def test_polling_disables_transport_retries_without_mutating_defaults(): - calls = [] - def handler(request): - calls.append(request) - assert request.extensions["timeout"]["read"] <= 1 - return httpx.Response(503, json={"error": "Unavailable"}) - client = client_for(handler) - client._default_request_options.maxRetries = 3 - with pytest.raises(DiffioApiError): - client.generations.wait_for_download(apiProjectId="p", generationId="g", timeout=1) - assert len(calls) == 1 - assert client._default_request_options.maxRetries == 3 - assert client._default_request_options.timeout is None From 1e4613b86fb3efa1a8cf891fea615a6e76460684 Mon Sep 17 00:00:00 2001 From: Nathan Harmon Date: Mon, 21 Sep 2026 18:19:52 -0400 Subject: [PATCH 3/3] Add Diffio 4.0 Flash and Pro model keys 4.0 returns the same generation result as 2.0 and 3.5, so the unreleased playback, mix and export surface is reverted and the models only need their keys and endpoints. Examples lead with diffio-4.0-flash. Co-Authored-By: Claude Fable 5.1 --- README.md | 6 ++-- .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1502 bytes src/diffio/__pycache__/client.cpython-312.pyc | Bin 0 -> 54113 bytes src/diffio/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1239 bytes src/diffio/__pycache__/types.cpython-312.pyc | Bin 0 -> 17712 bytes src/diffio/client.py | 2 ++ src/diffio/types.py | 2 +- .../conftest.cpython-312-pytest-9.1.1.pyc | Bin 0 -> 974 bytes .../test_client.cpython-312-pytest-9.1.1.pyc | Bin 0 -> 103417 bytes ...ffio40_models.cpython-312-pytest-9.1.1.pyc | Bin 0 -> 3836 bytes ...transcription.cpython-312-pytest-9.1.1.pyc | Bin 0 -> 28938 bytes tests/test_diffio40_models.py | 34 ++++++++++++++++++ 12 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/diffio/__pycache__/__init__.cpython-312.pyc create mode 100644 src/diffio/__pycache__/client.cpython-312.pyc create mode 100644 src/diffio/__pycache__/errors.cpython-312.pyc create mode 100644 src/diffio/__pycache__/types.cpython-312.pyc create mode 100644 tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc create mode 100644 tests/__pycache__/test_client.cpython-312-pytest-9.1.1.pyc create mode 100644 tests/__pycache__/test_diffio40_models.cpython-312-pytest-9.1.1.pyc create mode 100644 tests/__pycache__/test_transcription.cpython-312-pytest-9.1.1.pyc create mode 100644 tests/test_diffio40_models.py diff --git a/README.md b/README.md index 423f05b..8fcde6e 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ project = client.create_project( generation = client.create_generation( apiProjectId=project.apiProjectId, - model="diffio-3.5", + model="diffio-4.0-flash", sampling={"steps": 12, "guidance": 1.5}, idempotencyKey="restore-job-2026-001", requestOptions={"maxRetries": 2}, @@ -89,7 +89,7 @@ from diffio import DiffioClient client = DiffioClient(apiKey="diffio_live_...") result = client.audio_isolation.isolate( filePath="sample.wav", - model="diffio-3.5", + model="diffio-4.0-flash", sampling={"steps": 12, "guidance": 1.5}, ) @@ -106,7 +106,7 @@ from diffio import DiffioClient client = DiffioClient(apiKey="diffio_live_...") audio_bytes, info = client.restore_audio( filePath="sample.wav", - model="diffio-3.5", + model="diffio-4.0-flash", sampling={"steps": 12, "guidance": 1.5}, onProgress=lambda progress: print(progress.status), ) diff --git a/src/diffio/__pycache__/__init__.cpython-312.pyc b/src/diffio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34d5e29b1ca67d8d6d3de1feaca1170906f2caf9 GIT binary patch literal 1502 zcmaKsOK%fb6vt=m_>nkv9(HW!{cxbQs>C26*i=YSK!{RkMN(Bdo003eafZ&B>)yFe zWmfPh*t26%sr?*$gS%zL4iTY9U2)IN9giu{S^Q?sIrp6TpZj`UF3&jnk$?U34&*XcX{)Jqqx_&K;b%C`$|T`jvkt;z?z9v?>XFbr5E zwK)hLuu*Ky+QHBd`2CoNT^aDmdS~whCno{7*1|m&F=66KTIFZKpRk@B+mG2FLl(=Q z22>?hrC;K1pV|D%uk5!|&VRRq+pely*4I+*3c*FYsTx-05tA~A`tf5H4|E}>Dwlf4 zcGc?tF^J{Z`JXPde;7*DJQS=eS*m_Ke(5q|n;5%i@9;mPkazv#(SWIyG%@jyM^40a zk-lJYs<@HnC9xx^XG2W{jzu?$dm=E?rRr&hovOQMv{n-d)~^f6VXQW&d#gUpO(oPO z;;Fazkozpui?5oWool}mi6PaCoUDv(H%($5gN8^J*-EmtNnPu{Q1?Loaf3sD}JS)8W O!FA^9&r9MvUGF17LAI9w literal 0 HcmV?d00001 diff --git a/src/diffio/__pycache__/client.cpython-312.pyc b/src/diffio/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8a093dd522aa08bdd70adbd741d79b754cee4ec GIT binary patch literal 54113 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 literal 0 HcmV?d00001 diff --git a/src/diffio/__pycache__/types.cpython-312.pyc b/src/diffio/__pycache__/types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dca6cab69616d6f0981fdf112c58ba27f29c7d0 GIT binary patch literal 17712 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 G`~L^O?5DN> literal 0 HcmV?d00001 diff --git a/src/diffio/client.py b/src/diffio/client.py index 9a48da7..6a3c774 100644 --- a/src/diffio/client.py +++ b/src/diffio/client.py @@ -38,6 +38,8 @@ "diffio-2-flash": "diffio-2.0-flash-generation", "diffio-3.4": "diffio-3.4-generation", "diffio-3.5": "diffio-3.5-generation", + "diffio-4.0-flash": "diffio-4.0-flash-generation", + "diffio-4.0-pro": "diffio-4.0-pro-generation", } DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504] DEFAULT_RETRY_BACKOFF = 0.5 diff --git a/src/diffio/types.py b/src/diffio/types.py index 227c7e3..14e8fba 100644 --- a/src/diffio/types.py +++ b/src/diffio/types.py @@ -451,7 +451,7 @@ def __init__(self, project, generation): self.generation = generation -ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5") +ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3.4", "diffio-3.5", "diffio-4.0-flash", "diffio-4.0-pro") DownloadType = ("audio", "mp3", "video", "transcript") WebhookMode = ("test", "live") WebhookEventType = ( diff --git a/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc b/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40eb1a5adfd4e9db68c23f42be7f6ed2d6ba58bb GIT binary patch literal 974 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 httpx.Response: + received["path"] = request.url.path + received["payload"] = json.loads(request.content.decode("utf-8")) + return httpx.Response( + 200, + json={"generationId": "gen_40", "apiProjectId": "proj_40", "modelKey": model, "status": "queued"}, + ) + + http_client = httpx.Client(base_url="https://api.test", transport=httpx.MockTransport(handler)) + client = DiffioClient(apiKey="diffio_live_test", baseUrl="https://api.test", httpClient=http_client) + + response = client.create_generation(apiProjectId="proj_40", model=model) + + assert received["path"] == path + assert received["payload"]["apiProjectId"] == "proj_40" + assert response.modelKey == model