diffio 0.1.1__tar.gz → 0.1.2__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.1
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
@@ -19,6 +19,7 @@ Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: httpx>=0.24.0
22
+ Requires-Dist: svix<2.0.0,>=1.84.1
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: build>=1.0; extra == "dev"
24
25
  Requires-Dist: pytest>=7.4; extra == "dev"
@@ -45,17 +46,11 @@ pip install -e .
45
46
 
46
47
  ## Configuration
47
48
 
48
- Set the API key with `DIFFIO_API_KEY`. You can also override the base URL with `DIFFIO_API_BASE_URL`.
49
+ Set the API key with `DIFFIO_API_KEY`. If you need to set the base URL explicitly, use the production endpoint with `DIFFIO_API_BASE_URL`.
49
50
 
50
51
  ```bash
51
52
  export DIFFIO_API_KEY="diffio_live_..."
52
- export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net"
53
- ```
54
-
55
- For emulators, set the base URL to the Functions emulator host.
56
-
57
- ```bash
58
- export DIFFIO_API_BASE_URL="http://127.0.0.1:5001/diffioai/us-central1"
53
+ export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net/v1"
59
54
  ```
60
55
 
61
56
  ## Request options
@@ -194,16 +189,6 @@ for generation in generations.generations:
194
189
  print(generation.generationId, generation.status)
195
190
  ```
196
191
 
197
- ## Webhooks portal access
198
-
199
- ```py
200
- from diffio import DiffioClient
201
-
202
- client = DiffioClient(apiKey="diffio_live_...")
203
- portal = client.webhooks.get_portal_access(mode="test")
204
- print(portal.portalUrl)
205
- ```
206
-
207
192
  ## Send a test webhook event
208
193
 
209
194
  ```py
@@ -212,13 +197,41 @@ from diffio import DiffioClient
212
197
  client = DiffioClient(apiKey="diffio_live_...")
213
198
  event = client.webhooks.send_test_event(
214
199
  eventType="generation.completed",
215
- mode="test",
200
+ mode="live",
216
201
  samplePayload={"apiProjectId": "proj_123"},
217
202
  )
218
203
 
219
204
  print(event.svixMessageId)
220
205
  ```
221
206
 
207
+ ## Verify webhook signatures
208
+
209
+ Use the raw request body (bytes) plus the `svix-*` headers and your webhook signing secret.
210
+
211
+ ```py
212
+ from fastapi import FastAPI, Request, HTTPException
213
+ from diffio import DiffioClient
214
+ import os
215
+
216
+ app = FastAPI()
217
+ client = DiffioClient(apiKey=os.environ["DIFFIO_API_KEY"])
218
+
219
+ @app.post("/webhooks/diffio")
220
+ async def diffio_webhook(request: Request):
221
+ payload = await request.body()
222
+ headers = request.headers
223
+ try:
224
+ event = client.webhooks.verify_signature(
225
+ payload=payload,
226
+ headers=headers,
227
+ secret=os.environ["DIFFIO_WEBHOOK_SECRET"],
228
+ )
229
+ except Exception:
230
+ raise HTTPException(status_code=400, detail="Invalid signature")
231
+ print("Webhook received", event.eventType)
232
+ return {"ok": True}
233
+ ```
234
+
222
235
  ## Tutorials
223
236
 
224
237
  * Audio restoration CLI tutorial: `tutorials/audio-restoration-cli/README.md`
@@ -18,17 +18,11 @@ pip install -e .
18
18
 
19
19
  ## Configuration
20
20
 
21
- Set the API key with `DIFFIO_API_KEY`. You can also override the base URL with `DIFFIO_API_BASE_URL`.
21
+ Set the API key with `DIFFIO_API_KEY`. If you need to set the base URL explicitly, use the production endpoint with `DIFFIO_API_BASE_URL`.
22
22
 
23
23
  ```bash
24
24
  export DIFFIO_API_KEY="diffio_live_..."
