switchfile-sdk 0.1.0__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.
- switchfile_sdk-0.1.0/LICENSE +21 -0
- switchfile_sdk-0.1.0/PKG-INFO +266 -0
- switchfile_sdk-0.1.0/README.md +242 -0
- switchfile_sdk-0.1.0/pyproject.toml +43 -0
- switchfile_sdk-0.1.0/setup.cfg +4 -0
- switchfile_sdk-0.1.0/src/switchfile/__init__.py +178 -0
- switchfile_sdk-0.1.0/src/switchfile/_http.py +314 -0
- switchfile_sdk-0.1.0/src/switchfile/client.py +434 -0
- switchfile_sdk-0.1.0/src/switchfile/errors.py +59 -0
- switchfile_sdk-0.1.0/src/switchfile/py.typed +0 -0
- switchfile_sdk-0.1.0/src/switchfile/version.py +2 -0
- switchfile_sdk-0.1.0/src/switchfile_sdk.egg-info/PKG-INFO +266 -0
- switchfile_sdk-0.1.0/src/switchfile_sdk.egg-info/SOURCES.txt +17 -0
- switchfile_sdk-0.1.0/src/switchfile_sdk.egg-info/dependency_links.txt +1 -0
- switchfile_sdk-0.1.0/src/switchfile_sdk.egg-info/requires.txt +3 -0
- switchfile_sdk-0.1.0/src/switchfile_sdk.egg-info/top_level.txt +1 -0
- switchfile_sdk-0.1.0/tests/test_client.py +120 -0
- switchfile_sdk-0.1.0/tests/test_v1.py +287 -0
- switchfile_sdk-0.1.0/tests/test_v2.py +196 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aura UG (haftungsbeschränkt)
|
|
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.
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: switchfile-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the switchfile file conversion API. Drop-in replacement for the CloudConvert v2 SDK, plus a one-call convert() helper.
|
|
5
|
+
Author-email: "Aura UG (haftungsbeschränkt)" <hello@switchfile.io>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://switchfile.io/developers#sdks
|
|
8
|
+
Project-URL: Documentation, https://switchfile.io/developers
|
|
9
|
+
Keywords: switchfile,file conversion,converter,cloudconvert,api,sdk
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Topic :: Multimedia
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# switchfile for Python
|
|
26
|
+
|
|
27
|
+
The official Python SDK for the [switchfile](https://switchfile.io) file conversion API.
|
|
28
|
+
|
|
29
|
+
- The same functions and return values as the CloudConvert Python SDK (`Job.create`, `Job.wait`, `Task.upload`,
|
|
30
|
+
`Webhook.verify`, `download` and more), so existing code keeps working after you change the import.
|
|
31
|
+
- `convert()` for the simple API: one call with a file and a target format, and you get the finished conversion back.
|
|
32
|
+
- No dependencies. Python 3.10 or newer.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install switchfile-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Moving from CloudConvert
|
|
39
|
+
|
|
40
|
+
Change the import and the configuration. Everything else stays the same.
|
|
41
|
+
|
|
42
|
+
```diff
|
|
43
|
+
- import cloudconvert
|
|
44
|
+
- cloudconvert.configure(api_key=os.environ["CLOUDCONVERT_API_KEY"], sandbox=False)
|
|
45
|
+
+ import switchfile as cloudconvert
|
|
46
|
+
+ cloudconvert.configure(api_key=os.environ["SWITCHFILE_API_KEY"], sandbox=False)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Create an API key in the [dashboard](https://switchfile.io/dashboard/keys). Keys starting with `sf_test_` are
|
|
50
|
+
sandbox keys: they never use credits.
|
|
51
|
+
|
|
52
|
+
What is different:
|
|
53
|
+
|
|
54
|
+
| CloudConvert | switchfile |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `sandbox=True` uses a separate sandbox server | The key decides: `sf_test_` keys are sandbox keys. `sandbox=True` refuses live keys, so tests cannot use credits by mistake |
|
|
57
|
+
| `cloudconvert.default()` reads `CLOUDCONVERT_API_KEY` | `switchfile.default()` reads `SWITCHFILE_API_KEY`, `SWITCHFILE_API_URL` and `SWITCHFILE_SANDBOX` |
|
|
58
|
+
| Operations such as `optimize`, `merge`, `capture-website`, `import/s3` | Tasks: `import/url`, `import/upload`, `import/base64`, `convert`, `export/url` |
|
|
59
|
+
| `SignedUrl.sign()` | Not available |
|
|
60
|
+
| `Task.create()`, `Task.delete()` | Not available; create tasks inside a job with `Job.create()` |
|
|
61
|
+
| Errors from `cloudconvert.exceptions` | `switchfile.SwitchfileError` with `code`, `status` and `errors` |
|
|
62
|
+
|
|
63
|
+
## Convert a file in one call
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import switchfile
|
|
67
|
+
|
|
68
|
+
switchfile.configure(api_key=os.environ["SWITCHFILE_API_KEY"])
|
|
69
|
+
|
|
70
|
+
conversion = switchfile.convert("talk.mkv", "mp4")
|
|
71
|
+
switchfile.download(conversion, "talk.mp4")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`convert(file, to, options)` takes a file path, `bytes`, an open file, a public URL as a string
|
|
75
|
+
(`"https://..."`) or `{"url": ...}`, `{"upload_id": ...}` for a file sent through `/v1/uploads`, or
|
|
76
|
+
`{"from": conversion_id}` to convert an earlier input again.
|
|
77
|
+
|
|
78
|
+
Files up to 50 MB are sent with the request, larger files (up to your plan's limit) go through an upload URL and are
|
|
79
|
+
read from disk while they upload. The call waits until the conversion is done and raises `ConversionFailedError` if
|
|
80
|
+
it fails.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
conversion = switchfile.convert(
|
|
84
|
+
data, # bytes
|
|
85
|
+
"jpg",
|
|
86
|
+
{"quality": 80}, # settings for the conversion
|
|
87
|
+
filename="photo.heic", # name of the input (needed for bytes)
|
|
88
|
+
webhook_url="https://example.com/hooks/switchfile",
|
|
89
|
+
wait=True, # False: return right away with status queued
|
|
90
|
+
timeout=600, # stop waiting after 10 minutes
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for f in conversion["result"]["files"]: # e.g. one image per page
|
|
94
|
+
switchfile.download(f["url"], f["filename"])
|
|
95
|
+
data = switchfile.download(conversion) # bytes without a file name
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A preset from your dashboard sets the target format and its settings. Leave out `to` and pass the ID or name;
|
|
99
|
+
settings in `options` override single values of the preset:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
switchfile.convert("photo.heic", preset="Web images")
|
|
103
|
+
switchfile.convert("photo.heic", options={"quality": 90}, preset="Web images")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Without storage
|
|
107
|
+
|
|
108
|
+
`convert_without_storage` sends the file with the request and gets the result back in the response. switchfile keeps
|
|
109
|
+
neither of them, only an entry in the usage history without the file name. Files can be up to 50 MB, several result
|
|
110
|
+
files come back as one ZIP.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
result = switchfile.convert_without_storage("invoice.docx", "pdf")
|
|
114
|
+
with open(result.filename or "invoice.pdf", "wb") as f:
|
|
115
|
+
f.write(result.data)
|
|
116
|
+
result.conversion_id # the entry in the usage history
|
|
117
|
+
result.credits # credits used
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For jobs, pass `"store": False` to `Job.create` with three tasks, one of each: `import/base64`, `convert` and
|
|
121
|
+
`export/url`. The job comes back finished, and each file of the export task has its bytes as base64 in `content`.
|
|
122
|
+
`client.account.update(no_store=True)` makes the account refuse every request that would store a file. Keep using
|
|
123
|
+
`convert_without_storage` and `"store": False` after that: the other calls expect a stored conversion and are rejected.
|
|
124
|
+
Details are in the [guide](https://switchfile.io/docs/no-store).
|
|
125
|
+
|
|
126
|
+
More calls for the simple API:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
switchfile.Conversion.find(id) # status and result
|
|
130
|
+
switchfile.Conversion.wait(id) # until finished
|
|
131
|
+
switchfile.Conversion.list([id1, id2]) # up to 50 at once
|
|
132
|
+
switchfile.Conversion.delete(id) # delete input and result now
|
|
133
|
+
switchfile.formats("heic") # targets for one input format (no key needed)
|
|
134
|
+
switchfile.Account.get() # credits, free quota, webhook signing secret
|
|
135
|
+
switchfile.Account.update("https://example.com/hooks/switchfile")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Jobs and tasks
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
job = switchfile.Job.create(payload={
|
|
142
|
+
"tasks": {
|
|
143
|
+
"import-my-file": {"operation": "import/url", "url": "https://example.com/report.docx"},
|
|
144
|
+
"convert-my-file": {"operation": "convert", "input": "import-my-file", "output_format": "pdf"},
|
|
145
|
+
"export-my-file": {"operation": "export/url", "input": "convert-my-file"},
|
|
146
|
+
},
|
|
147
|
+
"tag": "order-1234",
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
job = switchfile.Job.wait(id=job["id"])
|
|
151
|
+
for file in switchfile.Job.get_export_urls(job):
|
|
152
|
+
switchfile.download(filename=file["filename"], url=file["url"])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`Job.wait()` returns the job when it is `finished` or `error`; it does not raise for a failed job. Check
|
|
156
|
+
`job["status"]` and the `code` and `message` of each task.
|
|
157
|
+
|
|
158
|
+
### Uploading files
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
job = switchfile.Job.create(payload={
|
|
162
|
+
"tasks": {
|
|
163
|
+
"upload-my-file": {"operation": "import/upload"},
|
|
164
|
+
"convert-my-file": {"operation": "convert", "input": "upload-my-file", "output_format": "pdf"},
|
|
165
|
+
"export-my-file": {"operation": "export/url", "input": "convert-my-file"},
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
upload_task = [t for t in job["tasks"] if t["name"] == "upload-my-file"][0]
|
|
170
|
+
switchfile.Task.upload(file_name="path/to/report.docx", task=upload_task)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`Task.upload()` also takes `bytes` or an open file (pass `filename=` for bytes).
|
|
174
|
+
|
|
175
|
+
### All functions
|
|
176
|
+
|
|
177
|
+
| Function | Returns |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `Job.create(payload)` | job `dict` |
|
|
180
|
+
| `Job.find(id)`, `Job.show(id)` | job `dict` |
|
|
181
|
+
| `Job.wait(id, timeout=None)` | job `dict` once it is `finished` or `error` |
|
|
182
|
+
| `Job.all(params)` | `list` of jobs, filters such as `{"filter[tag]": "..."}` |
|
|
183
|
+
| `Job.delete(id)` | `True` |
|
|
184
|
+
| `Job.get_export_urls(job)` | files of all finished `export/url` tasks |
|
|
185
|
+
| `Task.find(id)`, `Task.show(id)` | task `dict` |
|
|
186
|
+
| `Task.wait(id)` | task `dict` |
|
|
187
|
+
| `Task.all(params)` | `list` of tasks |
|
|
188
|
+
| `Task.cancel(id)` | `True` if the task is cancelled |
|
|
189
|
+
| `Task.retry(id)` | the new task |
|
|
190
|
+
| `Task.upload(file_name, task)` | `True` if the upload finished the task |
|
|
191
|
+
| `User.me()` | account with credits and today's free quota |
|
|
192
|
+
| `Webhook.verify(payload, signature, secret)` | `bool` |
|
|
193
|
+
| `Webhook.construct_event(payload, signature, secret)` | the parsed event, raises if the signature is wrong |
|
|
194
|
+
| `download(url, filename)` | the path, or `bytes` without a file name |
|
|
195
|
+
|
|
196
|
+
### Several keys
|
|
197
|
+
|
|
198
|
+
The functions above use one client for the whole program. For several keys or servers, create clients:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
client = switchfile.Client(api_key="sf_live_...", timeout=60, max_retries=3)
|
|
202
|
+
job = client.jobs.create({...})
|
|
203
|
+
client.tasks.upload("report.docx", upload_task)
|
|
204
|
+
conversion = client.convert("report.docx", "pdf")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Webhooks
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from flask import Flask, request
|
|
211
|
+
|
|
212
|
+
app = Flask(__name__)
|
|
213
|
+
|
|
214
|
+
@app.post("/hooks/switchfile")
|
|
215
|
+
def hook():
|
|
216
|
+
signature = request.headers.get("CloudConvert-Signature") or request.headers.get("Switchfile-Signature")
|
|
217
|
+
if not switchfile.Webhook.verify(request.get_data(), signature, os.environ["SWITCHFILE_WEBHOOK_SECRET"]):
|
|
218
|
+
return "", 400
|
|
219
|
+
event = request.get_json()
|
|
220
|
+
# job.finished / job.failed: event["job"]
|
|
221
|
+
# conversion.finished / conversion.failed: event["data"]
|
|
222
|
+
return "", 200
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Jobs send `job.finished` and `job.failed` with the header `CloudConvert-Signature`. Conversions from `convert()` send
|
|
226
|
+
`conversion.finished` and `conversion.failed` with `Switchfile-Signature`. Both are signed the same way. The signing
|
|
227
|
+
secret is `webhook_signing_secret` from `switchfile.Account.get()` and in the dashboard.
|
|
228
|
+
|
|
229
|
+
## Settings
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
switchfile.configure(
|
|
233
|
+
api_key="sf_test_...", # default: SWITCHFILE_API_KEY
|
|
234
|
+
base_url="https://api.switchfile.io", # default: SWITCHFILE_API_URL or https://api.switchfile.io
|
|
235
|
+
sandbox=True, # only accept sandbox keys (sf_test_)
|
|
236
|
+
timeout=60, # per request, in seconds
|
|
237
|
+
max_retries=3, # network errors, 429 and 5xx
|
|
238
|
+
user_agent="my-app/1.0", # added to the User-Agent header
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Requests are repeated after network errors, `429 RATE_LIMIT_EXCEEDED` (after `Retry-After`) and server errors.
|
|
243
|
+
`convert()` sends an `Idempotency-Key`, so a repeated request never creates a second conversion.
|
|
244
|
+
|
|
245
|
+
## Errors
|
|
246
|
+
|
|
247
|
+
Every error is a `switchfile.SwitchfileError` with a stable `code` (for example `UNAUTHENTICATED`, `INVALID_DATA`,
|
|
248
|
+
`UNSUPPORTED_PAIR`, `CREDITS_EXCEEDED`, `RATE_LIMIT_EXCEEDED`), the HTTP `status` and, for `INVALID_DATA`, `errors`
|
|
249
|
+
per field. A failed conversion from `convert()` raises `switchfile.ConversionFailedError` with the conversion in
|
|
250
|
+
`conversion`.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
try:
|
|
254
|
+
switchfile.convert("notes.txt", "mp4")
|
|
255
|
+
except switchfile.ConversionFailedError as e:
|
|
256
|
+
print("failed:", e.code, e.conversion["id"])
|
|
257
|
+
except switchfile.SwitchfileError as e:
|
|
258
|
+
print(e.status, e.code, e.message)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Links
|
|
262
|
+
|
|
263
|
+
- [API documentation](https://switchfile.io/developers)
|
|
264
|
+
- [Supported formats](https://switchfile.io/convert)
|
|
265
|
+
|
|
266
|
+
License: MIT, Aura UG (haftungsbeschränkt).
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# switchfile for Python
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [switchfile](https://switchfile.io) file conversion API.
|
|
4
|
+
|
|
5
|
+
- The same functions and return values as the CloudConvert Python SDK (`Job.create`, `Job.wait`, `Task.upload`,
|
|
6
|
+
`Webhook.verify`, `download` and more), so existing code keeps working after you change the import.
|
|
7
|
+
- `convert()` for the simple API: one call with a file and a target format, and you get the finished conversion back.
|
|
8
|
+
- No dependencies. Python 3.10 or newer.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install switchfile-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Moving from CloudConvert
|
|
15
|
+
|
|
16
|
+
Change the import and the configuration. Everything else stays the same.
|
|
17
|
+
|
|
18
|
+
```diff
|
|
19
|
+
- import cloudconvert
|
|
20
|
+
- cloudconvert.configure(api_key=os.environ["CLOUDCONVERT_API_KEY"], sandbox=False)
|
|
21
|
+
+ import switchfile as cloudconvert
|
|
22
|
+
+ cloudconvert.configure(api_key=os.environ["SWITCHFILE_API_KEY"], sandbox=False)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Create an API key in the [dashboard](https://switchfile.io/dashboard/keys). Keys starting with `sf_test_` are
|
|
26
|
+
sandbox keys: they never use credits.
|
|
27
|
+
|
|
28
|
+
What is different:
|
|
29
|
+
|
|
30
|
+
| CloudConvert | switchfile |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `sandbox=True` uses a separate sandbox server | The key decides: `sf_test_` keys are sandbox keys. `sandbox=True` refuses live keys, so tests cannot use credits by mistake |
|
|
33
|
+
| `cloudconvert.default()` reads `CLOUDCONVERT_API_KEY` | `switchfile.default()` reads `SWITCHFILE_API_KEY`, `SWITCHFILE_API_URL` and `SWITCHFILE_SANDBOX` |
|
|
34
|
+
| Operations such as `optimize`, `merge`, `capture-website`, `import/s3` | Tasks: `import/url`, `import/upload`, `import/base64`, `convert`, `export/url` |
|
|
35
|
+
| `SignedUrl.sign()` | Not available |
|
|
36
|
+
| `Task.create()`, `Task.delete()` | Not available; create tasks inside a job with `Job.create()` |
|
|
37
|
+
| Errors from `cloudconvert.exceptions` | `switchfile.SwitchfileError` with `code`, `status` and `errors` |
|
|
38
|
+
|
|
39
|
+
## Convert a file in one call
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import switchfile
|
|
43
|
+
|
|
44
|
+
switchfile.configure(api_key=os.environ["SWITCHFILE_API_KEY"])
|
|
45
|
+
|
|
46
|
+
conversion = switchfile.convert("talk.mkv", "mp4")
|
|
47
|
+
switchfile.download(conversion, "talk.mp4")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`convert(file, to, options)` takes a file path, `bytes`, an open file, a public URL as a string
|
|
51
|
+
(`"https://..."`) or `{"url": ...}`, `{"upload_id": ...}` for a file sent through `/v1/uploads`, or
|
|
52
|
+
`{"from": conversion_id}` to convert an earlier input again.
|
|
53
|
+
|
|
54
|
+
Files up to 50 MB are sent with the request, larger files (up to your plan's limit) go through an upload URL and are
|
|
55
|
+
read from disk while they upload. The call waits until the conversion is done and raises `ConversionFailedError` if
|
|
56
|
+
it fails.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
conversion = switchfile.convert(
|
|
60
|
+
data, # bytes
|
|
61
|
+
"jpg",
|
|
62
|
+
{"quality": 80}, # settings for the conversion
|
|
63
|
+
filename="photo.heic", # name of the input (needed for bytes)
|
|
64
|
+
webhook_url="https://example.com/hooks/switchfile",
|
|
65
|
+
wait=True, # False: return right away with status queued
|
|
66
|
+
timeout=600, # stop waiting after 10 minutes
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for f in conversion["result"]["files"]: # e.g. one image per page
|
|
70
|
+
switchfile.download(f["url"], f["filename"])
|
|
71
|
+
data = switchfile.download(conversion) # bytes without a file name
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A preset from your dashboard sets the target format and its settings. Leave out `to` and pass the ID or name;
|
|
75
|
+
settings in `options` override single values of the preset:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
switchfile.convert("photo.heic", preset="Web images")
|
|
79
|
+
switchfile.convert("photo.heic", options={"quality": 90}, preset="Web images")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Without storage
|
|
83
|
+
|
|
84
|
+
`convert_without_storage` sends the file with the request and gets the result back in the response. switchfile keeps
|
|
85
|
+
neither of them, only an entry in the usage history without the file name. Files can be up to 50 MB, several result
|
|
86
|
+
files come back as one ZIP.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
result = switchfile.convert_without_storage("invoice.docx", "pdf")
|
|
90
|
+
with open(result.filename or "invoice.pdf", "wb") as f:
|
|
91
|
+
f.write(result.data)
|
|
92
|
+
result.conversion_id # the entry in the usage history
|
|
93
|
+
result.credits # credits used
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For jobs, pass `"store": False` to `Job.create` with three tasks, one of each: `import/base64`, `convert` and
|
|
97
|
+
`export/url`. The job comes back finished, and each file of the export task has its bytes as base64 in `content`.
|
|
98
|
+
`client.account.update(no_store=True)` makes the account refuse every request that would store a file. Keep using
|
|
99
|
+
`convert_without_storage` and `"store": False` after that: the other calls expect a stored conversion and are rejected.
|
|
100
|
+
Details are in the [guide](https://switchfile.io/docs/no-store).
|
|
101
|
+
|
|
102
|
+
More calls for the simple API:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
switchfile.Conversion.find(id) # status and result
|
|
106
|
+
switchfile.Conversion.wait(id) # until finished
|
|
107
|
+
switchfile.Conversion.list([id1, id2]) # up to 50 at once
|
|
108
|
+
switchfile.Conversion.delete(id) # delete input and result now
|
|
109
|
+
switchfile.formats("heic") # targets for one input format (no key needed)
|
|
110
|
+
switchfile.Account.get() # credits, free quota, webhook signing secret
|
|
111
|
+
switchfile.Account.update("https://example.com/hooks/switchfile")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Jobs and tasks
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
job = switchfile.Job.create(payload={
|
|
118
|
+
"tasks": {
|
|
119
|
+
"import-my-file": {"operation": "import/url", "url": "https://example.com/report.docx"},
|
|
120
|
+
"convert-my-file": {"operation": "convert", "input": "import-my-file", "output_format": "pdf"},
|
|
121
|
+
"export-my-file": {"operation": "export/url", "input": "convert-my-file"},
|
|
122
|
+
},
|
|
123
|
+
"tag": "order-1234",
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
job = switchfile.Job.wait(id=job["id"])
|
|
127
|
+
for file in switchfile.Job.get_export_urls(job):
|
|
128
|
+
switchfile.download(filename=file["filename"], url=file["url"])
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`Job.wait()` returns the job when it is `finished` or `error`; it does not raise for a failed job. Check
|
|
132
|
+
`job["status"]` and the `code` and `message` of each task.
|
|
133
|
+
|
|
134
|
+
### Uploading files
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
job = switchfile.Job.create(payload={
|
|
138
|
+
"tasks": {
|
|
139
|
+
"upload-my-file": {"operation": "import/upload"},
|
|
140
|
+
"convert-my-file": {"operation": "convert", "input": "upload-my-file", "output_format": "pdf"},
|
|
141
|
+
"export-my-file": {"operation": "export/url", "input": "convert-my-file"},
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
upload_task = [t for t in job["tasks"] if t["name"] == "upload-my-file"][0]
|
|
146
|
+
switchfile.Task.upload(file_name="path/to/report.docx", task=upload_task)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`Task.upload()` also takes `bytes` or an open file (pass `filename=` for bytes).
|
|
150
|
+
|
|
151
|
+
### All functions
|
|
152
|
+
|
|
153
|
+
| Function | Returns |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `Job.create(payload)` | job `dict` |
|
|
156
|
+
| `Job.find(id)`, `Job.show(id)` | job `dict` |
|
|
157
|
+
| `Job.wait(id, timeout=None)` | job `dict` once it is `finished` or `error` |
|
|
158
|
+
| `Job.all(params)` | `list` of jobs, filters such as `{"filter[tag]": "..."}` |
|
|
159
|
+
| `Job.delete(id)` | `True` |
|
|
160
|
+
| `Job.get_export_urls(job)` | files of all finished `export/url` tasks |
|
|
161
|
+
| `Task.find(id)`, `Task.show(id)` | task `dict` |
|
|
162
|
+
| `Task.wait(id)` | task `dict` |
|
|
163
|
+
| `Task.all(params)` | `list` of tasks |
|
|
164
|
+
| `Task.cancel(id)` | `True` if the task is cancelled |
|
|
165
|
+
| `Task.retry(id)` | the new task |
|
|
166
|
+
| `Task.upload(file_name, task)` | `True` if the upload finished the task |
|
|
167
|
+
| `User.me()` | account with credits and today's free quota |
|
|
168
|
+
| `Webhook.verify(payload, signature, secret)` | `bool` |
|
|
169
|
+
| `Webhook.construct_event(payload, signature, secret)` | the parsed event, raises if the signature is wrong |
|
|
170
|
+
| `download(url, filename)` | the path, or `bytes` without a file name |
|
|
171
|
+
|
|
172
|
+
### Several keys
|
|
173
|
+
|
|
174
|
+
The functions above use one client for the whole program. For several keys or servers, create clients:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
client = switchfile.Client(api_key="sf_live_...", timeout=60, max_retries=3)
|
|
178
|
+
job = client.jobs.create({...})
|
|
179
|
+
client.tasks.upload("report.docx", upload_task)
|
|
180
|
+
conversion = client.convert("report.docx", "pdf")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Webhooks
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from flask import Flask, request
|
|
187
|
+
|
|
188
|
+
app = Flask(__name__)
|
|
189
|
+
|
|
190
|
+
@app.post("/hooks/switchfile")
|
|
191
|
+
def hook():
|
|
192
|
+
signature = request.headers.get("CloudConvert-Signature") or request.headers.get("Switchfile-Signature")
|
|
193
|
+
if not switchfile.Webhook.verify(request.get_data(), signature, os.environ["SWITCHFILE_WEBHOOK_SECRET"]):
|
|
194
|
+
return "", 400
|
|
195
|
+
event = request.get_json()
|
|
196
|
+
# job.finished / job.failed: event["job"]
|
|
197
|
+
# conversion.finished / conversion.failed: event["data"]
|
|
198
|
+
return "", 200
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Jobs send `job.finished` and `job.failed` with the header `CloudConvert-Signature`. Conversions from `convert()` send
|
|
202
|
+
`conversion.finished` and `conversion.failed` with `Switchfile-Signature`. Both are signed the same way. The signing
|
|
203
|
+
secret is `webhook_signing_secret` from `switchfile.Account.get()` and in the dashboard.
|
|
204
|
+
|
|
205
|
+
## Settings
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
switchfile.configure(
|
|
209
|
+
api_key="sf_test_...", # default: SWITCHFILE_API_KEY
|
|
210
|
+
base_url="https://api.switchfile.io", # default: SWITCHFILE_API_URL or https://api.switchfile.io
|
|
211
|
+
sandbox=True, # only accept sandbox keys (sf_test_)
|
|
212
|
+
timeout=60, # per request, in seconds
|
|
213
|
+
max_retries=3, # network errors, 429 and 5xx
|
|
214
|
+
user_agent="my-app/1.0", # added to the User-Agent header
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Requests are repeated after network errors, `429 RATE_LIMIT_EXCEEDED` (after `Retry-After`) and server errors.
|
|
219
|
+
`convert()` sends an `Idempotency-Key`, so a repeated request never creates a second conversion.
|
|
220
|
+
|
|
221
|
+
## Errors
|
|
222
|
+
|
|
223
|
+
Every error is a `switchfile.SwitchfileError` with a stable `code` (for example `UNAUTHENTICATED`, `INVALID_DATA`,
|
|
224
|
+
`UNSUPPORTED_PAIR`, `CREDITS_EXCEEDED`, `RATE_LIMIT_EXCEEDED`), the HTTP `status` and, for `INVALID_DATA`, `errors`
|
|
225
|
+
per field. A failed conversion from `convert()` raises `switchfile.ConversionFailedError` with the conversion in
|
|
226
|
+
`conversion`.
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
try:
|
|
230
|
+
switchfile.convert("notes.txt", "mp4")
|
|
231
|
+
except switchfile.ConversionFailedError as e:
|
|
232
|
+
print("failed:", e.code, e.conversion["id"])
|
|
233
|
+
except switchfile.SwitchfileError as e:
|
|
234
|
+
print(e.status, e.code, e.message)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Links
|
|
238
|
+
|
|
239
|
+
- [API documentation](https://switchfile.io/developers)
|
|
240
|
+
- [Supported formats](https://switchfile.io/convert)
|
|
241
|
+
|
|
242
|
+
License: MIT, Aura UG (haftungsbeschränkt).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Paket `switchfile-sdk` für PyPI (der Name `switchfile` ist dort wegen `switch-file` gesperrt); importiert wird `switchfile`. Keine Laufzeit-Abhängigkeiten (nur Standardbibliothek), Python >= 3.10.
|
|
2
|
+
# Version steht nur hier und in src/switchfile/version.py (tests/test_client.py prüft die Gleichheit).
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["setuptools>=69"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "switchfile-sdk"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Python SDK for the switchfile file conversion API. Drop-in replacement for the CloudConvert v2 SDK, plus a one-call convert() helper."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "Aura UG (haftungsbeschränkt)", email = "hello@switchfile.io" }]
|
|
16
|
+
keywords = ["switchfile", "file conversion", "converter", "cloudconvert", "api", "sdk"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Topic :: Multimedia",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = []
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
test = ["pytest>=8"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://switchfile.io/developers#sdks"
|
|
34
|
+
Documentation = "https://switchfile.io/developers"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
switchfile = ["py.typed"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|