diffio 0.1.0__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,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffio
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
7
+ Project-URL: Homepage, https://diffio.ai
8
+ Project-URL: Repository, https://github.com/Diffio-AI/diffio-python
7
9
  Classifier: License :: OSI Approved :: MIT License
8
10
  Classifier: Operating System :: OS Independent
9
11
  Classifier: Programming Language :: Python :: 3
@@ -17,6 +19,7 @@ Requires-Python: >=3.8
17
19
  Description-Content-Type: text/markdown
18
20
  License-File: LICENSE
19
21
  Requires-Dist: httpx>=0.24.0
22
+ Requires-Dist: svix<2.0.0,>=1.84.1
20
23
  Provides-Extra: dev
21
24
  Requires-Dist: build>=1.0; extra == "dev"
22
25
  Requires-Dist: pytest>=7.4; extra == "dev"
@@ -43,17 +46,11 @@ pip install -e .
43
46
 
44
47
  ## Configuration
45
48
 
46
- 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`.
47
50
 
48
51
  ```bash
49
52
  export DIFFIO_API_KEY="diffio_live_..."
50
- export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net"
51
- ```
52
-
53
- For emulators, set the base URL to the Functions emulator host.
54
-
55
- ```bash
56
- 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"
57
54
  ```
58
55
 
59
56
  ## Request options
@@ -192,16 +189,6 @@ for generation in generations.generations:
192
189
  print(generation.generationId, generation.status)
193
190
  ```
194
191
 
195
- ## Webhooks portal access
196
-
197
- ```py
198
- from diffio import DiffioClient
199
-
200
- client = DiffioClient(apiKey="diffio_live_...")
201
- portal = client.webhooks.get_portal_access(mode="test")
202
- print(portal.portalUrl)
203
- ```
204
-
205
192
  ## Send a test webhook event
206
193
 
207
194
  ```py
@@ -210,13 +197,41 @@ from diffio import DiffioClient
210
197
  client = DiffioClient(apiKey="diffio_live_...")
211
198
  event = client.webhooks.send_test_event(
212
199
  eventType="generation.completed",
213
- mode="test",
200
+ mode="live",
214
201
  samplePayload={"apiProjectId": "proj_123"},
215
202
  )
216
203
 
217
204
  print(event.svixMessageId)
218
205
  ```
219
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
+
220
235
  ## Tutorials
221
236
 
222
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.0"
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",
@@ -34,6 +35,10 @@ dev = [
34
35
  "twine>=5.0",
35
36
  ]
36
37
 
38
+ [project.urls]
39
+ Homepage = "https://diffio.ai"
40
+ Repository = "https://github.com/Diffio-AI/diffio-python"
41
+
37
42
  [tool.setuptools]
38
43
  package-dir = { "" = "src" }
39
44
 
@@ -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,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffio
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for the Diffio API.
5
5
  Author: Diffio
6
6
  License: MIT
7
+ Project-URL: Homepage, https://diffio.ai
8
+ Project-URL: Repository, https://github.com/Diffio-AI/diffio-python
7
9
  Classifier: License :: OSI Approved :: MIT License
8
10
  Classifier: Operating System :: OS Independent
9
11
  Classifier: Programming Language :: Python :: 3
@@ -17,6 +19,7 @@ Requires-Python: >=3.8
17
19
  Description-Content-Type: text/markdown
18
20
  License-File: LICENSE
19
21
  Requires-Dist: httpx>=0.24.0
22
+ Requires-Dist: svix<2.0.0,>=1.84.1
20
23
  Provides-Extra: dev
21
24
  Requires-Dist: build>=1.0; extra == "dev"
22
25
  Requires-Dist: pytest>=7.4; extra == "dev"
@@ -43,17 +46,11 @@ pip install -e .
43
46
 
44
47
  ## Configuration
45
48
 
46
- 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`.
47
50
 
48
51
  ```bash
49
52
  export DIFFIO_API_KEY="diffio_live_..."
50
- export DIFFIO_API_BASE_URL="https://us-central1-diffioai.cloudfunctions.net"
51
- ```
52
-
53
- For emulators, set the base URL to the Functions emulator host.
54
-
55
- ```bash
56
- 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"
57
54
  ```
58
55
 
59
56
  ## Request options
@@ -192,16 +189,6 @@ for generation in generations.generations:
192
189
  print(generation.generationId, generation.status)
193
190
  ```
194
191
 
195
- ## Webhooks portal access
196
-
197
- ```py
198
- from diffio import DiffioClient
199
-
200
- client = DiffioClient(apiKey="diffio_live_...")
201
- portal = client.webhooks.get_portal_access(mode="test")
202
- print(portal.portalUrl)
203
- ```
204
-
205
192
  ## Send a test webhook event
206
193
 
207
194
  ```py
@@ -210,13 +197,41 @@ from diffio import DiffioClient
210
197
  client = DiffioClient(apiKey="diffio_live_...")
211
198
  event = client.webhooks.send_test_event(
212
199
  eventType="generation.completed",
213
- mode="test",
200
+ mode="live",
214
201
  samplePayload={"apiProjectId": "proj_123"},
215
202
  )
216
203
 
217
204
  print(event.svixMessageId)
218
205
  ```
219
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
+
220
235
  ## Tutorials
221
236
 
222
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