fileboy 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.
- fileboy-0.1.0/LICENSE +21 -0
- fileboy-0.1.0/PKG-INFO +207 -0
- fileboy-0.1.0/README.md +174 -0
- fileboy-0.1.0/pyproject.toml +47 -0
- fileboy-0.1.0/setup.cfg +4 -0
- fileboy-0.1.0/src/fileboy/__init__.py +93 -0
- fileboy-0.1.0/src/fileboy/_version.py +1 -0
- fileboy-0.1.0/src/fileboy/client.py +414 -0
- fileboy-0.1.0/src/fileboy/errors.py +62 -0
- fileboy-0.1.0/src/fileboy/file_data.py +65 -0
- fileboy-0.1.0/src/fileboy/mime.py +27 -0
- fileboy-0.1.0/src/fileboy/py.typed +0 -0
- fileboy-0.1.0/src/fileboy/token.py +53 -0
- fileboy-0.1.0/src/fileboy/transforms.py +149 -0
- fileboy-0.1.0/src/fileboy/webhooks.py +106 -0
- fileboy-0.1.0/src/fileboy.egg-info/PKG-INFO +207 -0
- fileboy-0.1.0/src/fileboy.egg-info/SOURCES.txt +22 -0
- fileboy-0.1.0/src/fileboy.egg-info/dependency_links.txt +1 -0
- fileboy-0.1.0/src/fileboy.egg-info/requires.txt +5 -0
- fileboy-0.1.0/src/fileboy.egg-info/top_level.txt +1 -0
- fileboy-0.1.0/tests/test_client.py +272 -0
- fileboy-0.1.0/tests/test_token.py +37 -0
- fileboy-0.1.0/tests/test_transforms.py +43 -0
- fileboy-0.1.0/tests/test_webhooks.py +73 -0
fileboy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) CD2 Solutions Ltd
|
|
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.
|
fileboy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fileboy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for Fileboy: upload files and videos, build image transform URLs, read video data.
|
|
5
|
+
Author: Fileboy
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://fileboy.io/docs/libraries/python
|
|
8
|
+
Project-URL: Documentation, https://fileboy.io/docs/libraries/python
|
|
9
|
+
Project-URL: Changelog, https://github.com/CD2/Fileboy/blob/master/packages/python/CHANGELOG.md
|
|
10
|
+
Keywords: fileboy,file upload,image resize,video hosting,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests>=2.25
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
31
|
+
Requires-Dist: requests>=2.25; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Fileboy for Python
|
|
35
|
+
|
|
36
|
+
The official Python client for [Fileboy](https://fileboy.io): upload files and videos, build image
|
|
37
|
+
URLs that resize on the fly, and read video data. Its only dependency is `requests`.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install fileboy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Python 3.8 or newer.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import os
|
|
51
|
+
import fileboy
|
|
52
|
+
|
|
53
|
+
client = fileboy.Client(public_key=os.environ["FILEBOY_PUBLIC_KEY"])
|
|
54
|
+
|
|
55
|
+
file = client.upload_file("report.pdf") # a path, a binary file object or bytes
|
|
56
|
+
print(file.id) # store this
|
|
57
|
+
|
|
58
|
+
client.build_file_url(file.id, download=True)
|
|
59
|
+
client.build_image_url(file.id, transforms=[{"type": "resize", "width": 300}, {"type": "format", "format": "webp"}])
|
|
60
|
+
# https://api.fileboy.io/images/get/<id>?transform=resize:300x_;format:webp
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Django and other frameworks
|
|
64
|
+
|
|
65
|
+
Configure once, then call `fileboy.client()` anywhere:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# settings.py (or an AppConfig.ready())
|
|
69
|
+
import os
|
|
70
|
+
import fileboy
|
|
71
|
+
|
|
72
|
+
fileboy.configure(
|
|
73
|
+
public_key=os.environ["FILEBOY_PUBLIC_KEY"],
|
|
74
|
+
# private_key=..., only if your app uses signed requests
|
|
75
|
+
# api_url="https://api.fileboy.io",
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# views.py
|
|
81
|
+
import fileboy
|
|
82
|
+
|
|
83
|
+
def upload(request):
|
|
84
|
+
file = fileboy.client().upload_file(request.FILES["file"], name=request.FILES["file"].name)
|
|
85
|
+
...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`FILEBOY_PUBLIC_KEY`, `FILEBOY_PRIVATE_KEY` and `FILEBOY_API_URL` are read from the environment when you do not set them.
|
|
89
|
+
|
|
90
|
+
## What you can do
|
|
91
|
+
|
|
92
|
+
| Task | Method |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| Upload a path, file object or bytes | `upload_file(file, name=, mime=, metadata=)` |
|
|
95
|
+
| Upload from a path | `upload_file_from_path(path, ...)` |
|
|
96
|
+
| Upload from a URL | `upload_file_from_url(url, name=, metadata=, expect=)` |
|
|
97
|
+
| File details | `fetch_file_data(id_or_label)` |
|
|
98
|
+
| File URL | `build_file_url(id_or_label, filename=, download=)` |
|
|
99
|
+
| Image URL with transforms | `build_image_url(id_or_label, transforms=, filename=, download=)` |
|
|
100
|
+
| Download bytes | `download_file(id_or_label, range=)`, `download_image(id_or_label, transforms=, range=)` |
|
|
101
|
+
| Upload a video | `upload_video(file, name=, mime=)` |
|
|
102
|
+
| Video details | `fetch_video_data(id)`, `fetch_video_info(id)` |
|
|
103
|
+
| Manifest and poster URLs | `build_video_manifest_url(id, type="hls")`, `build_video_poster_url(id)` |
|
|
104
|
+
| Custom poster | `upload_video_poster(video_id, image)`, `download_video_poster(video_id)` |
|
|
105
|
+
| Transcripts | `upload_video_transcript(id, file, language="English")`, `fetch_video_transcript(id, transcript_id)` |
|
|
106
|
+
| Signed access token | `create_access_token(read=, write=, upload=, expires_in=)` |
|
|
107
|
+
|
|
108
|
+
An id that is a UUID is used as is. Anything else is treated as a label in your app, and a value
|
|
109
|
+
that already contains a `+` is passed through. Labels are created in the dashboard.
|
|
110
|
+
|
|
111
|
+
Uploads return a `FileData` dataclass (`id`, `name`, `original_filename`, `mime`, `metadata`,
|
|
112
|
+
`file_size`, `content_hash`, `signed`, `dies_at`, `created_at`). `upload_video` returns a `Video`
|
|
113
|
+
(`id`, `name`, `raw_file_id`). `range` is a `(first_byte, last_byte)` tuple.
|
|
114
|
+
|
|
115
|
+
### Image transforms
|
|
116
|
+
|
|
117
|
+
`transforms=` takes a list of dicts applied in order. The image service supports `resize`,
|
|
118
|
+
`format`, `quality`, `background` and `invert`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
[
|
|
122
|
+
{"type": "resize", "width": 200, "height": 200, "fit": "cover"}, # fit: contain cover fill inside outside
|
|
123
|
+
{"type": "resize", "width": 400, "fit": "contain", "position": "top-left", "background": "#ffffff"},
|
|
124
|
+
{"type": "format", "format": "webp"}, # jpeg png webp tiff bmp
|
|
125
|
+
{"type": "quality", "value": 75},
|
|
126
|
+
{"type": "background", "color": "#fff"},
|
|
127
|
+
{"type": "invert"},
|
|
128
|
+
]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also pass a dict of `name: value`, such as `{"resize": "300x_", "format": "webp", "quality": 75}`, or a ready-made string.
|
|
132
|
+
|
|
133
|
+
## Errors
|
|
134
|
+
|
|
135
|
+
Every error is a `FileboyError` with `status`, `code` (the API's error code) and `body`.
|
|
136
|
+
|
|
137
|
+
| Class | When |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `AuthenticationError` | 401 |
|
|
140
|
+
| `FileboyPermissionError` | 403, including plan limits |
|
|
141
|
+
| `NotFoundError` | 404 |
|
|
142
|
+
| `RateLimitError` | 429 |
|
|
143
|
+
| `BadRequestError` | other 4xx, such as a file that is too large |
|
|
144
|
+
| `ServerError` | 5xx |
|
|
145
|
+
| `FileboyConnectionError` | timeouts and network failures |
|
|
146
|
+
| `InvalidArgumentError`, `ConfigurationError` | bad arguments, before any request |
|
|
147
|
+
|
|
148
|
+
`FileboyPermissionError` and `FileboyConnectionError` are prefixed so they do not shadow the
|
|
149
|
+
Python built-ins `PermissionError` and `ConnectionError`.
|
|
150
|
+
|
|
151
|
+
## Notes
|
|
152
|
+
|
|
153
|
+
* File, image and video reads need no authorization, so URL builders never sign anything.
|
|
154
|
+
* The shared `demo` key only accepts small images and cannot upload video.
|
|
155
|
+
* There is no endpoint yet to update, delete or list files, so the client has no such methods.
|
|
156
|
+
* The client uses a `requests.Session`, so connections are reused between calls.
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install -e ".[dev]"
|
|
162
|
+
pytest
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The tests run against a local fake server. The transform builder is also checked against
|
|
166
|
+
`packages/sdk-fixtures/transforms.json`, the same cases the Go image parser is tested with.
|
|
167
|
+
|
|
168
|
+
MIT licensed.
|
|
169
|
+
|
|
170
|
+
## Webhooks
|
|
171
|
+
|
|
172
|
+
Verify the requests Fileboy sends to your endpoints. Use the raw body, exactly as received:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Django
|
|
176
|
+
import fileboy
|
|
177
|
+
from django.http import HttpResponse, HttpResponseBadRequest
|
|
178
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
179
|
+
|
|
180
|
+
@csrf_exempt
|
|
181
|
+
def fileboy_webhook(request):
|
|
182
|
+
try:
|
|
183
|
+
event = fileboy.verify_webhook(request.body, request.headers, settings.FILEBOY_WEBHOOK_SECRET)
|
|
184
|
+
except fileboy.WebhookVerificationError as error:
|
|
185
|
+
return HttpResponseBadRequest(error.code) # "invalid_signature", "timestamp_out_of_tolerance", ...
|
|
186
|
+
if event["type"] == "video.ready":
|
|
187
|
+
... # event["resource"]["urls"]["hls"], event["resource"]["duration"]
|
|
188
|
+
return HttpResponse(status=200)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`verify_webhook` returns the parsed event (a dict) or raises `WebhookVerificationError`. The timestamp
|
|
192
|
+
must be within 5 minutes (`tolerance_seconds=` changes that). Events carry a unique `id` that is the same
|
|
193
|
+
on every retry, so use it to ignore duplicates. `fileboy.sign_webhook(body, timestamp, secret)` builds a
|
|
194
|
+
signature for your own tests.
|
|
195
|
+
|
|
196
|
+
## Publishing (maintainers)
|
|
197
|
+
|
|
198
|
+
You need a [PyPI](https://pypi.org) account that owns `fileboy` (the first upload claims the name) and an API token (account settings, scope: that project once it exists). Try [TestPyPI](https://test.pypi.org) first.
|
|
199
|
+
|
|
200
|
+
1. Bump the version in `src/fileboy/_version.py` and `pyproject.toml` (keep them equal) and add a section to `CHANGELOG.md`.
|
|
201
|
+
2. `pip install -e '.[dev]' && pytest` must pass.
|
|
202
|
+
3. `python -m pip install build twine && rm -rf dist && python -m build && twine check dist/*`
|
|
203
|
+
4. Rehearse: `twine upload --repository testpypi dist/*`.
|
|
204
|
+
5. Publish: `twine upload dist/*` with username `__token__` and the API token as the password.
|
|
205
|
+
6. Tag the commit: `git tag python-vX.Y.Z && git push origin python-vX.Y.Z`.
|
|
206
|
+
|
|
207
|
+
PyPI never lets you reuse a version number, so bump it for every fix.
|
fileboy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Fileboy for Python
|
|
2
|
+
|
|
3
|
+
The official Python client for [Fileboy](https://fileboy.io): upload files and videos, build image
|
|
4
|
+
URLs that resize on the fly, and read video data. Its only dependency is `requests`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install fileboy
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Python 3.8 or newer.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import os
|
|
18
|
+
import fileboy
|
|
19
|
+
|
|
20
|
+
client = fileboy.Client(public_key=os.environ["FILEBOY_PUBLIC_KEY"])
|
|
21
|
+
|
|
22
|
+
file = client.upload_file("report.pdf") # a path, a binary file object or bytes
|
|
23
|
+
print(file.id) # store this
|
|
24
|
+
|
|
25
|
+
client.build_file_url(file.id, download=True)
|
|
26
|
+
client.build_image_url(file.id, transforms=[{"type": "resize", "width": 300}, {"type": "format", "format": "webp"}])
|
|
27
|
+
# https://api.fileboy.io/images/get/<id>?transform=resize:300x_;format:webp
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Django and other frameworks
|
|
31
|
+
|
|
32
|
+
Configure once, then call `fileboy.client()` anywhere:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# settings.py (or an AppConfig.ready())
|
|
36
|
+
import os
|
|
37
|
+
import fileboy
|
|
38
|
+
|
|
39
|
+
fileboy.configure(
|
|
40
|
+
public_key=os.environ["FILEBOY_PUBLIC_KEY"],
|
|
41
|
+
# private_key=..., only if your app uses signed requests
|
|
42
|
+
# api_url="https://api.fileboy.io",
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# views.py
|
|
48
|
+
import fileboy
|
|
49
|
+
|
|
50
|
+
def upload(request):
|
|
51
|
+
file = fileboy.client().upload_file(request.FILES["file"], name=request.FILES["file"].name)
|
|
52
|
+
...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`FILEBOY_PUBLIC_KEY`, `FILEBOY_PRIVATE_KEY` and `FILEBOY_API_URL` are read from the environment when you do not set them.
|
|
56
|
+
|
|
57
|
+
## What you can do
|
|
58
|
+
|
|
59
|
+
| Task | Method |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Upload a path, file object or bytes | `upload_file(file, name=, mime=, metadata=)` |
|
|
62
|
+
| Upload from a path | `upload_file_from_path(path, ...)` |
|
|
63
|
+
| Upload from a URL | `upload_file_from_url(url, name=, metadata=, expect=)` |
|
|
64
|
+
| File details | `fetch_file_data(id_or_label)` |
|
|
65
|
+
| File URL | `build_file_url(id_or_label, filename=, download=)` |
|
|
66
|
+
| Image URL with transforms | `build_image_url(id_or_label, transforms=, filename=, download=)` |
|
|
67
|
+
| Download bytes | `download_file(id_or_label, range=)`, `download_image(id_or_label, transforms=, range=)` |
|
|
68
|
+
| Upload a video | `upload_video(file, name=, mime=)` |
|
|
69
|
+
| Video details | `fetch_video_data(id)`, `fetch_video_info(id)` |
|
|
70
|
+
| Manifest and poster URLs | `build_video_manifest_url(id, type="hls")`, `build_video_poster_url(id)` |
|
|
71
|
+
| Custom poster | `upload_video_poster(video_id, image)`, `download_video_poster(video_id)` |
|
|
72
|
+
| Transcripts | `upload_video_transcript(id, file, language="English")`, `fetch_video_transcript(id, transcript_id)` |
|
|
73
|
+
| Signed access token | `create_access_token(read=, write=, upload=, expires_in=)` |
|
|
74
|
+
|
|
75
|
+
An id that is a UUID is used as is. Anything else is treated as a label in your app, and a value
|
|
76
|
+
that already contains a `+` is passed through. Labels are created in the dashboard.
|
|
77
|
+
|
|
78
|
+
Uploads return a `FileData` dataclass (`id`, `name`, `original_filename`, `mime`, `metadata`,
|
|
79
|
+
`file_size`, `content_hash`, `signed`, `dies_at`, `created_at`). `upload_video` returns a `Video`
|
|
80
|
+
(`id`, `name`, `raw_file_id`). `range` is a `(first_byte, last_byte)` tuple.
|
|
81
|
+
|
|
82
|
+
### Image transforms
|
|
83
|
+
|
|
84
|
+
`transforms=` takes a list of dicts applied in order. The image service supports `resize`,
|
|
85
|
+
`format`, `quality`, `background` and `invert`:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
[
|
|
89
|
+
{"type": "resize", "width": 200, "height": 200, "fit": "cover"}, # fit: contain cover fill inside outside
|
|
90
|
+
{"type": "resize", "width": 400, "fit": "contain", "position": "top-left", "background": "#ffffff"},
|
|
91
|
+
{"type": "format", "format": "webp"}, # jpeg png webp tiff bmp
|
|
92
|
+
{"type": "quality", "value": 75},
|
|
93
|
+
{"type": "background", "color": "#fff"},
|
|
94
|
+
{"type": "invert"},
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You can also pass a dict of `name: value`, such as `{"resize": "300x_", "format": "webp", "quality": 75}`, or a ready-made string.
|
|
99
|
+
|
|
100
|
+
## Errors
|
|
101
|
+
|
|
102
|
+
Every error is a `FileboyError` with `status`, `code` (the API's error code) and `body`.
|
|
103
|
+
|
|
104
|
+
| Class | When |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `AuthenticationError` | 401 |
|
|
107
|
+
| `FileboyPermissionError` | 403, including plan limits |
|
|
108
|
+
| `NotFoundError` | 404 |
|
|
109
|
+
| `RateLimitError` | 429 |
|
|
110
|
+
| `BadRequestError` | other 4xx, such as a file that is too large |
|
|
111
|
+
| `ServerError` | 5xx |
|
|
112
|
+
| `FileboyConnectionError` | timeouts and network failures |
|
|
113
|
+
| `InvalidArgumentError`, `ConfigurationError` | bad arguments, before any request |
|
|
114
|
+
|
|
115
|
+
`FileboyPermissionError` and `FileboyConnectionError` are prefixed so they do not shadow the
|
|
116
|
+
Python built-ins `PermissionError` and `ConnectionError`.
|
|
117
|
+
|
|
118
|
+
## Notes
|
|
119
|
+
|
|
120
|
+
* File, image and video reads need no authorization, so URL builders never sign anything.
|
|
121
|
+
* The shared `demo` key only accepts small images and cannot upload video.
|
|
122
|
+
* There is no endpoint yet to update, delete or list files, so the client has no such methods.
|
|
123
|
+
* The client uses a `requests.Session`, so connections are reused between calls.
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install -e ".[dev]"
|
|
129
|
+
pytest
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The tests run against a local fake server. The transform builder is also checked against
|
|
133
|
+
`packages/sdk-fixtures/transforms.json`, the same cases the Go image parser is tested with.
|
|
134
|
+
|
|
135
|
+
MIT licensed.
|
|
136
|
+
|
|
137
|
+
## Webhooks
|
|
138
|
+
|
|
139
|
+
Verify the requests Fileboy sends to your endpoints. Use the raw body, exactly as received:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# Django
|
|
143
|
+
import fileboy
|
|
144
|
+
from django.http import HttpResponse, HttpResponseBadRequest
|
|
145
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
146
|
+
|
|
147
|
+
@csrf_exempt
|
|
148
|
+
def fileboy_webhook(request):
|
|
149
|
+
try:
|
|
150
|
+
event = fileboy.verify_webhook(request.body, request.headers, settings.FILEBOY_WEBHOOK_SECRET)
|
|
151
|
+
except fileboy.WebhookVerificationError as error:
|
|
152
|
+
return HttpResponseBadRequest(error.code) # "invalid_signature", "timestamp_out_of_tolerance", ...
|
|
153
|
+
if event["type"] == "video.ready":
|
|
154
|
+
... # event["resource"]["urls"]["hls"], event["resource"]["duration"]
|
|
155
|
+
return HttpResponse(status=200)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`verify_webhook` returns the parsed event (a dict) or raises `WebhookVerificationError`. The timestamp
|
|
159
|
+
must be within 5 minutes (`tolerance_seconds=` changes that). Events carry a unique `id` that is the same
|
|
160
|
+
on every retry, so use it to ignore duplicates. `fileboy.sign_webhook(body, timestamp, secret)` builds a
|
|
161
|
+
signature for your own tests.
|
|
162
|
+
|
|
163
|
+
## Publishing (maintainers)
|
|
164
|
+
|
|
165
|
+
You need a [PyPI](https://pypi.org) account that owns `fileboy` (the first upload claims the name) and an API token (account settings, scope: that project once it exists). Try [TestPyPI](https://test.pypi.org) first.
|
|
166
|
+
|
|
167
|
+
1. Bump the version in `src/fileboy/_version.py` and `pyproject.toml` (keep them equal) and add a section to `CHANGELOG.md`.
|
|
168
|
+
2. `pip install -e '.[dev]' && pytest` must pass.
|
|
169
|
+
3. `python -m pip install build twine && rm -rf dist && python -m build && twine check dist/*`
|
|
170
|
+
4. Rehearse: `twine upload --repository testpypi dist/*`.
|
|
171
|
+
5. Publish: `twine upload dist/*` with username `__token__` and the API token as the password.
|
|
172
|
+
6. Tag the commit: `git tag python-vX.Y.Z && git push origin python-vX.Y.Z`.
|
|
173
|
+
|
|
174
|
+
PyPI never lets you reuse a version number, so bump it for every fix.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fileboy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python client for Fileboy: upload files and videos, build image transform URLs, read video data."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [{ name = "Fileboy" }]
|
|
13
|
+
keywords = ["fileboy", "file upload", "image resize", "video hosting", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = ["requests>=2.25"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=7", "requests>=2.25"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://fileboy.io/docs/libraries/python"
|
|
37
|
+
Documentation = "https://fileboy.io/docs/libraries/python"
|
|
38
|
+
Changelog = "https://github.com/CD2/Fileboy/blob/master/packages/python/CHANGELOG.md"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
fileboy = ["py.typed"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
fileboy-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Official Python client for Fileboy (https://fileboy.io).
|
|
2
|
+
|
|
3
|
+
import fileboy
|
|
4
|
+
|
|
5
|
+
fileboy.configure(public_key="YOUR_PUBLIC_KEY")
|
|
6
|
+
fileboy.client().upload_file("report.pdf")
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from . import mime, token, transforms
|
|
14
|
+
from ._version import __version__
|
|
15
|
+
from .client import DEFAULT_API_URL, Client, Uploadable
|
|
16
|
+
from .errors import (
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
BadRequestError,
|
|
19
|
+
ConfigurationError,
|
|
20
|
+
FileboyConnectionError,
|
|
21
|
+
FileboyError,
|
|
22
|
+
FileboyPermissionError,
|
|
23
|
+
InvalidArgumentError,
|
|
24
|
+
NotFoundError,
|
|
25
|
+
RateLimitError,
|
|
26
|
+
ServerError,
|
|
27
|
+
)
|
|
28
|
+
from .file_data import FileData, Video
|
|
29
|
+
from .webhooks import WebhookVerificationError, sign_webhook, verify_webhook
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Configuration:
|
|
34
|
+
"""Settings for the shared client. Unset values fall back to environment variables."""
|
|
35
|
+
|
|
36
|
+
public_key: Optional[str] = field(default_factory=lambda: os.environ.get("FILEBOY_PUBLIC_KEY"))
|
|
37
|
+
private_key: Optional[str] = field(default_factory=lambda: os.environ.get("FILEBOY_PRIVATE_KEY"))
|
|
38
|
+
api_url: str = field(default_factory=lambda: os.environ.get("FILEBOY_API_URL") or DEFAULT_API_URL)
|
|
39
|
+
timeout: float = 60
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_configuration: Optional[Configuration] = None
|
|
43
|
+
_client: Optional[Client] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def configuration() -> Configuration:
|
|
47
|
+
"""The current settings. Created from the environment on first use."""
|
|
48
|
+
global _configuration
|
|
49
|
+
if _configuration is None:
|
|
50
|
+
_configuration = Configuration()
|
|
51
|
+
return _configuration
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def configure(**settings: Any) -> Configuration:
|
|
55
|
+
"""Set ``public_key``, ``private_key``, ``api_url`` and/or ``timeout`` for :func:`client`."""
|
|
56
|
+
global _client
|
|
57
|
+
config = configuration()
|
|
58
|
+
for key, value in settings.items():
|
|
59
|
+
if not hasattr(config, key):
|
|
60
|
+
raise ConfigurationError(f"Unknown setting {key!r}")
|
|
61
|
+
setattr(config, key, value)
|
|
62
|
+
_client = None
|
|
63
|
+
return config
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def client() -> Client:
|
|
67
|
+
"""A client built from :func:`configuration`. Memoized, and rebuilt after :func:`configure`."""
|
|
68
|
+
global _client
|
|
69
|
+
if _client is None:
|
|
70
|
+
config = configuration()
|
|
71
|
+
_client = Client(
|
|
72
|
+
public_key=config.public_key or "",
|
|
73
|
+
private_key=config.private_key,
|
|
74
|
+
api_url=config.api_url,
|
|
75
|
+
timeout=config.timeout,
|
|
76
|
+
)
|
|
77
|
+
return _client
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def reset() -> None:
|
|
81
|
+
"""Forget all settings and the shared client. Mostly useful in tests."""
|
|
82
|
+
global _configuration, _client
|
|
83
|
+
_configuration = None
|
|
84
|
+
_client = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"__version__", "Client", "Configuration", "configuration", "configure", "client", "reset",
|
|
89
|
+
"FileData", "Video", "Uploadable", "DEFAULT_API_URL", "mime", "token", "transforms",
|
|
90
|
+
"FileboyError", "ConfigurationError", "InvalidArgumentError", "FileboyConnectionError",
|
|
91
|
+
"BadRequestError", "AuthenticationError", "FileboyPermissionError", "NotFoundError",
|
|
92
|
+
"RateLimitError", "ServerError", "WebhookVerificationError", "sign_webhook", "verify_webhook",
|
|
93
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|