diffio 0.1.49__tar.gz → 0.1.51__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diffio-0.1.49/src/diffio.egg-info → diffio-0.1.51}/PKG-INFO +15 -1
- {diffio-0.1.49 → diffio-0.1.51}/README.md +14 -0
- {diffio-0.1.49 → diffio-0.1.51}/pyproject.toml +1 -1
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio/__init__.py +1 -1
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio/client.py +12 -2
- {diffio-0.1.49 → diffio-0.1.51/src/diffio.egg-info}/PKG-INFO +15 -1
- {diffio-0.1.49 → diffio-0.1.51}/tests/test_client.py +122 -0
- {diffio-0.1.49 → diffio-0.1.51}/LICENSE +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/setup.cfg +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio/errors.py +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio/testing.py +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio/types.py +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio.egg-info/SOURCES.txt +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio.egg-info/dependency_links.txt +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio.egg-info/requires.txt +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/src/diffio.egg-info/top_level.txt +0 -0
- {diffio-0.1.49 → diffio-0.1.51}/tests/test_emulator_e2e.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffio
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.51
|
|
4
4
|
Summary: Python SDK for the Diffio API.
|
|
5
5
|
Author: Diffio
|
|
6
6
|
License: MIT
|
|
@@ -58,6 +58,14 @@ export DIFFIO_API_BASE_URL="https://api.diffio.ai/v1"
|
|
|
58
58
|
Use request options to override headers, timeouts, retries, or the API key per request.
|
|
59
59
|
You can also pass `timeoutInSeconds` as an alias for `timeout`.
|
|
60
60
|
|
|
61
|
+
Generation creation is retried only when you supply a non-empty `idempotencyKey`,
|
|
62
|
+
even when `maxRetries` is configured globally or per request. Without a key, a
|
|
63
|
+
timeout or lost response can hide an accepted generation, so the SDK returns the
|
|
64
|
+
error after the first attempt. With a key, retries send the same key and payload.
|
|
65
|
+
Reuse that key when manually retrying the same operation; use a new key for a new
|
|
66
|
+
generation. The audio isolation and restore helpers do not supply a key and do
|
|
67
|
+
not automatically retry their generation-creation step.
|
|
68
|
+
|
|
61
69
|
```py
|
|
62
70
|
from diffio import DiffioClient, RequestOptions
|
|
63
71
|
|
|
@@ -90,6 +98,7 @@ generation = client.create_generation(
|
|
|
90
98
|
model="diffio-3.5",
|
|
91
99
|
sampling={"steps": 12, "guidance": 1.5},
|
|
92
100
|
idempotencyKey="restore-job-2026-001",
|
|
101
|
+
requestOptions={"maxRetries": 2},
|
|
93
102
|
)
|
|
94
103
|
|
|
95
104
|
print(generation.generationId)
|
|
@@ -141,6 +150,11 @@ print(info["apiProjectId"], info["generationId"])
|
|
|
141
150
|
|
|
142
151
|
## Generation progress
|
|
143
152
|
|
|
153
|
+
`wait_for_generation` and `generations.wait_for_complete` wait for the overall
|
|
154
|
+
`status` to become `complete`. Individual stages can reach 100% while video
|
|
155
|
+
restoration or final settlement is still pending; stage progress alone does not
|
|
156
|
+
indicate overall completion.
|
|
157
|
+
|
|
144
158
|
```py
|
|
145
159
|
from diffio import DiffioClient
|
|
146
160
|
|
|
@@ -30,6 +30,14 @@ export DIFFIO_API_BASE_URL="https://api.diffio.ai/v1"
|
|
|
30
30
|
Use request options to override headers, timeouts, retries, or the API key per request.
|
|
31
31
|
You can also pass `timeoutInSeconds` as an alias for `timeout`.
|
|
32
32
|
|
|
33
|
+
Generation creation is retried only when you supply a non-empty `idempotencyKey`,
|
|
34
|
+
even when `maxRetries` is configured globally or per request. Without a key, a
|
|
35
|
+
timeout or lost response can hide an accepted generation, so the SDK returns the
|
|
36
|
+
error after the first attempt. With a key, retries send the same key and payload.
|
|
37
|
+
Reuse that key when manually retrying the same operation; use a new key for a new
|
|
38
|
+
generation. The audio isolation and restore helpers do not supply a key and do
|
|
39
|
+
not automatically retry their generation-creation step.
|
|
40
|
+
|
|
33
41
|
```py
|
|
34
42
|
from diffio import DiffioClient, RequestOptions
|
|
35
43
|
|
|
@@ -62,6 +70,7 @@ generation = client.create_generation(
|
|
|
62
70
|
model="diffio-3.5",
|
|
63
71
|
sampling={"steps": 12, "guidance": 1.5},
|
|
64
72
|
idempotencyKey="restore-job-2026-001",
|
|
73
|
+
requestOptions={"maxRetries": 2},
|
|
65
74
|
)
|
|
66
75
|
|
|
67
76
|
print(generation.generationId)
|
|
@@ -113,6 +122,11 @@ print(info["apiProjectId"], info["generationId"])
|
|
|
113
122
|
|
|
114
123
|
## Generation progress
|
|
115
124
|
|
|
125
|
+
`wait_for_generation` and `generations.wait_for_complete` wait for the overall
|
|
126
|
+
`status` to become `complete`. Individual stages can reach 100% while video
|
|
127
|
+
restoration or final settlement is still pending; stage progress alone does not
|
|
128
|
+
indicate overall completion.
|
|
129
|
+
|
|
116
130
|
```py
|
|
117
131
|
from diffio import DiffioClient
|
|
118
132
|
|
|
@@ -311,6 +311,7 @@ class DiffioClient:
|
|
|
311
311
|
idempotencyKey=None,
|
|
312
312
|
requestOptions=None,
|
|
313
313
|
):
|
|
314
|
+
"""Create a generation; automatic retries require a supplied idempotency key."""
|
|
314
315
|
endpoint = MODEL_ENDPOINTS.get(model)
|
|
315
316
|
if not endpoint:
|
|
316
317
|
raise ValueError(f"Unsupported model: {model}")
|
|
@@ -323,7 +324,13 @@ class DiffioClient:
|
|
|
323
324
|
if idempotencyKey is not None:
|
|
324
325
|
payload["idempotencyKey"] = idempotencyKey
|
|
325
326
|
|
|
326
|
-
response = self._request(
|
|
327
|
+
response = self._request(
|
|
328
|
+
"POST",
|
|
329
|
+
endpoint,
|
|
330
|
+
json_payload=payload,
|
|
331
|
+
requestOptions=requestOptions,
|
|
332
|
+
allow_retries=isinstance(idempotencyKey, str) and bool(idempotencyKey.strip()),
|
|
333
|
+
)
|
|
327
334
|
return CreateGenerationResponse.from_dict(response)
|
|
328
335
|
|
|
329
336
|
def list_projects(self, *, requestOptions=None):
|
|
@@ -734,7 +741,7 @@ class DiffioClient:
|
|
|
734
741
|
raiseOnError=raiseOnError,
|
|
735
742
|
)
|
|
736
743
|
|
|
737
|
-
def _request(self, method, path, *, json_payload, requestOptions=None):
|
|
744
|
+
def _request(self, method, path, *, json_payload, requestOptions=None, allow_retries=True):
|
|
738
745
|
request_path = path.lstrip("/")
|
|
739
746
|
if self._api_prefix:
|
|
740
747
|
request_path = f"{self._api_prefix}/{request_path}"
|
|
@@ -743,6 +750,9 @@ class DiffioClient:
|
|
|
743
750
|
headers = _merge_headers({"Authorization": f"Bearer {api_key}"}, merged_options.headers)
|
|
744
751
|
timeout = merged_options.timeout
|
|
745
752
|
max_retries = merged_options.maxRetries if merged_options.maxRetries is not None else 0
|
|
753
|
+
# A lost response can hide an accepted generation; only its stable key makes replay safe.
|
|
754
|
+
if not allow_retries:
|
|
755
|
+
max_retries = 0
|
|
746
756
|
retry_backoff = (
|
|
747
757
|
merged_options.retryBackoff if merged_options.retryBackoff is not None else DEFAULT_RETRY_BACKOFF
|
|
748
758
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diffio
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.51
|
|
4
4
|
Summary: Python SDK for the Diffio API.
|
|
5
5
|
Author: Diffio
|
|
6
6
|
License: MIT
|
|
@@ -58,6 +58,14 @@ export DIFFIO_API_BASE_URL="https://api.diffio.ai/v1"
|
|
|
58
58
|
Use request options to override headers, timeouts, retries, or the API key per request.
|
|
59
59
|
You can also pass `timeoutInSeconds` as an alias for `timeout`.
|
|
60
60
|
|
|
61
|
+
Generation creation is retried only when you supply a non-empty `idempotencyKey`,
|
|
62
|
+
even when `maxRetries` is configured globally or per request. Without a key, a
|
|
63
|
+
timeout or lost response can hide an accepted generation, so the SDK returns the
|
|
64
|
+
error after the first attempt. With a key, retries send the same key and payload.
|
|
65
|
+
Reuse that key when manually retrying the same operation; use a new key for a new
|
|
66
|
+
generation. The audio isolation and restore helpers do not supply a key and do
|
|
67
|
+
not automatically retry their generation-creation step.
|
|
68
|
+
|
|
61
69
|
```py
|
|
62
70
|
from diffio import DiffioClient, RequestOptions
|
|
63
71
|
|
|
@@ -90,6 +98,7 @@ generation = client.create_generation(
|
|
|
90
98
|
model="diffio-3.5",
|
|
91
99
|
sampling={"steps": 12, "guidance": 1.5},
|
|
92
100
|
idempotencyKey="restore-job-2026-001",
|
|
101
|
+
requestOptions={"maxRetries": 2},
|
|
93
102
|
)
|
|
94
103
|
|
|
95
104
|
print(generation.generationId)
|
|
@@ -141,6 +150,11 @@ print(info["apiProjectId"], info["generationId"])
|
|
|
141
150
|
|
|
142
151
|
## Generation progress
|
|
143
152
|
|
|
153
|
+
`wait_for_generation` and `generations.wait_for_complete` wait for the overall
|
|
154
|
+
`status` to become `complete`. Individual stages can reach 100% while video
|
|
155
|
+
restoration or final settlement is still pending; stage progress alone does not
|
|
156
|
+
indicate overall completion.
|
|
157
|
+
|
|
144
158
|
```py
|
|
145
159
|
from diffio import DiffioClient
|
|
146
160
|
|
|
@@ -7,6 +7,7 @@ import pytest
|
|
|
7
7
|
|
|
8
8
|
from diffio import DiffioClient, ModelKey
|
|
9
9
|
from diffio.client import MODEL_ENDPOINTS
|
|
10
|
+
from diffio.errors import DiffioApiError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def test_create_project_payload_and_headers(tmp_path: Path):
|
|
@@ -997,3 +998,124 @@ def test_webhooks_send_test_event_rejects_invalid_type():
|
|
|
997
998
|
eventType="generation.unknown",
|
|
998
999
|
mode="test",
|
|
999
1000
|
)
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
@pytest.mark.parametrize("failure_kind", ["status", "transport"])
|
|
1004
|
+
@pytest.mark.parametrize("model", list(MODEL_ENDPOINTS))
|
|
1005
|
+
@pytest.mark.parametrize("options_scope", ["client", "request"])
|
|
1006
|
+
@pytest.mark.parametrize("idempotency_key", [None, "", " ", 123])
|
|
1007
|
+
def test_create_generation_does_not_retry_without_usable_idempotency_key(
|
|
1008
|
+
failure_kind, model, options_scope, idempotency_key
|
|
1009
|
+
):
|
|
1010
|
+
calls = []
|
|
1011
|
+
|
|
1012
|
+
def handler(request):
|
|
1013
|
+
calls.append(request)
|
|
1014
|
+
if failure_kind == "transport":
|
|
1015
|
+
raise httpx.ReadTimeout("Response lost after acceptance", request=request)
|
|
1016
|
+
return httpx.Response(503, json={"error": "unavailable"})
|
|
1017
|
+
|
|
1018
|
+
retry_options = {"maxRetries": 2, "retryBackoff": 0}
|
|
1019
|
+
with httpx.Client(base_url="https://api.test", transport=httpx.MockTransport(handler)) as http_client:
|
|
1020
|
+
client = DiffioClient(
|
|
1021
|
+
apiKey="diffio_live_test",
|
|
1022
|
+
baseUrl="https://api.test",
|
|
1023
|
+
httpClient=http_client,
|
|
1024
|
+
requestOptions=retry_options if options_scope == "client" else None,
|
|
1025
|
+
)
|
|
1026
|
+
expected_error = httpx.ReadTimeout if failure_kind == "transport" else DiffioApiError
|
|
1027
|
+
with pytest.raises(expected_error):
|
|
1028
|
+
client.generations.create(
|
|
1029
|
+
apiProjectId="proj",
|
|
1030
|
+
model=model,
|
|
1031
|
+
idempotencyKey=idempotency_key,
|
|
1032
|
+
requestOptions=retry_options if options_scope == "request" else None,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
assert len(calls) == 1
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
@pytest.mark.parametrize("failure_kind", ["status", "transport"])
|
|
1039
|
+
@pytest.mark.parametrize("model", list(MODEL_ENDPOINTS))
|
|
1040
|
+
@pytest.mark.parametrize("options_scope", ["client", "request"])
|
|
1041
|
+
def test_create_generation_retries_same_idempotent_request(failure_kind, model, options_scope):
|
|
1042
|
+
calls = []
|
|
1043
|
+
|
|
1044
|
+
def handler(request):
|
|
1045
|
+
calls.append(request.content)
|
|
1046
|
+
if len(calls) == 1:
|
|
1047
|
+
if failure_kind == "transport":
|
|
1048
|
+
raise httpx.ReadTimeout("Response lost after acceptance", request=request)
|
|
1049
|
+
return httpx.Response(503, json={"error": "unavailable"})
|
|
1050
|
+
return httpx.Response(
|
|
1051
|
+
200,
|
|
1052
|
+
json={
|
|
1053
|
+
"generationId": "gen_original",
|
|
1054
|
+
"apiProjectId": "proj",
|
|
1055
|
+
"modelKey": model,
|
|
1056
|
+
"status": "queued",
|
|
1057
|
+
"idempotentReplay": True,
|
|
1058
|
+
},
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
retry_options = {"maxRetries": 2, "retryBackoff": 0}
|
|
1062
|
+
with httpx.Client(base_url="https://api.test", transport=httpx.MockTransport(handler)) as http_client:
|
|
1063
|
+
client = DiffioClient(
|
|
1064
|
+
apiKey="diffio_live_test",
|
|
1065
|
+
baseUrl="https://api.test",
|
|
1066
|
+
httpClient=http_client,
|
|
1067
|
+
requestOptions=retry_options if options_scope == "client" else None,
|
|
1068
|
+
)
|
|
1069
|
+
generation = client.generations.create(
|
|
1070
|
+
apiProjectId="proj",
|
|
1071
|
+
model=model,
|
|
1072
|
+
idempotencyKey="customer-operation-1",
|
|
1073
|
+
sampling={"steps": 12},
|
|
1074
|
+
params={"seed": 42},
|
|
1075
|
+
requestOptions=retry_options if options_scope == "request" else None,
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
assert len(calls) == 2
|
|
1079
|
+
assert calls[0] == calls[1]
|
|
1080
|
+
assert json.loads(calls[1])["idempotencyKey"] == "customer-operation-1"
|
|
1081
|
+
assert generation.generationId == "gen_original"
|
|
1082
|
+
assert generation.idempotentReplay is True
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def test_wait_for_generation_waits_for_overall_completion_after_stage_completion():
|
|
1086
|
+
responses = [
|
|
1087
|
+
("pending", "pending", 0),
|
|
1088
|
+
("processing", "running", 20),
|
|
1089
|
+
("processing", "complete", 100),
|
|
1090
|
+
("complete", "complete", 100),
|
|
1091
|
+
]
|
|
1092
|
+
progress_calls = []
|
|
1093
|
+
|
|
1094
|
+
def handler(request):
|
|
1095
|
+
status, video_status, video_progress = responses.pop(0)
|
|
1096
|
+
return httpx.Response(
|
|
1097
|
+
200,
|
|
1098
|
+
json={
|
|
1099
|
+
"generationId": "gen_video",
|
|
1100
|
+
"apiProjectId": "proj",
|
|
1101
|
+
"status": status,
|
|
1102
|
+
"hasVideo": True,
|
|
1103
|
+
"preProcessing": {"status": "complete", "progress": 100},
|
|
1104
|
+
"inference": {"status": "complete", "progress": 100},
|
|
1105
|
+
"restoredVideo": {"status": video_status, "progress": video_progress},
|
|
1106
|
+
},
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
with httpx.Client(base_url="https://api.test", transport=httpx.MockTransport(handler)) as http_client:
|
|
1110
|
+
client = DiffioClient(apiKey="diffio_live_test", httpClient=http_client)
|
|
1111
|
+
progress = client.generations.wait_for_complete(
|
|
1112
|
+
generationId="gen_video",
|
|
1113
|
+
apiProjectId="proj",
|
|
1114
|
+
pollInterval=0,
|
|
1115
|
+
timeout=5,
|
|
1116
|
+
onProgress=lambda progress: progress_calls.append(progress.status),
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
assert progress.status == "complete"
|
|
1120
|
+
assert progress_calls == ["pending", "processing", "processing", "complete"]
|
|
1121
|
+
assert not responses
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|