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.
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+