diffio 0.1.6__tar.gz → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffio
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
@@ -87,7 +87,7 @@ project = client.create_project(
87
87
 
88
88
  generation = client.create_generation(
89
89
  apiProjectId=project.apiProjectId,
90
- model="diffio-2",
90
+ model="diffio-3.5",
91
91
  sampling={"steps": 12, "guidance": 1.5},
92
92
  )
93
93
 
@@ -102,7 +102,7 @@ from diffio import DiffioClient
102
102
  client = DiffioClient(apiKey="diffio_live_...")
103
103
  result = client.audio_isolation.isolate(
104
104
  filePath="sample.wav",
105
- model="diffio-2",
105
+ model="diffio-3.5",
106
106
  sampling={"steps": 12, "guidance": 1.5},
107
107
  )
108
108
 
@@ -119,7 +119,7 @@ from diffio import DiffioClient
119
119
  client = DiffioClient(apiKey="diffio_live_...")
120
120
  audio_bytes, info = client.restore_audio(
121
121
  filePath="sample.wav",
122
- model="diffio-2",
122
+ model="diffio-3.5",
123
123
  sampling={"steps": 12, "guidance": 1.5},
124
124
  onProgress=lambda progress: print(progress.status),
125
125
  )
@@ -165,6 +165,36 @@ print(download.downloadUrl)
165
165
 
166
166
  If you only need the URL, use `client.generations.get_download`.
167
167
 
168
+ Set `downloadType="transcript"` to download the transcript JSON artifact when the generation has one.
169
+
170
+ ```py
171
+ transcript = client.generations.download(
172
+ generationId="gen_123",
173
+ apiProjectId="proj_123",
174
+ downloadType="transcript",
175
+ downloadFilePath="word_timestamps.json",
176
+ )
177
+ ```
178
+
179
+ ## Account, keys, usage, and webhook configuration
180
+
181
+ Agent keys can manage account settings, scoped keys, usage, and webhook endpoints.
182
+
183
+ ```py
184
+ settings = client.account.get_settings()
185
+ key = client.api_keys.create(
186
+ label="Backend worker",
187
+ scopes=["projects:read", "projects:write", "generations:read", "generations:write", "artifacts:read"],
188
+ )
189
+ usage = client.usage.summary(apiKeyId=key.keyId)
190
+ webhook = client.webhooks.configure(
191
+ mode="live",
192
+ url="https://example.com/webhooks/diffio",
193
+ eventTypes=["generation.completed", "generation.failed"],
194
+ apiKeyId=key.keyId,
195
+ )
196
+ ```
197
+
168
198
  ## List projects
169
199
 
170
200
  ```py
@@ -59,7 +59,7 @@ project = client.create_project(
59
59
 
60
60
  generation = client.create_generation(
61
61
  apiProjectId=project.apiProjectId,
62
- model="diffio-2",
62
+ model="diffio-3.5",
63
63
  sampling={"steps": 12, "guidance": 1.5},
64
64
  )
65
65
 
@@ -74,7 +74,7 @@ from diffio import DiffioClient
74
74
  client = DiffioClient(apiKey="diffio_live_...")
75
75
  result = client.audio_isolation.isolate(
76
76
  filePath="sample.wav",
77
- model="diffio-2",
77
+ model="diffio-3.5",
78
78
  sampling={"steps": 12, "guidance": 1.5},
79
79
  )
80
80
 
@@ -91,7 +91,7 @@ from diffio import DiffioClient
91
91
  client = DiffioClient(apiKey="diffio_live_...")
92
92
  audio_bytes, info = client.restore_audio(
93
93
  filePath="sample.wav",
94
- model="diffio-2",
94
+ model="diffio-3.5",
95
95
  sampling={"steps": 12, "guidance": 1.5},
96
96
  onProgress=lambda progress: print(progress.status),
97
97
  )
@@ -137,6 +137,36 @@ print(download.downloadUrl)
137
137
 
138
138
  If you only need the URL, use `client.generations.get_download`.
139
139
 
140
+ Set `downloadType="transcript"` to download the transcript JSON artifact when the generation has one.
141
+
142
+ ```py
143
+ transcript = client.generations.download(
144
+ generationId="gen_123",
145
+ apiProjectId="proj_123",
146
+ downloadType="transcript",
147
+ downloadFilePath="word_timestamps.json",
148
+ )
149
+ ```
150
+
151
+ ## Account, keys, usage, and webhook configuration
152
+
153
+ Agent keys can manage account settings, scoped keys, usage, and webhook endpoints.
154
+
155
+ ```py
156
+ settings = client.account.get_settings()
157
+ key = client.api_keys.create(
158
+ label="Backend worker",
159
+ scopes=["projects:read", "projects:write", "generations:read", "generations:write", "artifacts:read"],
160
+ )
161
+ usage = client.usage.summary(apiKeyId=key.keyId)
162
+ webhook = client.webhooks.configure(
163
+ mode="live",
164
+ url="https://example.com/webhooks/diffio",
165
+ eventTypes=["generation.completed", "generation.failed"],
166
+ apiKeyId=key.keyId,
167
+ )
168
+ ```
169
+
140
170
  ## List projects
141
171
 
142
172
  ```py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "diffio"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "Python SDK for the Diffio API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,13 +1,19 @@
1
1
  from .client import (
2
+ AccountClient,
3
+ ApiKeysClient,
2
4
  AudioIsolationClient,
3
5
  DiffioClient,
4
6
  GenerationsClient,
5
7
  ProjectsClient,
6
8
  RequestOptions,
9
+ UsageClient,
7
10
  WebhooksClient,
8
11
  )
9
12
  from .errors import DiffioApiError
10
13
  from .types import (
14
+ AccountSettingsResponse,
15
+ ApiKeyResponse,
16
+ ApiKeysListResponse,
11
17
  AudioIsolationResult,
12
18
  CreateGenerationResponse,
13
19
  CreateProjectResponse,
@@ -22,12 +28,19 @@ from .types import (
22
28
  ModelKey,
23
29
  ProjectGenerationSummary,
24
30
  ProjectSummary,
31
+ UsageSummaryResponse,
32
+ WebhookConfigureResponse,
25
33
  WebhookEventType,
26
34
  WebhookMode,
27
35
  WebhookTestEventResponse,
28
36
  )
29
37
 
30
38
  __all__ = [
39
+ "AccountClient",
40
+ "AccountSettingsResponse",
41
+ "ApiKeyResponse",
42
+ "ApiKeysClient",
43
+ "ApiKeysListResponse",
31
44
  "AudioIsolationClient",
32
45
  "AudioIsolationResult",
33
46
  "CreateGenerationResponse",
@@ -48,10 +61,13 @@ __all__ = [
48
61
  "ProjectSummary",
49
62
  "ProjectsClient",
50
63
  "RequestOptions",
64
+ "UsageClient",
65
+ "UsageSummaryResponse",
66
+ "WebhookConfigureResponse",
51
67
  "WebhookEventType",
52
68
  "WebhookMode",
53
69
  "WebhookTestEventResponse",
54
70
  "WebhooksClient",
55
71
  ]
56
72
 
57
- __version__ = "0.1.6"
73
+ __version__ = "0.1.7"
@@ -11,6 +11,9 @@ from svix.webhooks import Webhook
11
11
 
12
12
  from .errors import DiffioApiError
13
13
  from .types import (
14
+ AccountSettingsResponse,
15
+ ApiKeyResponse,
16
+ ApiKeysListResponse,
14
17
  AudioIsolationResult,
15
18
  CreateGenerationResponse,
16
19
  CreateProjectResponse,
@@ -21,6 +24,8 @@ from .types import (
21
24
  ListProjectGenerationsResponse,
22
25
  ListProjectsResponse,
23
26
  ModelKey,
27
+ UsageSummaryResponse,
28
+ WebhookConfigureResponse,
24
29
  WebhookEventType,
25
30
  WebhookMode,
26
31
  WebhookTestEventResponse,
@@ -32,6 +37,8 @@ MODEL_ENDPOINTS = {
32
37
  "diffio-2": "diffio-2.0-generation",
33
38
  "diffio-2-flash": "diffio-2.0-flash-generation",
34
39
  "diffio-3": "diffio-3.0-generation",
40
+ "diffio-3.4": "diffio-3.4-generation",
41
+ "diffio-3.5": "diffio-3.5-generation",
35
42
  }
36
43
  DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504]
37
44
  DEFAULT_RETRY_BACKOFF = 0.5
@@ -175,6 +182,9 @@ class DiffioClient:
175
182
  self.audio_isolation = AudioIsolationClient(self)
176
183
  self.generations = GenerationsClient(self)
177
184
  self.projects = ProjectsClient(self)
185
+ self.account = AccountClient(self)
186
+ self.api_keys = ApiKeysClient(self)
187
+ self.usage = UsageClient(self)
178
188
  self.webhooks = WebhooksClient(self)
179
189
 
180
190
  def __enter__(self):
@@ -575,6 +585,112 @@ class DiffioClient:
575
585
  )
576
586
  return WebhookTestEventResponse.from_dict(response)
577
587
 
588
+ def get_account_settings(self, *, requestOptions=None):
589
+ response = self._request(
590
+ "POST",
591
+ "account/settings/get",
592
+ json_payload={},
593
+ requestOptions=requestOptions,
594
+ )
595
+ return AccountSettingsResponse.from_dict(response)
596
+
597
+ def update_account_settings(self, *, billingPolicy, requestOptions=None):
598
+ if not isinstance(billingPolicy, dict):
599
+ raise ValueError("billingPolicy must be an object")
600
+ response = self._request(
601
+ "POST",
602
+ "account/settings/update",
603
+ json_payload={"billingPolicy": billingPolicy},
604
+ requestOptions=requestOptions,
605
+ )
606
+ return AccountSettingsResponse.from_dict(response)
607
+
608
+ def create_api_key(self, *, label, scopes, resourceBounds=None, requestOptions=None):
609
+ if not label:
610
+ raise ValueError("label is required")
611
+ if not isinstance(scopes, list):
612
+ raise ValueError("scopes must be a list")
613
+ response = self._request(
614
+ "POST",
615
+ "api_keys/create",
616
+ json_payload={
617
+ "label": label,
618
+ "scopes": scopes,
619
+ "resourceBounds": resourceBounds or {},
620
+ },
621
+ requestOptions=requestOptions,
622
+ )
623
+ return ApiKeyResponse.from_dict(response)
624
+
625
+ def list_api_keys(self, *, requestOptions=None):
626
+ response = self._request(
627
+ "POST",
628
+ "api_keys/list",
629
+ json_payload={},
630
+ requestOptions=requestOptions,
631
+ )
632
+ return ApiKeysListResponse.from_dict(response)
633
+
634
+ def rotate_api_key(self, *, keyId, requestOptions=None):
635
+ if not keyId:
636
+ raise ValueError("keyId is required")
637
+ response = self._request(
638
+ "POST",
639
+ "api_keys/rotate",
640
+ json_payload={"keyId": keyId},
641
+ requestOptions=requestOptions,
642
+ )
643
+ return ApiKeyResponse.from_dict(response)
644
+
645
+ def revoke_api_key(self, *, keyId, requestOptions=None):
646
+ if not keyId:
647
+ raise ValueError("keyId is required")
648
+ response = self._request(
649
+ "POST",
650
+ "api_keys/revoke",
651
+ json_payload={"keyId": keyId},
652
+ requestOptions=requestOptions,
653
+ )
654
+ return ApiKeyResponse.from_dict(response)
655
+
656
+ def get_usage_summary(self, *, apiKeyId=None, requestOptions=None):
657
+ payload = {}
658
+ if apiKeyId is not None:
659
+ payload["apiKeyId"] = apiKeyId
660
+ response = self._request(
661
+ "POST",
662
+ "usage/summary",
663
+ json_payload=payload,
664
+ requestOptions=requestOptions,
665
+ )
666
+ return UsageSummaryResponse.from_dict(response)
667
+
668
+ def configure_webhook(
669
+ self,
670
+ *,
671
+ mode,
672
+ url,
673
+ eventTypes,
674
+ apiKeyId=None,
675
+ requestOptions=None,
676
+ ):
677
+ if mode not in WebhookMode:
678
+ raise ValueError("mode must be test or live")
679
+ if not url:
680
+ raise ValueError("url is required")
681
+ if not isinstance(eventTypes, list) or not eventTypes:
682
+ raise ValueError("eventTypes must be a non-empty list")
683
+ payload = {"mode": mode, "url": url, "eventTypes": eventTypes}
684
+ if apiKeyId is not None:
685
+ payload["apiKeyId"] = apiKeyId
686
+ response = self._request(
687
+ "POST",
688
+ "webhooks/configure",
689
+ json_payload=payload,
690
+ requestOptions=requestOptions,
691
+ )
692
+ return WebhookConfigureResponse.from_dict(response)
693
+
578
694
  def restore_audio(
579
695
  self,
580
696
  *,
@@ -662,6 +778,50 @@ class DiffioClient:
662
778
  return _raise_for_error(response)
663
779
 
664
780
 
781
+ class AccountClient:
782
+ def __init__(self, parent):
783
+ self._parent = parent
784
+
785
+ def get_settings(self, *, requestOptions=None):
786
+ return self._parent.get_account_settings(requestOptions=requestOptions)
787
+
788
+ def update_settings(self, *, billingPolicy, requestOptions=None):
789
+ return self._parent.update_account_settings(
790
+ billingPolicy=billingPolicy,
791
+ requestOptions=requestOptions,
792
+ )
793
+
794
+
795
+ class ApiKeysClient:
796
+ def __init__(self, parent):
797
+ self._parent = parent
798
+
799
+ def create(self, *, label, scopes, resourceBounds=None, requestOptions=None):
800
+ return self._parent.create_api_key(
801
+ label=label,
802
+ scopes=scopes,
803
+ resourceBounds=resourceBounds,
804
+ requestOptions=requestOptions,
805
+ )
806
+
807
+ def list(self, *, requestOptions=None):
808
+ return self._parent.list_api_keys(requestOptions=requestOptions)
809
+
810
+ def rotate(self, *, keyId, requestOptions=None):
811
+ return self._parent.rotate_api_key(keyId=keyId, requestOptions=requestOptions)
812
+
813
+ def revoke(self, *, keyId, requestOptions=None):
814
+ return self._parent.revoke_api_key(keyId=keyId, requestOptions=requestOptions)
815
+
816
+
817
+ class UsageClient:
818
+ def __init__(self, parent):
819
+ self._parent = parent
820
+
821
+ def summary(self, *, apiKeyId=None, requestOptions=None):
822
+ return self._parent.get_usage_summary(apiKeyId=apiKeyId, requestOptions=requestOptions)
823
+
824
+
665
825
  class GenerationsClient:
666
826
  def __init__(self, parent):
667
827
  self._parent = parent
@@ -852,6 +1012,23 @@ class WebhooksClient:
852
1012
  requestOptions=requestOptions,
853
1013
  )
854
1014
 
1015
+ def configure(
1016
+ self,
1017
+ *,
1018
+ mode,
1019
+ url,
1020
+ eventTypes,
1021
+ apiKeyId=None,
1022
+ requestOptions=None,
1023
+ ):
1024
+ return self._parent.configure_webhook(
1025
+ mode=mode,
1026
+ url=url,
1027
+ eventTypes=eventTypes,
1028
+ apiKeyId=apiKeyId,
1029
+ requestOptions=requestOptions,
1030
+ )
1031
+
855
1032
  def verify_signature(
856
1033
  self,
857
1034
  *,
@@ -1151,9 +1328,9 @@ def _normalize_download_type(download_type):
1151
1328
  return None, None
1152
1329
  if download_type == "mp3":
1153
1330
  return "audio", "mp3"
1154
- if download_type in {"audio", "video"}:
1331
+ if download_type in {"audio", "video", "transcript"}:
1155
1332
  return download_type, download_type
1156
- raise ValueError("downloadType must be audio, mp3, or video")
1333
+ raise ValueError("downloadType must be audio, mp3, video, or transcript")
1157
1334
 
1158
1335
 
1159
1336
  def _extension_from_file_name(file_name):
@@ -1190,6 +1367,8 @@ def _extension_from_url(download_url):
1190
1367
  def _expected_download_extension(download):
1191
1368
  if download.downloadType == "audio":
1192
1369
  return ".mp3"
1370
+ if download.downloadType == "transcript":
1371
+ return ".json"
1193
1372
  if download.downloadType == "video":
1194
1373
  return (
1195
1374
  _extension_from_file_name(download.fileName)
@@ -256,6 +256,119 @@ class WebhookTestEventResponse:
256
256
  )
257
257
 
258
258
 
259
+ class AccountSettingsResponse:
260
+ def __init__(self, account, apiKeyId=None):
261
+ self.account = account
262
+ self.apiKeyId = apiKeyId
263
+
264
+ @classmethod
265
+ def from_dict(cls, data):
266
+ account = data.get("account")
267
+ if not isinstance(account, dict):
268
+ account = {}
269
+ return cls(account=account, apiKeyId=data.get("apiKeyId"))
270
+
271
+
272
+ class ApiKeyResponse:
273
+ def __init__(
274
+ self,
275
+ keyId,
276
+ label,
277
+ status,
278
+ keyPrefix,
279
+ role,
280
+ scopes,
281
+ resourceBounds,
282
+ key=None,
283
+ parentKeyId=None,
284
+ createdAt=None,
285
+ rotatedAt=None,
286
+ revokedAt=None,
287
+ permissions=None,
288
+ ):
289
+ self.key = key
290
+ self.keyId = keyId
291
+ self.label = label
292
+ self.status = status
293
+ self.keyPrefix = keyPrefix
294
+ self.role = role
295
+ self.scopes = scopes
296
+ self.resourceBounds = resourceBounds
297
+ self.parentKeyId = parentKeyId
298
+ self.createdAt = createdAt
299
+ self.rotatedAt = rotatedAt
300
+ self.revokedAt = revokedAt
301
+ self.permissions = permissions
302
+
303
+ @classmethod
304
+ def from_dict(cls, data):
305
+ scopes = data.get("scopes")
306
+ if not isinstance(scopes, list):
307
+ scopes = []
308
+ resource_bounds = data.get("resourceBounds")
309
+ if not isinstance(resource_bounds, dict):
310
+ resource_bounds = {}
311
+ permissions = data.get("permissions")
312
+ if permissions is not None and not isinstance(permissions, dict):
313
+ permissions = None
314
+ return cls(
315
+ key=data.get("key"),
316
+ keyId=data["keyId"],
317
+ label=data.get("label") or "",
318
+ status=data.get("status") or "active",
319
+ keyPrefix=data.get("keyPrefix") or "",
320
+ role=data.get("role") or "scoped",
321
+ scopes=[str(scope) for scope in scopes],
322
+ resourceBounds=resource_bounds,
323
+ parentKeyId=data.get("parentKeyId"),
324
+ createdAt=data.get("createdAt"),
325
+ rotatedAt=data.get("rotatedAt"),
326
+ revokedAt=data.get("revokedAt"),
327
+ permissions=permissions,
328
+ )
329
+
330
+
331
+ class ApiKeysListResponse:
332
+ def __init__(self, keys):
333
+ self.keys = keys
334
+
335
+ @classmethod
336
+ def from_dict(cls, data):
337
+ items = data.get("keys")
338
+ if not isinstance(items, list):
339
+ items = []
340
+ keys = [ApiKeyResponse.from_dict(item) for item in items if isinstance(item, dict)]
341
+ return cls(keys=keys)
342
+
343
+
344
+ class UsageSummaryResponse:
345
+ def __init__(self, usage, billing):
346
+ self.usage = usage
347
+ self.billing = billing
348
+
349
+ @classmethod
350
+ def from_dict(cls, data):
351
+ usage = data.get("usage")
352
+ if not isinstance(usage, dict):
353
+ usage = {}
354
+ billing = data.get("billing")
355
+ if not isinstance(billing, dict):
356
+ billing = {}
357
+ return cls(usage=usage, billing=billing)
358
+
359
+
360
+ class WebhookConfigureResponse:
361
+ def __init__(self, webhook):
362
+ self.webhook = webhook
363
+
364
+ @classmethod
365
+ def from_dict(cls, data):
366
+ webhook = data.get("webhook")
367
+ if not isinstance(webhook, dict):
368
+ webhook = {}
369
+ return cls(webhook=webhook)
370
+
371
+
259
372
  class GenerationWebhookEvent:
260
373
  def __init__(
261
374
  self,
@@ -306,8 +419,8 @@ class AudioIsolationResult:
306
419
  self.generation = generation
307
420
 
308
421
 
309
- ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3")
310
- DownloadType = ("audio", "mp3", "video")
422
+ ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3", "diffio-3.4", "diffio-3.5")
423
+ DownloadType = ("audio", "mp3", "video", "transcript")
311
424
  WebhookMode = ("test", "live")
312
425
  WebhookEventType = (
313
426
  "generation.queued",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffio
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
@@ -87,7 +87,7 @@ project = client.create_project(
87
87
 
88
88
  generation = client.create_generation(
89
89
  apiProjectId=project.apiProjectId,
90
- model="diffio-2",
90
+ model="diffio-3.5",
91
91
  sampling={"steps": 12, "guidance": 1.5},
92
92
  )
93
93
 
@@ -102,7 +102,7 @@ from diffio import DiffioClient
102
102
  client = DiffioClient(apiKey="diffio_live_...")
103
103
  result = client.audio_isolation.isolate(
104
104
  filePath="sample.wav",
105
- model="diffio-2",
105
+ model="diffio-3.5",
106
106
  sampling={"steps": 12, "guidance": 1.5},
107
107
  )
108
108
 
@@ -119,7 +119,7 @@ from diffio import DiffioClient
119
119
  client = DiffioClient(apiKey="diffio_live_...")
120
120
  audio_bytes, info = client.restore_audio(
121
121
  filePath="sample.wav",
122
- model="diffio-2",
122
+ model="diffio-3.5",
123
123
  sampling={"steps": 12, "guidance": 1.5},
124
124
  onProgress=lambda progress: print(progress.status),
125
125
  )
@@ -165,6 +165,36 @@ print(download.downloadUrl)
165
165
 
166
166
  If you only need the URL, use `client.generations.get_download`.
167
167
 
168
+ Set `downloadType="transcript"` to download the transcript JSON artifact when the generation has one.
169
+
170
+ ```py
171
+ transcript = client.generations.download(
172
+ generationId="gen_123",
173
+ apiProjectId="proj_123",
174
+ downloadType="transcript",
175
+ downloadFilePath="word_timestamps.json",
176
+ )
177
+ ```
178
+
179
+ ## Account, keys, usage, and webhook configuration
180
+
181
+ Agent keys can manage account settings, scoped keys, usage, and webhook endpoints.
182
+
183
+ ```py
184
+ settings = client.account.get_settings()
185
+ key = client.api_keys.create(
186
+ label="Backend worker",
187
+ scopes=["projects:read", "projects:write", "generations:read", "generations:write", "artifacts:read"],
188
+ )
189
+ usage = client.usage.summary(apiKeyId=key.keyId)
190
+ webhook = client.webhooks.configure(
191
+ mode="live",
192
+ url="https://example.com/webhooks/diffio",
193
+ eventTypes=["generation.completed", "generation.failed"],
194
+ apiKeyId=key.keyId,
195
+ )
196
+ ```
197
+
168
198
  ## List projects
169
199
 
170
200
  ```py
@@ -283,6 +283,34 @@ def test_audio_isolation_isolate_runs_full_flow(tmp_path: Path):
283
283
  assert calls[2][0] == "POST"
284
284
 
285
285
 
286
+ def test_create_generation_routes_to_diffio_3_5_endpoint():
287
+ received = {}
288
+
289
+ def handler(request: httpx.Request) -> httpx.Response:
290
+ received["path"] = request.url.path
291
+ payload = json.loads(request.content.decode("utf-8"))
292
+ received["payload"] = payload
293
+ return httpx.Response(
294
+ 200,
295
+ json={
296
+ "generationId": "gen_35",
297
+ "apiProjectId": "proj_35",
298
+ "modelKey": "diffio-3.5",
299
+ "status": "queued",
300
+ },
301
+ )
302
+
303
+ transport = httpx.MockTransport(handler)
304
+ http_client = httpx.Client(base_url="https://api.test", transport=transport)
305
+ client = DiffioClient(apiKey="diffio_live_test", baseUrl="https://api.test", httpClient=http_client)
306
+
307
+ response = client.create_generation(apiProjectId="proj_35", model="diffio-3.5")
308
+
309
+ assert received["path"] == "/v1/diffio-3.5-generation"
310
+ assert received["payload"]["apiProjectId"] == "proj_35"
311
+ assert response.modelKey == "diffio-3.5"
312
+
313
+
286
314
  def test_restore_audio_runs_full_flow_and_downloads(tmp_path: Path, monkeypatch):
287
315
  status_sequence = ["queued", "processing", "complete"]
288
316
 
@@ -501,6 +529,43 @@ def test_get_generation_download_payload_and_response():
501
529
  assert download.mimeType == "audio/mpeg"
502
530
 
503
531
 
532
+ def test_get_generation_download_accepts_transcript():
533
+ received = {}
534
+
535
+ def handler(request: httpx.Request) -> httpx.Response:
536
+ received["path"] = request.url.path
537
+ payload = json.loads(request.content.decode("utf-8"))
538
+ received["payload"] = payload
539
+ return httpx.Response(
540
+ 200,
541
+ json={
542
+ "generationId": "gen_456",
543
+ "apiProjectId": "proj_456",
544
+ "downloadType": "transcript",
545
+ "downloadUrl": "https://storage.test/word_timestamps.json",
546
+ "fileName": "word_timestamps.json",
547
+ "storagePath": "users/u/projects/proj_456/generations/gen_456/word_timestamps.json",
548
+ "bucket": "diffio_api",
549
+ "mimeType": "application/json",
550
+ },
551
+ )
552
+
553
+ transport = httpx.MockTransport(handler)
554
+ http_client = httpx.Client(base_url="https://api.test", transport=transport)
555
+ client = DiffioClient(apiKey="diffio_live_test", baseUrl="https://api.test", httpClient=http_client)
556
+
557
+ download = client.generations.get_download(
558
+ generationId="gen_456",
559
+ apiProjectId="proj_456",
560
+ downloadType="transcript",
561
+ )
562
+
563
+ assert received["path"] == "/v1/get_generation_download"
564
+ assert received["payload"]["downloadType"] == "transcript"
565
+ assert download.downloadType == "transcript"
566
+ assert download.mimeType == "application/json"
567
+
568
+
504
569
  def test_generation_download_writes_file(tmp_path: Path):
505
570
  received = {}
506
571
 
@@ -699,6 +764,80 @@ def test_webhooks_send_test_event_payload_and_response():
699
764
  assert response.eventType == "generation.completed"
700
765
 
701
766
 
767
+ def test_account_keys_usage_and_webhook_configure_endpoints():
768
+ calls = []
769
+
770
+ def handler(request: httpx.Request) -> httpx.Response:
771
+ payload = json.loads(request.content.decode("utf-8"))
772
+ calls.append((request.url.path, payload))
773
+ if request.url.path == "/v1/account/settings/get":
774
+ return httpx.Response(
775
+ 200,
776
+ json={
777
+ "apiKeyId": "agent_1",
778
+ "account": {"userId": "user_1", "billingPolicy": {"type": "internalDiffio"}},
779
+ },
780
+ )
781
+ if request.url.path == "/v1/api_keys/create":
782
+ return httpx.Response(
783
+ 200,
784
+ json={
785
+ "key": "diffio_live_new",
786
+ "keyId": "key_1",
787
+ "label": "VFC worker",
788
+ "status": "active",
789
+ "keyPrefix": "diffio_live_",
790
+ "role": "scoped",
791
+ "scopes": ["projects:read", "projects:write"],
792
+ "resourceBounds": {},
793
+ "parentKeyId": "agent_1",
794
+ },
795
+ )
796
+ if request.url.path == "/v1/usage/summary":
797
+ return httpx.Response(
798
+ 200,
799
+ json={
800
+ "usage": {"apiKeyId": "key_1", "periods": []},
801
+ "billing": {"billingPolicy": {"type": "internalDiffio"}},
802
+ },
803
+ )
804
+ if request.url.path == "/v1/webhooks/configure":
805
+ return httpx.Response(
806
+ 200,
807
+ json={
808
+ "webhook": {
809
+ "apiKeyId": "key_1",
810
+ "mode": "live",
811
+ "endpointId": "ep_1",
812
+ }
813
+ },
814
+ )
815
+ return httpx.Response(404, json={"error": "not found"})
816
+
817
+ transport = httpx.MockTransport(handler)
818
+ http_client = httpx.Client(base_url="https://api.test", transport=transport)
819
+ client = DiffioClient(apiKey="diffio_live_test", baseUrl="https://api.test", httpClient=http_client)
820
+
821
+ settings = client.account.get_settings()
822
+ key = client.api_keys.create(label="VFC worker", scopes=["projects:read", "projects:write"])
823
+ usage = client.usage.summary(apiKeyId="key_1")
824
+ webhook = client.webhooks.configure(
825
+ mode="live",
826
+ url="https://example.com/webhook",
827
+ eventTypes=["generation.completed"],
828
+ apiKeyId="key_1",
829
+ )
830
+
831
+ assert settings.account["billingPolicy"]["type"] == "internalDiffio"
832
+ assert key.key == "diffio_live_new"
833
+ assert usage.billing["billingPolicy"]["type"] == "internalDiffio"
834
+ assert webhook.webhook["endpointId"] == "ep_1"
835
+ assert calls[1] == (
836
+ "/v1/api_keys/create",
837
+ {"label": "VFC worker", "scopes": ["projects:read", "projects:write"], "resourceBounds": {}},
838
+ )
839
+
840
+
702
841
  def test_webhooks_send_test_event_rejects_invalid_type():
703
842
  transport = httpx.MockTransport(lambda request: httpx.Response(200, json={}))
704
843
  http_client = httpx.Client(base_url="https://api.test", transport=transport)
@@ -9,7 +9,7 @@ import httpx
9
9
  import pytest
10
10
 
11
11
  from diffio import DiffioApiError, DiffioClient
12
- from emulator_api_key import create_emulator_api_key
12
+ from emulator_api_key import EmulatorApiKeyError, create_emulator_api_key
13
13
 
14
14
  EMULATOR_BASE_URL = "http://127.0.0.1:5001/diffioai/us-central1"
15
15
  FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
@@ -33,7 +33,12 @@ def emulator_env() -> None:
33
33
 
34
34
  @pytest.fixture(scope="session")
35
35
  def emulator_api_key(emulator_env):
36
- result = create_emulator_api_key()
36
+ try:
37
+ result = create_emulator_api_key()
38
+ except EmulatorApiKeyError as exc:
39
+ if "Upgrade to the Developer plan" in str(exc):
40
+ pytest.skip("Local emulator account cannot create API keys without the Developer plan.")
41
+ raise
37
42
  os.environ["DIFFIO_API_KEY"] = result.api_key
38
43
  return result
39
44
 
File without changes
File without changes
File without changes
File without changes