25
- export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net"
26
- ```
27
-
28
- For emulators, set the base URL to the Functions emulator host.
29
-
30
- ```bash
31
- export DIFFIO_API_BASE_URL="http://127.0.0.1:5001/diffioai/us-central1"
25
+ export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net/v1"
32
26
  ```
33
27
 
34
28
  ## Request options
@@ -167,16 +161,6 @@ for generation in generations.generations:
167
161
  print(generation.generationId, generation.status)
168
162
  ```
169
163
 
170
- ## Webhooks portal access
171
-
172
- ```py
173
- from diffio import DiffioClient
174
-
175
- client = DiffioClient(apiKey="diffio_live_...")
176
- portal = client.webhooks.get_portal_access(mode="test")
177
- print(portal.portalUrl)
178
- ```
179
-
180
164
  ## Send a test webhook event
181
165
 
182
166
  ```py
@@ -185,13 +169,41 @@ from diffio import DiffioClient
185
169
  client = DiffioClient(apiKey="diffio_live_...")
186
170
  event = client.webhooks.send_test_event(
187
171
  eventType="generation.completed",
188
- mode="test",
172
+ mode="live",
189
173
  samplePayload={"apiProjectId": "proj_123"},
190
174
  )
191
175
 
192
176
  print(event.svixMessageId)
193
177
  ```
194
178
 
179
+ ## Verify webhook signatures
180
+
181
+ Use the raw request body (bytes) plus the `svix-*` headers and your webhook signing secret.
182
+
183
+ ```py
184
+ from fastapi import FastAPI, Request, HTTPException
185
+ from diffio import DiffioClient
186
+ import os
187
+
188
+ app = FastAPI()
189
+ client = DiffioClient(apiKey=os.environ["DIFFIO_API_KEY"])
190
+
191
+ @app.post("/webhooks/diffio")
192
+ async def diffio_webhook(request: Request):
193
+ payload = await request.body()
194
+ headers = request.headers
195
+ try:
196
+ event = client.webhooks.verify_signature(
197
+ payload=payload,
198
+ headers=headers,
199
+ secret=os.environ["DIFFIO_WEBHOOK_SECRET"],
200
+ )
201
+ except Exception:
202
+ raise HTTPException(status_code=400, detail="Invalid signature")
203
+ print("Webhook received", event.eventType)
204
+ return {"ok": True}
205
+ ```
206
+
195
207
  ## Tutorials
196
208
 
197
209
  * Audio restoration CLI tutorial: `tutorials/audio-restoration-cli/README.md`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "diffio"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "Python SDK for the Diffio API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -13,7 +13,8 @@ authors = [
13
13
  { name = "Diffio" }
14
14
  ]
15
15
  dependencies = [
16
- "httpx>=0.24.0"
16
+ "httpx>=0.24.0",
17
+ "svix>=1.84.1,<2.0.0"
17
18
  ]
