dipsac 0.1.1__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.
- dipsac-0.1.1/.gitignore +16 -0
- dipsac-0.1.1/LICENSE +21 -0
- dipsac-0.1.1/PKG-INFO +242 -0
- dipsac-0.1.1/README.md +210 -0
- dipsac-0.1.1/pyproject.toml +50 -0
- dipsac-0.1.1/src/dipsac/__init__.py +41 -0
- dipsac-0.1.1/src/dipsac/_client.py +263 -0
- dipsac-0.1.1/src/dipsac/_errors.py +182 -0
- dipsac-0.1.1/src/dipsac/_files.py +101 -0
- dipsac-0.1.1/src/dipsac/_models.py +195 -0
- dipsac-0.1.1/src/dipsac/_resources.py +2707 -0
- dipsac-0.1.1/src/dipsac/_version.py +1 -0
- dipsac-0.1.1/src/dipsac/py.typed +0 -0
- dipsac-0.1.1/tests/live_smoke.py +180 -0
- dipsac-0.1.1/tests/test_client.py +394 -0
dipsac-0.1.1/.gitignore
ADDED
dipsac-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DIPSAC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dipsac-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dipsac
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The official Python library for the DIPSAC API: PDF, image, audio, video and SEO tools, and the AI Voice Studio.
|
|
5
|
+
Project-URL: Homepage, https://dipsac.com
|
|
6
|
+
Project-URL: Documentation, https://dipsac.com/dashboard/reference
|
|
7
|
+
Project-URL: Source, https://github.com/dipsactools/dipsac-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/dipsactools/dipsac-sdk/issues
|
|
9
|
+
Author-email: DIPSAC <contact@dipsac.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,audio,dipsac,image,pdf,sdk,text-to-speech,transcription,video
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx<1,>=0.25
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# DIPSAC Python library
|
|
34
|
+
|
|
35
|
+
The official Python library for the [DIPSAC API](https://dipsac.com/dashboard/reference):
|
|
36
|
+
PDF, image, audio, video and SEO tools, and the AI Voice Studio (text to speech,
|
|
37
|
+
transcription, subtitles).
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install dipsac
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Python 3.9 or newer. The only dependency is `httpx`.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
Make a key at <https://dipsac.com/dashboard/keys>. Keys start with `dps_`.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dipsac import Dipsac
|
|
51
|
+
|
|
52
|
+
client = Dipsac(api_key="dps_...") # or set the DIPSAC_API_KEY environment variable
|
|
53
|
+
|
|
54
|
+
# Files in, a file out
|
|
55
|
+
merged = client.pdf.merge(["invoice.pdf", "terms.pdf"])
|
|
56
|
+
merged.save("merged.pdf")
|
|
57
|
+
|
|
58
|
+
# Files in, facts out
|
|
59
|
+
info = client.image.info("photo.jpg")
|
|
60
|
+
print(info["width"], info["height"])
|
|
61
|
+
|
|
62
|
+
# No file at all
|
|
63
|
+
print(client.dev.json('{"b": 1, "a": 2}', sort_keys=True)["result"])
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What is in it
|
|
67
|
+
|
|
68
|
+
| | |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `client.pdf` | merge, split, delete_pages, rotate, compress, convert, from_office, protect, unlock, watermark, metadata |
|
|
71
|
+
| `client.image` | convert, compress, resize, crop, remove_background, to_pdf, info |
|
|
72
|
+
| `client.audio` | convert, normalize, merge |
|
|
73
|
+
| `client.video` | convert, extract_audio, cut, resize, mute |
|
|
74
|
+
| `client.seo` | meta, ssl, redirects, whois, domain_age, canonical |
|
|
75
|
+
| `client.dev` | hash, hash_verify, json |
|
|
76
|
+
| `client.ai` | speak, transcribe, transcribe_video, subtitles, builtin_voices, voices, register_voice, library, ... |
|
|
77
|
+
| `client.jobs` | get, result |
|
|
78
|
+
| `client.glb` | limits, inspect, plan, image |
|
|
79
|
+
|
|
80
|
+
Every method is documented in its docstring, and typed. Which methods your key
|
|
81
|
+
may call depends on your plan; a call your plan does not include raises
|
|
82
|
+
`PermissionDeniedError`, which says which plan does.
|
|
83
|
+
|
|
84
|
+
## Giving it a file
|
|
85
|
+
|
|
86
|
+
Anywhere a method takes a file you can pass:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
client.pdf.metadata("report.pdf") # a path
|
|
90
|
+
client.pdf.metadata(Path("report.pdf")) # a pathlib.Path
|
|
91
|
+
client.pdf.metadata(open("report.pdf", "rb")) # an open binary file
|
|
92
|
+
client.pdf.metadata(pdf_bytes) # bytes
|
|
93
|
+
client.pdf.metadata(("report.pdf", pdf_bytes)) # a name and bytes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The API tells what a file is by the extension of its name. A path and an open
|
|
97
|
+
file bring their own name. Bare bytes have none, so the library reads their type
|
|
98
|
+
from how they begin (PDF, PNG, JPEG, GIF, WebP, WAV, MP3, MP4 and a few more);
|
|
99
|
+
for anything else, such as a Word file, pass the `(name, bytes)` form.
|
|
100
|
+
|
|
101
|
+
Uploads are read into memory before they are sent.
|
|
102
|
+
|
|
103
|
+
## What you get back
|
|
104
|
+
|
|
105
|
+
- **JSON** comes back as a `dict`.
|
|
106
|
+
- **A file** comes back as a `DipsacFile`:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
out = client.image.convert("photo.png", "jpg")
|
|
110
|
+
out.content # the bytes
|
|
111
|
+
out.content_type # "image/jpeg"
|
|
112
|
+
out.filename # the name the API gave it, e.g. "photo.jpg"
|
|
113
|
+
out.save("small.jpg") # or out.save() to use that name, or out.save("some/folder")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **A `Job`**, when you send the work to the background (next section).
|
|
117
|
+
|
|
118
|
+
## Long work: background jobs
|
|
119
|
+
|
|
120
|
+
Slow operations (video, big PDFs, background removal) take `mode="async"`. The
|
|
121
|
+
call returns at once with a `Job`; `wait()` polls until it is done and downloads
|
|
122
|
+
the result.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
job = client.video.convert("talk.mov", "mp4", mode="async")
|
|
126
|
+
clip = job.wait() # raises JobFailedError if the job fails
|
|
127
|
+
clip.save("talk.mp4")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`wait(timeout=600, poll_interval=2)` raises `JobTimeoutError` if the job is not
|
|
131
|
+
done in time. The job is not cancelled: keep `job.id` and pick it up later with
|
|
132
|
+
`client.job(job_id).wait()`. Results are kept for a limited time
|
|
133
|
+
(`job.raw["expires_at"]`).
|
|
134
|
+
|
|
135
|
+
## AI Voice Studio
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# Text to speech
|
|
139
|
+
audio = client.ai.speak("Namaste, aap kaise hain?", language="hi", fmt="mp3")
|
|
140
|
+
audio.save("hello.mp3")
|
|
141
|
+
|
|
142
|
+
# Long text: synthesise in the background
|
|
143
|
+
audio = client.ai.speak(long_text, language="en", background=True).wait()
|
|
144
|
+
|
|
145
|
+
# Speech to text
|
|
146
|
+
result = client.ai.transcribe("call.mp3", language="hi")
|
|
147
|
+
print(result["text"])
|
|
148
|
+
|
|
149
|
+
# Subtitles
|
|
150
|
+
client.ai.subtitles("episode.mp4", fmt="srt").save("episode.srt")
|
|
151
|
+
|
|
152
|
+
# Which voices exist
|
|
153
|
+
voices = client.ai.builtin_voices(language="hi")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Speech is metered per account. When an allowance is used up the call raises
|
|
157
|
+
`PaymentRequiredError`; `error.body` carries the numbers.
|
|
158
|
+
|
|
159
|
+
## Errors
|
|
160
|
+
|
|
161
|
+
Every error is a `dipsac.DipsacError`.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
import dipsac
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
client.audio.convert("voice.wav", "mp3")
|
|
168
|
+
except dipsac.PermissionDeniedError as e:
|
|
169
|
+
print(e.message) # "/v1/audio/convert requires the Pro plan or higher. ..."
|
|
170
|
+
print(e.required_plan) # "pro"
|
|
171
|
+
except dipsac.RateLimitError as e:
|
|
172
|
+
print(e.retry_after) # seconds
|
|
173
|
+
except dipsac.APIStatusError as e:
|
|
174
|
+
print(e.status_code, e.code, e.message, e.request_id)
|
|
175
|
+
except dipsac.APIConnectionError:
|
|
176
|
+
print("could not reach the API")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
| Status | Exception | |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| 400 | `BadRequestError` | |
|
|
182
|
+
| 401 | `AuthenticationError` | the key is missing or wrong |
|
|
183
|
+
| 402 | `PaymentRequiredError` | an allowance is used up |
|
|
184
|
+
| 403 | `PermissionDeniedError` | the plan does not include this; `.required_plan` |
|
|
185
|
+
| 404 | `NotFoundError` | |
|
|
186
|
+
| 409 | `ConflictError` | e.g. a job that has not finished |
|
|
187
|
+
| 413 | `PayloadTooLargeError` | the file is over your plan's size limit |
|
|
188
|
+
| 415 | `UnsupportedMediaTypeError` | the wrong kind of file; `.accepted` |
|
|
189
|
+
| 422 | `UnprocessableEntityError` | a parameter is missing or invalid; `.fields` |
|
|
190
|
+
| 429 | `RateLimitError` | `.retry_after` |
|
|
191
|
+
| 5xx | `InternalServerError` | |
|
|
192
|
+
| | `APIConnectionError`, `APITimeoutError` | no answer |
|
|
193
|
+
| | `JobFailedError`, `JobTimeoutError` | background jobs |
|
|
194
|
+
|
|
195
|
+
Quote `e.request_id` when you write to support.
|
|
196
|
+
|
|
197
|
+
## Retries
|
|
198
|
+
|
|
199
|
+
The library sends a request again, up to `max_retries` times (default 2), only
|
|
200
|
+
where that cannot do the work twice:
|
|
201
|
+
|
|
202
|
+
- a `429` from your plan's per-second limit is waited out and retried;
|
|
203
|
+
- a `429` from your plan's monthly limit is raised at once (waiting would take a day);
|
|
204
|
+
- a connection that never opened is retried;
|
|
205
|
+
- a `GET` that got a 502, 503 or 504 is retried;
|
|
206
|
+
- a `POST` that failed on the server is **not** retried.
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client = Dipsac(
|
|
212
|
+
api_key="dps_...",
|
|
213
|
+
timeout=300, # seconds; per call: client.pdf.compress(f, timeout=60)
|
|
214
|
+
max_retries=2,
|
|
215
|
+
base_url="https://api.dipsac.com",
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`DIPSAC_API_KEY` and `DIPSAC_BASE_URL` are read from the environment when the
|
|
220
|
+
arguments are left out. Pass `http_client=httpx.Client(...)` to use a proxy or
|
|
221
|
+
your own transport.
|
|
222
|
+
|
|
223
|
+
## asyncio
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
import asyncio
|
|
227
|
+
from dipsac import AsyncDipsac
|
|
228
|
+
|
|
229
|
+
async def main():
|
|
230
|
+
async with AsyncDipsac() as client:
|
|
231
|
+
merged = await client.pdf.merge(["a.pdf", "b.pdf"])
|
|
232
|
+
job = await client.video.mute("clip.mp4", mode="async")
|
|
233
|
+
muted = await job.wait()
|
|
234
|
+
|
|
235
|
+
asyncio.run(main())
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Links
|
|
239
|
+
|
|
240
|
+
- API reference: <https://dipsac.com/dashboard/reference>
|
|
241
|
+
- Speech API: <https://dipsac.com/dashboard/speech>
|
|
242
|
+
- Support: contact@dipsac.com
|
dipsac-0.1.1/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# DIPSAC Python library
|
|
2
|
+
|
|
3
|
+
The official Python library for the [DIPSAC API](https://dipsac.com/dashboard/reference):
|
|
4
|
+
PDF, image, audio, video and SEO tools, and the AI Voice Studio (text to speech,
|
|
5
|
+
transcription, subtitles).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install dipsac
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Python 3.9 or newer. The only dependency is `httpx`.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Make a key at <https://dipsac.com/dashboard/keys>. Keys start with `dps_`.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from dipsac import Dipsac
|
|
19
|
+
|
|
20
|
+
client = Dipsac(api_key="dps_...") # or set the DIPSAC_API_KEY environment variable
|
|
21
|
+
|
|
22
|
+
# Files in, a file out
|
|
23
|
+
merged = client.pdf.merge(["invoice.pdf", "terms.pdf"])
|
|
24
|
+
merged.save("merged.pdf")
|
|
25
|
+
|
|
26
|
+
# Files in, facts out
|
|
27
|
+
info = client.image.info("photo.jpg")
|
|
28
|
+
print(info["width"], info["height"])
|
|
29
|
+
|
|
30
|
+
# No file at all
|
|
31
|
+
print(client.dev.json('{"b": 1, "a": 2}', sort_keys=True)["result"])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What is in it
|
|
35
|
+
|
|
36
|
+
| | |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `client.pdf` | merge, split, delete_pages, rotate, compress, convert, from_office, protect, unlock, watermark, metadata |
|
|
39
|
+
| `client.image` | convert, compress, resize, crop, remove_background, to_pdf, info |
|
|
40
|
+
| `client.audio` | convert, normalize, merge |
|
|
41
|
+
| `client.video` | convert, extract_audio, cut, resize, mute |
|
|
42
|
+
| `client.seo` | meta, ssl, redirects, whois, domain_age, canonical |
|
|
43
|
+
| `client.dev` | hash, hash_verify, json |
|
|
44
|
+
| `client.ai` | speak, transcribe, transcribe_video, subtitles, builtin_voices, voices, register_voice, library, ... |
|
|
45
|
+
| `client.jobs` | get, result |
|
|
46
|
+
| `client.glb` | limits, inspect, plan, image |
|
|
47
|
+
|
|
48
|
+
Every method is documented in its docstring, and typed. Which methods your key
|
|
49
|
+
may call depends on your plan; a call your plan does not include raises
|
|
50
|
+
`PermissionDeniedError`, which says which plan does.
|
|
51
|
+
|
|
52
|
+
## Giving it a file
|
|
53
|
+
|
|
54
|
+
Anywhere a method takes a file you can pass:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
client.pdf.metadata("report.pdf") # a path
|
|
58
|
+
client.pdf.metadata(Path("report.pdf")) # a pathlib.Path
|
|
59
|
+
client.pdf.metadata(open("report.pdf", "rb")) # an open binary file
|
|
60
|
+
client.pdf.metadata(pdf_bytes) # bytes
|
|
61
|
+
client.pdf.metadata(("report.pdf", pdf_bytes)) # a name and bytes
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The API tells what a file is by the extension of its name. A path and an open
|
|
65
|
+
file bring their own name. Bare bytes have none, so the library reads their type
|
|
66
|
+
from how they begin (PDF, PNG, JPEG, GIF, WebP, WAV, MP3, MP4 and a few more);
|
|
67
|
+
for anything else, such as a Word file, pass the `(name, bytes)` form.
|
|
68
|
+
|
|
69
|
+
Uploads are read into memory before they are sent.
|
|
70
|
+
|
|
71
|
+
## What you get back
|
|
72
|
+
|
|
73
|
+
- **JSON** comes back as a `dict`.
|
|
74
|
+
- **A file** comes back as a `DipsacFile`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
out = client.image.convert("photo.png", "jpg")
|
|
78
|
+
out.content # the bytes
|
|
79
|
+
out.content_type # "image/jpeg"
|
|
80
|
+
out.filename # the name the API gave it, e.g. "photo.jpg"
|
|
81
|
+
out.save("small.jpg") # or out.save() to use that name, or out.save("some/folder")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **A `Job`**, when you send the work to the background (next section).
|
|
85
|
+
|
|
86
|
+
## Long work: background jobs
|
|
87
|
+
|
|
88
|
+
Slow operations (video, big PDFs, background removal) take `mode="async"`. The
|
|
89
|
+
call returns at once with a `Job`; `wait()` polls until it is done and downloads
|
|
90
|
+
the result.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
job = client.video.convert("talk.mov", "mp4", mode="async")
|
|
94
|
+
clip = job.wait() # raises JobFailedError if the job fails
|
|
95
|
+
clip.save("talk.mp4")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`wait(timeout=600, poll_interval=2)` raises `JobTimeoutError` if the job is not
|
|
99
|
+
done in time. The job is not cancelled: keep `job.id` and pick it up later with
|
|
100
|
+
`client.job(job_id).wait()`. Results are kept for a limited time
|
|
101
|
+
(`job.raw["expires_at"]`).
|
|
102
|
+
|
|
103
|
+
## AI Voice Studio
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Text to speech
|
|
107
|
+
audio = client.ai.speak("Namaste, aap kaise hain?", language="hi", fmt="mp3")
|
|
108
|
+
audio.save("hello.mp3")
|
|
109
|
+
|
|
110
|
+
# Long text: synthesise in the background
|
|
111
|
+
audio = client.ai.speak(long_text, language="en", background=True).wait()
|
|
112
|
+
|
|
113
|
+
# Speech to text
|
|
114
|
+
result = client.ai.transcribe("call.mp3", language="hi")
|
|
115
|
+
print(result["text"])
|
|
116
|
+
|
|
117
|
+
# Subtitles
|
|
118
|
+
client.ai.subtitles("episode.mp4", fmt="srt").save("episode.srt")
|
|
119
|
+
|
|
120
|
+
# Which voices exist
|
|
121
|
+
voices = client.ai.builtin_voices(language="hi")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Speech is metered per account. When an allowance is used up the call raises
|
|
125
|
+
`PaymentRequiredError`; `error.body` carries the numbers.
|
|
126
|
+
|
|
127
|
+
## Errors
|
|
128
|
+
|
|
129
|
+
Every error is a `dipsac.DipsacError`.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import dipsac
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
client.audio.convert("voice.wav", "mp3")
|
|
136
|
+
except dipsac.PermissionDeniedError as e:
|
|
137
|
+
print(e.message) # "/v1/audio/convert requires the Pro plan or higher. ..."
|
|
138
|
+
print(e.required_plan) # "pro"
|
|
139
|
+
except dipsac.RateLimitError as e:
|
|
140
|
+
print(e.retry_after) # seconds
|
|
141
|
+
except dipsac.APIStatusError as e:
|
|
142
|
+
print(e.status_code, e.code, e.message, e.request_id)
|
|
143
|
+
except dipsac.APIConnectionError:
|
|
144
|
+
print("could not reach the API")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Status | Exception | |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| 400 | `BadRequestError` | |
|
|
150
|
+
| 401 | `AuthenticationError` | the key is missing or wrong |
|
|
151
|
+
| 402 | `PaymentRequiredError` | an allowance is used up |
|
|
152
|
+
| 403 | `PermissionDeniedError` | the plan does not include this; `.required_plan` |
|
|
153
|
+
| 404 | `NotFoundError` | |
|
|
154
|
+
| 409 | `ConflictError` | e.g. a job that has not finished |
|
|
155
|
+
| 413 | `PayloadTooLargeError` | the file is over your plan's size limit |
|
|
156
|
+
| 415 | `UnsupportedMediaTypeError` | the wrong kind of file; `.accepted` |
|
|
157
|
+
| 422 | `UnprocessableEntityError` | a parameter is missing or invalid; `.fields` |
|
|
158
|
+
| 429 | `RateLimitError` | `.retry_after` |
|
|
159
|
+
| 5xx | `InternalServerError` | |
|
|
160
|
+
| | `APIConnectionError`, `APITimeoutError` | no answer |
|
|
161
|
+
| | `JobFailedError`, `JobTimeoutError` | background jobs |
|
|
162
|
+
|
|
163
|
+
Quote `e.request_id` when you write to support.
|
|
164
|
+
|
|
165
|
+
## Retries
|
|
166
|
+
|
|
167
|
+
The library sends a request again, up to `max_retries` times (default 2), only
|
|
168
|
+
where that cannot do the work twice:
|
|
169
|
+
|
|
170
|
+
- a `429` from your plan's per-second limit is waited out and retried;
|
|
171
|
+
- a `429` from your plan's monthly limit is raised at once (waiting would take a day);
|
|
172
|
+
- a connection that never opened is retried;
|
|
173
|
+
- a `GET` that got a 502, 503 or 504 is retried;
|
|
174
|
+
- a `POST` that failed on the server is **not** retried.
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
client = Dipsac(
|
|
180
|
+
api_key="dps_...",
|
|
181
|
+
timeout=300, # seconds; per call: client.pdf.compress(f, timeout=60)
|
|
182
|
+
max_retries=2,
|
|
183
|
+
base_url="https://api.dipsac.com",
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`DIPSAC_API_KEY` and `DIPSAC_BASE_URL` are read from the environment when the
|
|
188
|
+
arguments are left out. Pass `http_client=httpx.Client(...)` to use a proxy or
|
|
189
|
+
your own transport.
|
|
190
|
+
|
|
191
|
+
## asyncio
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import asyncio
|
|
195
|
+
from dipsac import AsyncDipsac
|
|
196
|
+
|
|
197
|
+
async def main():
|
|
198
|
+
async with AsyncDipsac() as client:
|
|
199
|
+
merged = await client.pdf.merge(["a.pdf", "b.pdf"])
|
|
200
|
+
job = await client.video.mute("clip.mp4", mode="async")
|
|
201
|
+
muted = await job.wait()
|
|
202
|
+
|
|
203
|
+
asyncio.run(main())
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Links
|
|
207
|
+
|
|
208
|
+
- API reference: <https://dipsac.com/dashboard/reference>
|
|
209
|
+
- Speech API: <https://dipsac.com/dashboard/speech>
|
|
210
|
+
- Support: contact@dipsac.com
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dipsac"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "The official Python library for the DIPSAC API: PDF, image, audio, video and SEO tools, and the AI Voice Studio."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{ name = "DIPSAC", email = "contact@dipsac.com" }]
|
|
14
|
+
keywords = ["dipsac", "pdf", "image", "audio", "video", "text-to-speech", "transcription", "api", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = ["httpx>=0.25,<1"]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23", "build>=1.2", "twine>=5"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://dipsac.com"
|
|
35
|
+
Documentation = "https://dipsac.com/dashboard/reference"
|
|
36
|
+
Source = "https://github.com/dipsactools/dipsac-sdk"
|
|
37
|
+
Issues = "https://github.com/dipsactools/dipsac-sdk/issues"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version]
|
|
40
|
+
path = "src/dipsac/_version.py"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/dipsac"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = ["src/dipsac", "README.md", "LICENSE", "tests"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""The official Python library for the DIPSAC API.
|
|
2
|
+
|
|
3
|
+
from dipsac import Dipsac
|
|
4
|
+
|
|
5
|
+
client = Dipsac(api_key="dps_...") # or set DIPSAC_API_KEY
|
|
6
|
+
client.pdf.merge(["a.pdf", "b.pdf"]).save("merged.pdf")
|
|
7
|
+
|
|
8
|
+
Docs: https://dipsac.com/dashboard/reference
|
|
9
|
+
"""
|
|
10
|
+
from ._client import DEFAULT_BASE_URL, AsyncDipsac, Dipsac
|
|
11
|
+
from ._errors import (
|
|
12
|
+
APIConnectionError,
|
|
13
|
+
APIStatusError,
|
|
14
|
+
APITimeoutError,
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
BadRequestError,
|
|
17
|
+
ConflictError,
|
|
18
|
+
DipsacError,
|
|
19
|
+
InternalServerError,
|
|
20
|
+
JobFailedError,
|
|
21
|
+
JobTimeoutError,
|
|
22
|
+
NotFoundError,
|
|
23
|
+
PayloadTooLargeError,
|
|
24
|
+
PaymentRequiredError,
|
|
25
|
+
PermissionDeniedError,
|
|
26
|
+
RateLimitError,
|
|
27
|
+
UnprocessableEntityError,
|
|
28
|
+
UnsupportedMediaTypeError,
|
|
29
|
+
)
|
|
30
|
+
from ._files import FileInput
|
|
31
|
+
from ._models import AsyncJob, DipsacFile, Job
|
|
32
|
+
from ._version import __version__
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Dipsac", "AsyncDipsac", "DipsacFile", "Job", "AsyncJob", "FileInput", "DEFAULT_BASE_URL",
|
|
36
|
+
"DipsacError", "APIConnectionError", "APITimeoutError", "APIStatusError", "BadRequestError",
|
|
37
|
+
"AuthenticationError", "PaymentRequiredError", "PermissionDeniedError", "NotFoundError",
|
|
38
|
+
"ConflictError", "PayloadTooLargeError", "UnsupportedMediaTypeError",
|
|
39
|
+
"UnprocessableEntityError", "RateLimitError", "InternalServerError", "JobFailedError",
|
|
40
|
+
"JobTimeoutError", "__version__",
|
|
41
|
+
]
|