18
19
  classifiers = [
19
20
  "License :: OSI Approved :: MIT License",
@@ -24,7 +24,6 @@ from .types import (
24
24
  ProjectSummary,
25
25
  WebhookEventType,
26
26
  WebhookMode,
27
- WebhookPortalResponse,
28
27
  WebhookTestEventResponse,
29
28
  )
30
29
 
@@ -51,9 +50,8 @@ __all__ = [
51
50
  "RequestOptions",
52
51
  "WebhookEventType",
53
52
  "WebhookMode",
54
- "WebhookPortalResponse",
55
53
  "WebhookTestEventResponse",
56
54
  "WebhooksClient",
57
55
  ]
58
56
 
59
- __version__ = "0.1.0"
57
+ __version__ = "0.1.2"
@@ -7,6 +7,7 @@ import warnings
7
7
  from urllib.parse import urlparse
8
8
 
9
9
  import httpx
10
+ from svix.webhooks import Webhook
10
11
 
11
12
  from .errors import DiffioApiError
12
13
  from .types import (
@@ -16,12 +17,12 @@ from .types import (
16
17
  DownloadType,
17
18
  GenerationDownloadResponse,
18
19
  GenerationProgressResponse,
20
+ GenerationWebhookEvent,
19
21
  ListProjectGenerationsResponse,
20
22
  ListProjectsResponse,
21
23
  ModelKey,
22
24
  WebhookEventType,
23
25
  WebhookMode,
24
- WebhookPortalResponse,
25
26
  WebhookTestEventResponse,
26
27
  )
27
28
 
@@ -100,6 +101,35 @@ def _merge_request_options(base_options, override_options):
100
101
  )
101
102
 
102
103
 
104
+ def _normalize_svix_headers(headers):
105
+ normalized = {}
106
+ if headers is None:
107
+ return normalized
108
+ if not hasattr(headers, "items"):
109
+ raise ValueError("headers must be a dict-like object")
110
+ for key, value in headers.items():
111
+ if value is None:
112
+ continue
113
+ if isinstance(value, (list, tuple)):
114
+ normalized[str(key).lower()] = ",".join([str(item) for item in value])
115
+ else:
116
+ normalized[str(key).lower()] = str(value)
117
+ return normalized
118
+
119
+
120
+ def _extract_svix_headers(headers):
121
+ normalized = _normalize_svix_headers(headers)
122
+ required = ["svix-id", "svix-timestamp", "svix-signature"]
123
+ missing = [header for header in required if not normalized.get(header)]
124
+ if missing:
125
+ raise DiffioApiError(f"Missing webhook headers: {', '.join(missing)}")
126
+ return {
127
+ "svix-id": normalized["svix-id"],
128
+ "svix-timestamp": normalized["svix-timestamp"],
129
+ "svix-signature": normalized["svix-signature"],
130
+ }
131
+
132
+
103
133
  def _default_request_options():
104
134
  return RequestOptions(
105
135
  headers={},
@@ -496,42 +526,6 @@ class DiffioClient:
496
526
  )
497
527
  return GenerationDownloadResponse.from_dict(response)
498
528
 
499
- def get_webhooks_portal_access(
500
- self,
501
- *,
502
- mode,
503
- apiKeyId=None,
504
- requestOptions=None,
505
- ):
506
- """
507
- Gets the Svix App Portal URL for configuring webhooks.
508
-
509
- Parameters
510
- ----------
511
- mode : str
512
- Webhook mode, test or live.
513
- apiKeyId : str, optional
514
- Optional API key id to validate access.
515
-
516
- Returns
517
- -------
518
- WebhookPortalResponse
519
- App portal URL and mode.
520
- """
521
- if mode not in WebhookMode:
522
- raise ValueError("mode must be test or live")
523
- payload = {"mode": mode}
524
- if apiKeyId is not None:
525
- payload["apiKeyId"] = apiKeyId
526
-
527
- response = self._request(
528
- "POST",
529
- "webhooks/app_portal_access",
530
- json_payload=payload,
531
- requestOptions=requestOptions,
532
- )
533
- return WebhookPortalResponse.from_dict(response)
534
-
535
529
  def send_webhook_test_event(
536
530
  self,
537
531
  *,
@@ -841,19 +835,6 @@ class WebhooksClient:
841
835
  def __init__(self, parent):
842
836
  self._parent = parent
843
837
 
844
- def get_portal_access(
845
- self,
846
- *,
847
- mode,
848
- apiKeyId=None,
849
- requestOptions=None,
850
- ):
851
- return self._parent.get_webhooks_portal_access(
852
- mode=mode,
853
- apiKeyId=apiKeyId,
854
- requestOptions=requestOptions,
855
- )
856
-
857
838
  def send_test_event(
858
839
  self,
859
840
  *,
@@ -871,6 +852,29 @@ class WebhooksClient:
871
852
  requestOptions=requestOptions,
872
853
  )
873
854
 
855
+ def verify_signature(
856
+ self,
857
+ *,
858
+ payload,
859
+ headers,
860
+ secret,
861
+ ):
862
+ if not secret:
863
+ raise DiffioApiError("secret is required")
864
+ if payload is None:
865
+ raise DiffioApiError("payload is required")
866
+ if not isinstance(payload, (bytes, str)):
867
+ raise DiffioApiError("payload must be bytes or str")
868
+
869
+ webhook = Webhook(secret)
870
+ try:
871
+ event = webhook.verify(payload, _extract_svix_headers(headers))
872
+ except Exception as exc:
873
+ raise DiffioApiError(str(exc))
874
+ if not isinstance(event, dict):
875
+ raise DiffioApiError("Webhook payload must be an object")
876
+ return GenerationWebhookEvent.from_dict(event)
877
+
874
878
 
875
879
  class AudioIsolationClient:
876
880
  def __init__(self, parent):
@@ -237,21 +237,6 @@ class GenerationDownloadResponse:
237
237
  )
238
238
 
239
239
 
240
- class WebhookPortalResponse:
241
- def __init__(self, portalUrl, mode, apiKeyId=None):
242
- self.portalUrl = portalUrl
243
- self.mode = mode
244
- self.apiKeyId = apiKeyId
245
-
246
- @classmethod
247
- def from_dict(cls, data):
248
- return cls(
249
- portalUrl=data["portalUrl"],
250
- mode=data.get("mode"),
251
- apiKeyId=data.get("apiKeyId"),
252
- )
253
-
254
-
255
240
  class WebhookTestEventResponse:
256
241
  def __init__(self, svixMessageId, eventId, eventType, mode, apiKeyId=None):
257
242
  self.svixMessageId = svixMessageId
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffio
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
@@ -19,6 +19,7 @@ Requires-Python: >=3.8
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  Requires-Dist: httpx>=0.24.0
22
+ Requires-Dist: svix<2.0.0,>=1.84.1
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: build>=1.0; extra == "dev"
24
25
  Requires-Dist: pytest>=7.4; extra == "dev"
@@ -45,17 +46,11 @@ pip install -e .
45
46
 
46
47
  ## Configuration
47
48
 
48
- Set the API key with `DIFFIO_API_KEY`. You can also override the base URL with `DIFFIO_API_BASE_URL`.
49
+ Set the API key with `DIFFIO_API_KEY`. If you need to set the base URL explicitly, use the production endpoint with `DIFFIO_API_BASE_URL`.
49
50
 
50
51
  ```bash
51
52
  export DIFFIO_API_KEY="diffio_live_..."
52
- export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net"
53
- ```
54
-
55
- For emulators, set the base URL to the Functions emulator host.
56
-
57
- ```bash
58
- export DIFFIO_API_BASE_URL="http://127.0.0.1:5001/diffioai/us-central1"
53
+ export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net/v1"
59
54
  ```
60
55
 
61
56
  ## Request options
@@ -194,16 +189,6 @@ for generation in generations.generations:
194
189
  print(generation.generationId, generation.status)
195
190
  ```
196
191
 
197
- ## Webhooks portal access
198
-
199
- ```py
200
- from diffio import DiffioClient
201
-
202
- client = DiffioClient(apiKey="diffio_live_...")
203
- portal = client.webhooks.get_portal_access(mode="test")
204
- print(portal.portalUrl)
205
- ```
206
-
207
192
  ## Send a test webhook event
208
193
 
209
194
  ```py
@@ -212,13 +197,41 @@ from diffio import DiffioClient
212
197
  client = DiffioClient(apiKey="diffio_live_...")
213
198
  event = client.webhooks.send_test_event(
214
199
  eventType="generation.completed",
215
- mode="test",
200
+ mode="live",
216
201
  samplePayload={"apiProjectId": "proj_123"},
217
202
  )
218
203
 
219
204
  print(event.svixMessageId)
220
205
  ```
221
206
 
207
+ ## Verify webhook signatures
208
+
209
+ Use the raw request body (bytes) plus the `svix-*` headers and your webhook signing secret.
210
+
211
+ ```py
212
+ from fastapi import FastAPI, Request, HTTPException
213
+ from diffio import DiffioClient
214
+ import os
215
+
216
+ app = FastAPI()
217
+ client = DiffioClient(apiKey=os.environ["DIFFIO_API_KEY"])
218
+
219
+ @app.post("/webhooks/diffio")
220
+ async def diffio_webhook(request: Request):
221
+ payload = await request.body()
222
+ headers = request.headers
223
+ try:
224
+ event = client.webhooks.verify_signature(
225
+ payload=payload,
226
+ headers=headers,
227
+ secret=os.environ["DIFFIO_WEBHOOK_SECRET"],
228
+ )
229
+ except Exception:
230
+ raise HTTPException(status_code=400, detail="Invalid signature")
231
+ print("Webhook received", event.eventType)
232
+ return {"ok": True}
233
+ ```
234
+
222
235
  ## Tutorials
223
236
 
224
237
  * Audio restoration CLI tutorial: `tutorials/audio-restoration-cli/README.md`
@@ -1,4 +1,5 @@
1
1
  httpx>=0.24.0
2
+ svix<2.0.0,>=1.84.1
2
3
 
3
4
  [dev]
4
5
  build>=1.0
@@ -660,35 +660,6 @@ def test_list_project_generations_payload_and_response():
660
660
  assert response.generations[0].progress is None
661
661
 
662
662
 
663
- def test_webhooks_portal_access_payload_and_response():
664
- received = {}
665
-
666
- def handler(request: httpx.Request) -> httpx.Response:
667
- received["path"] = request.url.path
668
- payload = json.loads(request.content.decode("utf-8"))
669
- received["payload"] = payload
670
- return httpx.Response(
671
- 200,
672
- json={
673
- "portalUrl": "https://app.svix.com/app-portal/test",
674
- "mode": "test",
675
- "apiKeyId": "key_123",
676
- },
677
- )
678
-
679
- transport = httpx.MockTransport(handler)
680
- http_client = httpx.Client(base_url="https://api.test", transport=transport)
681
- client = DiffioClient(apiKey="diffio_live_test", baseUrl="https://api.test", httpClient=http_client)
682
-
683
- response = client.webhooks.get_portal_access(mode="test", apiKeyId="key_123")
684
-
685
- assert received["path"] == "/v1/webhooks/app_portal_access"
686
- assert received["payload"]["mode"] == "test"
687
- assert received["payload"]["apiKeyId"] == "key_123"
688
- assert response.portalUrl == "https://app.svix.com/app-portal/test"
689
- assert response.mode == "test"
690
-
691
-
692
663
  def test_webhooks_send_test_event_payload_and_response():
693
664
  received = {}
694
665
 
@@ -702,7 +673,7 @@ def test_webhooks_send_test_event_payload_and_response():
702
673
  "svixMessageId": "msg_123",
703
674
  "eventId": "evt_123",
704
675
  "eventType": "generation.completed",
705
- "mode": "test",
676
+ "mode": "live",
706
677
  "apiKeyId": "key_123",
707
678
  },
708
679
  )
@@ -713,14 +684,14 @@ def test_webhooks_send_test_event_payload_and_response():
713
684
 
714
685
  response = client.webhooks.send_test_event(
715
686
  eventType="generation.completed",
716
- mode="test",
687
+ mode="live",
717
688
  apiKeyId="key_123",
718
689
  samplePayload={"apiProjectId": "proj_123"},
719
690
  )
720
691
 
721
692
  assert received["path"] == "/v1/webhooks/send_test_event"
722
693
  assert received["payload"]["eventType"] == "generation.completed"
723
- assert received["payload"]["mode"] == "test"
694
+ assert received["payload"]["mode"] == "live"
724
695
  assert received["payload"]["apiKeyId"] == "key_123"
725
696
  assert received["payload"]["samplePayload"]["apiProjectId"] == "proj_123"
726
697
  assert response.svixMessageId == "msg_123"
File without changes
File without changes
File without changes
File without changes