bckt-sdk 0.1.0b1__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.
- bckt_sdk-0.1.0b1/.github/workflows/check.yml +32 -0
- bckt_sdk-0.1.0b1/.gitignore +14 -0
- bckt_sdk-0.1.0b1/LICENSE +21 -0
- bckt_sdk-0.1.0b1/PKG-INFO +215 -0
- bckt_sdk-0.1.0b1/README.md +185 -0
- bckt_sdk-0.1.0b1/docs/assets/bckt-sdk-header-1200x630-transparent.png +0 -0
- bckt_sdk-0.1.0b1/docs/operations.md +69 -0
- bckt_sdk-0.1.0b1/docs/reference.md +120 -0
- bckt_sdk-0.1.0b1/examples/async_upload.py +17 -0
- bckt_sdk-0.1.0b1/examples/browser-upload/README.md +11 -0
- bckt_sdk-0.1.0b1/examples/browser-upload/index.html +62 -0
- bckt_sdk-0.1.0b1/examples/browser-upload/server.py +76 -0
- bckt_sdk-0.1.0b1/examples/files.py +20 -0
- bckt_sdk-0.1.0b1/examples/logs.py +23 -0
- bckt_sdk-0.1.0b1/examples/s3.md +28 -0
- bckt_sdk-0.1.0b1/pyproject.toml +52 -0
- bckt_sdk-0.1.0b1/src/bckt/__init__.py +19 -0
- bckt_sdk-0.1.0b1/src/bckt/_async.py +346 -0
- bckt_sdk-0.1.0b1/src/bckt/_logger.py +182 -0
- bckt_sdk-0.1.0b1/src/bckt/_models.py +183 -0
- bckt_sdk-0.1.0b1/src/bckt/_sync.py +324 -0
- bckt_sdk-0.1.0b1/src/bckt/_transport.py +220 -0
- bckt_sdk-0.1.0b1/src/bckt/_validation.py +108 -0
- bckt_sdk-0.1.0b1/src/bckt/client.py +82 -0
- bckt_sdk-0.1.0b1/src/bckt/errors.py +24 -0
- bckt_sdk-0.1.0b1/src/bckt/py.typed +0 -0
- bckt_sdk-0.1.0b1/src/bckt/types.py +67 -0
- bckt_sdk-0.1.0b1/tests/test_sdk.py +497 -0
- bckt_sdk-0.1.0b1/tests/test_streaming.py +124 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Check SDK
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
check:
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest]
|
|
16
|
+
python: ['3.10', '3.14']
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
cache: pip
|
|
23
|
+
- run: python -m pip install -e ".[dev]"
|
|
24
|
+
- run: python -m ruff check .
|
|
25
|
+
- run: python -m mypy src/bckt
|
|
26
|
+
- run: python -m pytest
|
|
27
|
+
- run: python -m build
|
|
28
|
+
- run: python -m twine check dist/*
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: bckt-sdk-${{ matrix.os }}-${{ matrix.python }}
|
|
32
|
+
path: dist/*
|
bckt_sdk-0.1.0b1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Alvenin
|
|
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,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bckt-sdk
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: The official Python SDK for BCKT storage, uploads and logs.
|
|
5
|
+
Project-URL: Homepage, https://bckt.io
|
|
6
|
+
Project-URL: Repository, https://github.com/bcktio/bckt-python
|
|
7
|
+
Project-URL: Documentation, https://bckt.io/docs
|
|
8
|
+
Author-email: BCKT <hello@bckt.io>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bckt,cdn,logs,sdk,storage
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: httpx<1,>=0.28
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy<2,>=1.15; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest<9,>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff<1,>=0.12; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine<7,>=6; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<a href="https://bckt.io">
|
|
33
|
+
<img src="docs/assets/bckt-sdk-header-1200x630-transparent.png" alt="BCKT object storage, edge delivery and logs" width="100%">
|
|
34
|
+
</a>
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
# BCKT for Python
|
|
38
|
+
|
|
39
|
+
Upload the file. Keep the URL. Find the log.
|
|
40
|
+
|
|
41
|
+
The official Python SDK for BCKT. Files, direct browser uploads and structured logs, with typed sync and async clients.
|
|
42
|
+
|
|
43
|
+
Python 3.10 or newer. HTTPX handles the connections. Your API key stays on the server.
|
|
44
|
+
|
|
45
|
+
This is a beta. Local checks cover HTTP requests, streaming, queue failures and the package. It still needs a live BCKT acceptance run before calling it production-tested.
|
|
46
|
+
|
|
47
|
+
## Get started
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m pip install --pre bckt-sdk
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import os
|
|
55
|
+
from bckt import Bckt
|
|
56
|
+
|
|
57
|
+
with Bckt(os.environ["BCKT_API_KEY"]) as bckt:
|
|
58
|
+
uploaded = bckt.files.upload_file(
|
|
59
|
+
"./photo.webp",
|
|
60
|
+
folder="phone/photos",
|
|
61
|
+
private=False,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
print(uploaded["url"])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The package is installed as `bckt-sdk` and imported as `bckt`. If you are working from this repository before the first PyPI release, use `python -m pip install -e .` instead.
|
|
68
|
+
|
|
69
|
+
Create a key in [API and S3](https://bckt.io/dashboard/credentials). Give it only the scopes your application needs. Don't put it in client-side code or a shared repository.
|
|
70
|
+
|
|
71
|
+
## Don't load the whole backup into memory
|
|
72
|
+
|
|
73
|
+
`upload_file` streams from disk. `upload` also accepts bytes, a binary file or an iterator of byte chunks. Streams need their byte length up front.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
import os
|
|
78
|
+
from bckt import Bckt
|
|
79
|
+
|
|
80
|
+
backup = Path("./backup.enc")
|
|
81
|
+
|
|
82
|
+
with Bckt(os.environ["BCKT_API_KEY"]) as bckt, backup.open("rb") as source:
|
|
83
|
+
bckt.files.upload(
|
|
84
|
+
source,
|
|
85
|
+
filename=backup.name,
|
|
86
|
+
size=backup.stat().st_size,
|
|
87
|
+
folder="backups",
|
|
88
|
+
private=True,
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Encrypt sensitive backups before uploading and keep an independent copy. Streams are consumed once. The SDK does not silently restart a failed upload.
|
|
93
|
+
|
|
94
|
+
## Already using async?
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import asyncio
|
|
98
|
+
import os
|
|
99
|
+
from bckt import AsyncBckt
|
|
100
|
+
|
|
101
|
+
async def main():
|
|
102
|
+
async with AsyncBckt(os.environ["BCKT_API_KEY"]) as bckt:
|
|
103
|
+
uploaded = await bckt.files.upload_file("./photo.webp", folder="photos")
|
|
104
|
+
print(uploaded["url"])
|
|
105
|
+
|
|
106
|
+
async for file in bckt.files.iterate(folder="photos"):
|
|
107
|
+
print(file["id"], file["original_name"])
|
|
108
|
+
|
|
109
|
+
asyncio.run(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`AsyncBckt` uses native async HTTP requests. Keep one client open for the life of your worker or request handler instead of opening a connection pool per operation.
|
|
113
|
+
|
|
114
|
+
## Let the browser send the photo
|
|
115
|
+
|
|
116
|
+
Your server checks the user and issues an upload ticket. The browser sends the image straight to BCKT. Your server does not relay the file or expose its API key.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
ticket = bckt.files.create_upload_url(
|
|
120
|
+
filename="photo.webp",
|
|
121
|
+
size=image_size,
|
|
122
|
+
content_type="image/webp",
|
|
123
|
+
folder="phone/photos",
|
|
124
|
+
private=False,
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Validate the user's permission, file size and file type before issuing a ticket. Send the raw file body, not multipart form data. A ticket expires after 15 minutes. Don't log or share it.
|
|
129
|
+
|
|
130
|
+
The [browser upload example](examples/browser-upload/README.md) includes a local Python server and a working file picker. It is a development demo, not an account or session system.
|
|
131
|
+
|
|
132
|
+
## A file key is not a file ID
|
|
133
|
+
|
|
134
|
+
Uploads return `file_key` and `url`. Management methods take the UUID from `files.list()`, not the public file key or URL.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
files = bckt.files.list(folder="phone/photos", search="photo.webp")
|
|
138
|
+
|
|
139
|
+
for file in files["files"]:
|
|
140
|
+
access = bckt.files.get_access_url(file["id"])
|
|
141
|
+
print(file["original_name"], access["url"])
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use `get_access_url` for private files. The plain CDN URL returned by an upload is not a signed access link. Upload tickets currently last 15 minutes; private access links last one hour. Read the returned expiration instead of calculating it yourself.
|
|
145
|
+
|
|
146
|
+
`files.iterate()` handles pagination. `files.download()` streams bytes through a context manager, and `download_to()` refuses to overwrite an existing file. The [file example](examples/files.py) shows both operations.
|
|
147
|
+
|
|
148
|
+
## Logs you can find again
|
|
149
|
+
|
|
150
|
+
Create your stream in the dashboard or with `logs.streams.create()` before sending events.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
bckt.logs.streams.create("Payments", "payments")
|
|
154
|
+
|
|
155
|
+
bckt.logs.send("payments", {
|
|
156
|
+
"level": "info",
|
|
157
|
+
"event_type": "payment.completed",
|
|
158
|
+
"entity_id": "order_123",
|
|
159
|
+
"message": "Payment received",
|
|
160
|
+
"payload": {"amount": 29.99, "currency": "EUR"},
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`send` waits for API acceptance. `send_batch` accepts up to 1,000 events in one request. A successful response means BCKT accepted the events, not that your business transaction succeeded.
|
|
165
|
+
|
|
166
|
+
For frequent events, use a bounded queue:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
with bckt.logs.create_logger("server") as logger:
|
|
170
|
+
logger.info("Worker started", {"worker": "invoices"})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Call `close()` during application shutdown. The queue lives in memory and is lost if the process exits. Failed batches stay queued and pause delivery until you retry or discard them. Read [queue behaviour](docs/operations.md) before using it for anything important.
|
|
174
|
+
|
|
175
|
+
## Handle the failure
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from bckt import BcktError
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
bckt.files.upload_file("./report.pdf", private=True)
|
|
182
|
+
except BcktError as error:
|
|
183
|
+
print(error.code, error.status, error.request_id)
|
|
184
|
+
if error.uncertain:
|
|
185
|
+
print("Check whether it arrived before uploading again.")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Reads have bounded retries. Writes do not. A lost response after a write may mean the operation succeeded. The SDK exposes that uncertainty instead of creating a second file or log batch.
|
|
189
|
+
|
|
190
|
+
Local filesystem errors retain their native Python error types. Download stream errors are raised while consuming the context manager.
|
|
191
|
+
|
|
192
|
+
## Pick what you need
|
|
193
|
+
|
|
194
|
+
| Task | Methods |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| Upload content | `files.upload`, `files.upload_file`, `files.upload_json` |
|
|
197
|
+
| Authorize a direct upload | `files.create_upload_url` |
|
|
198
|
+
| Browse files | `files.list`, `files.iterate` |
|
|
199
|
+
| Read files | `files.get_access_url`, `files.download`, `files.download_to` |
|
|
200
|
+
| Manage files | `files.set_visibility`, `files.delete`, `files.delete_many` |
|
|
201
|
+
| Work with folders | `folders.list`, `folders.create`, `folders.delete` |
|
|
202
|
+
| Send and search logs | `logs.send`, `logs.send_batch`, `logs.query` |
|
|
203
|
+
| Work with streams | `logs.streams.list`, `logs.streams.create` |
|
|
204
|
+
| Buffer logs | `logs.create_logger` |
|
|
205
|
+
| Sign an S3 operation | `s3.create_presigned_url` |
|
|
206
|
+
|
|
207
|
+
The async client has the same resources with `await`, `async for` and `async with` where needed. API responses keep their original snake_case fields, including keys inside your own payloads. See the [method reference](docs/reference.md) for arguments, returns and permissions.
|
|
208
|
+
|
|
209
|
+
## Something broken?
|
|
210
|
+
|
|
211
|
+
Open an issue with your Python version, SDK version and a small reproduction. Include the BCKT request ID if you have one. Remove API keys, signed URLs and customer data before posting.
|
|
212
|
+
|
|
213
|
+
Account question? [hello@bckt.io](mailto:hello@bckt.io).
|
|
214
|
+
|
|
215
|
+
MIT licensed.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://bckt.io">
|
|
3
|
+
<img src="docs/assets/bckt-sdk-header-1200x630-transparent.png" alt="BCKT object storage, edge delivery and logs" width="100%">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
# BCKT for Python
|
|
8
|
+
|
|
9
|
+
Upload the file. Keep the URL. Find the log.
|
|
10
|
+
|
|
11
|
+
The official Python SDK for BCKT. Files, direct browser uploads and structured logs, with typed sync and async clients.
|
|
12
|
+
|
|
13
|
+
Python 3.10 or newer. HTTPX handles the connections. Your API key stays on the server.
|
|
14
|
+
|
|
15
|
+
This is a beta. Local checks cover HTTP requests, streaming, queue failures and the package. It still needs a live BCKT acceptance run before calling it production-tested.
|
|
16
|
+
|
|
17
|
+
## Get started
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python -m pip install --pre bckt-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import os
|
|
25
|
+
from bckt import Bckt
|
|
26
|
+
|
|
27
|
+
with Bckt(os.environ["BCKT_API_KEY"]) as bckt:
|
|
28
|
+
uploaded = bckt.files.upload_file(
|
|
29
|
+
"./photo.webp",
|
|
30
|
+
folder="phone/photos",
|
|
31
|
+
private=False,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
print(uploaded["url"])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The package is installed as `bckt-sdk` and imported as `bckt`. If you are working from this repository before the first PyPI release, use `python -m pip install -e .` instead.
|
|
38
|
+
|
|
39
|
+
Create a key in [API and S3](https://bckt.io/dashboard/credentials). Give it only the scopes your application needs. Don't put it in client-side code or a shared repository.
|
|
40
|
+
|
|
41
|
+
## Don't load the whole backup into memory
|
|
42
|
+
|
|
43
|
+
`upload_file` streams from disk. `upload` also accepts bytes, a binary file or an iterator of byte chunks. Streams need their byte length up front.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
import os
|
|
48
|
+
from bckt import Bckt
|
|
49
|
+
|
|
50
|
+
backup = Path("./backup.enc")
|
|
51
|
+
|
|
52
|
+
with Bckt(os.environ["BCKT_API_KEY"]) as bckt, backup.open("rb") as source:
|
|
53
|
+
bckt.files.upload(
|
|
54
|
+
source,
|
|
55
|
+
filename=backup.name,
|
|
56
|
+
size=backup.stat().st_size,
|
|
57
|
+
folder="backups",
|
|
58
|
+
private=True,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Encrypt sensitive backups before uploading and keep an independent copy. Streams are consumed once. The SDK does not silently restart a failed upload.
|
|
63
|
+
|
|
64
|
+
## Already using async?
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import asyncio
|
|
68
|
+
import os
|
|
69
|
+
from bckt import AsyncBckt
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
async with AsyncBckt(os.environ["BCKT_API_KEY"]) as bckt:
|
|
73
|
+
uploaded = await bckt.files.upload_file("./photo.webp", folder="photos")
|
|
74
|
+
print(uploaded["url"])
|
|
75
|
+
|
|
76
|
+
async for file in bckt.files.iterate(folder="photos"):
|
|
77
|
+
print(file["id"], file["original_name"])
|
|
78
|
+
|
|
79
|
+
asyncio.run(main())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`AsyncBckt` uses native async HTTP requests. Keep one client open for the life of your worker or request handler instead of opening a connection pool per operation.
|
|
83
|
+
|
|
84
|
+
## Let the browser send the photo
|
|
85
|
+
|
|
86
|
+
Your server checks the user and issues an upload ticket. The browser sends the image straight to BCKT. Your server does not relay the file or expose its API key.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
ticket = bckt.files.create_upload_url(
|
|
90
|
+
filename="photo.webp",
|
|
91
|
+
size=image_size,
|
|
92
|
+
content_type="image/webp",
|
|
93
|
+
folder="phone/photos",
|
|
94
|
+
private=False,
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Validate the user's permission, file size and file type before issuing a ticket. Send the raw file body, not multipart form data. A ticket expires after 15 minutes. Don't log or share it.
|
|
99
|
+
|
|
100
|
+
The [browser upload example](examples/browser-upload/README.md) includes a local Python server and a working file picker. It is a development demo, not an account or session system.
|
|
101
|
+
|
|
102
|
+
## A file key is not a file ID
|
|
103
|
+
|
|
104
|
+
Uploads return `file_key` and `url`. Management methods take the UUID from `files.list()`, not the public file key or URL.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
files = bckt.files.list(folder="phone/photos", search="photo.webp")
|
|
108
|
+
|
|
109
|
+
for file in files["files"]:
|
|
110
|
+
access = bckt.files.get_access_url(file["id"])
|
|
111
|
+
print(file["original_name"], access["url"])
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `get_access_url` for private files. The plain CDN URL returned by an upload is not a signed access link. Upload tickets currently last 15 minutes; private access links last one hour. Read the returned expiration instead of calculating it yourself.
|
|
115
|
+
|
|
116
|
+
`files.iterate()` handles pagination. `files.download()` streams bytes through a context manager, and `download_to()` refuses to overwrite an existing file. The [file example](examples/files.py) shows both operations.
|
|
117
|
+
|
|
118
|
+
## Logs you can find again
|
|
119
|
+
|
|
120
|
+
Create your stream in the dashboard or with `logs.streams.create()` before sending events.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
bckt.logs.streams.create("Payments", "payments")
|
|
124
|
+
|
|
125
|
+
bckt.logs.send("payments", {
|
|
126
|
+
"level": "info",
|
|
127
|
+
"event_type": "payment.completed",
|
|
128
|
+
"entity_id": "order_123",
|
|
129
|
+
"message": "Payment received",
|
|
130
|
+
"payload": {"amount": 29.99, "currency": "EUR"},
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`send` waits for API acceptance. `send_batch` accepts up to 1,000 events in one request. A successful response means BCKT accepted the events, not that your business transaction succeeded.
|
|
135
|
+
|
|
136
|
+
For frequent events, use a bounded queue:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
with bckt.logs.create_logger("server") as logger:
|
|
140
|
+
logger.info("Worker started", {"worker": "invoices"})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Call `close()` during application shutdown. The queue lives in memory and is lost if the process exits. Failed batches stay queued and pause delivery until you retry or discard them. Read [queue behaviour](docs/operations.md) before using it for anything important.
|
|
144
|
+
|
|
145
|
+
## Handle the failure
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from bckt import BcktError
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
bckt.files.upload_file("./report.pdf", private=True)
|
|
152
|
+
except BcktError as error:
|
|
153
|
+
print(error.code, error.status, error.request_id)
|
|
154
|
+
if error.uncertain:
|
|
155
|
+
print("Check whether it arrived before uploading again.")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Reads have bounded retries. Writes do not. A lost response after a write may mean the operation succeeded. The SDK exposes that uncertainty instead of creating a second file or log batch.
|
|
159
|
+
|
|
160
|
+
Local filesystem errors retain their native Python error types. Download stream errors are raised while consuming the context manager.
|
|
161
|
+
|
|
162
|
+
## Pick what you need
|
|
163
|
+
|
|
164
|
+
| Task | Methods |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| Upload content | `files.upload`, `files.upload_file`, `files.upload_json` |
|
|
167
|
+
| Authorize a direct upload | `files.create_upload_url` |
|
|
168
|
+
| Browse files | `files.list`, `files.iterate` |
|
|
169
|
+
| Read files | `files.get_access_url`, `files.download`, `files.download_to` |
|
|
170
|
+
| Manage files | `files.set_visibility`, `files.delete`, `files.delete_many` |
|
|
171
|
+
| Work with folders | `folders.list`, `folders.create`, `folders.delete` |
|
|
172
|
+
| Send and search logs | `logs.send`, `logs.send_batch`, `logs.query` |
|
|
173
|
+
| Work with streams | `logs.streams.list`, `logs.streams.create` |
|
|
174
|
+
| Buffer logs | `logs.create_logger` |
|
|
175
|
+
| Sign an S3 operation | `s3.create_presigned_url` |
|
|
176
|
+
|
|
177
|
+
The async client has the same resources with `await`, `async for` and `async with` where needed. API responses keep their original snake_case fields, including keys inside your own payloads. See the [method reference](docs/reference.md) for arguments, returns and permissions.
|
|
178
|
+
|
|
179
|
+
## Something broken?
|
|
180
|
+
|
|
181
|
+
Open an issue with your Python version, SDK version and a small reproduction. Include the BCKT request ID if you have one. Remove API keys, signed URLs and customer data before posting.
|
|
182
|
+
|
|
183
|
+
Account question? [hello@bckt.io](mailto:hello@bckt.io).
|
|
184
|
+
|
|
185
|
+
MIT licensed.
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Running it
|
|
2
|
+
|
|
3
|
+
Keep the client open while your application runs. Close it on shutdown. Finish any buffered log flush before closing the HTTP client.
|
|
4
|
+
|
|
5
|
+
## Keys and scopes
|
|
6
|
+
|
|
7
|
+
Use `files:read` for catalog queries and downloads, `files:write` for uploads and changes, `logs:read` for log queries and `logs:write` for ingestion. Use a server-side key. A browser receives an upload ticket, never your key.
|
|
8
|
+
|
|
9
|
+
Temporary upload and access URLs are bearer credentials. Don't put them in public logs. The SDK refuses insecure remote endpoints, upload tickets pointing to another origin and HTTP redirects. It attaches authorization only to API calls and authenticated folder creation. File transfers do not carry the key.
|
|
10
|
+
|
|
11
|
+
Environment proxy variables are deliberately ignored. An explicitly provided HTTPX transport can configure proxies, certificate verification or testing. Treat custom transports as trusted application code.
|
|
12
|
+
|
|
13
|
+
## Transfer limits
|
|
14
|
+
|
|
15
|
+
The current API accepts upload-ticket sizes up to 20 GiB, while the current upload service enforces a 5 GiB file limit. Your request can hit an edge or plan limit earlier. This SDK does not bypass those limits, implement multipart S3 uploads, or resume a partially sent body.
|
|
16
|
+
|
|
17
|
+
Uploads are raw POST bodies. Browser callers must pass the exact file described by their ticket. A successful upload response does not mean moderation has completed. Store the `file_key`, and resolve the catalog UUID when you need file management.
|
|
18
|
+
|
|
19
|
+
Do not call `upload_file` on a file another process is still writing. The SDK declares its size before transfer and checks streamed byte counts. It cannot provide an atomic filesystem snapshot. Encrypt sensitive backups before uploading and keep an independent copy.
|
|
20
|
+
|
|
21
|
+
Downloads stream through a context manager. There are no automatic retries after download bytes are delivered. A retry at that point would require your application to decide whether to restart or resume. `download_to` writes to a new destination and removes a partial result when it fails.
|
|
22
|
+
|
|
23
|
+
## Retries and cancellation
|
|
24
|
+
|
|
25
|
+
GET requests retry transport failures and HTTP 429, 502, 503 and 504, at most twice by default. Backoff is bounded by `max_retry_delay`. `Retry-After` supports seconds and HTTP dates. If that delay exceeds the configured maximum, the error is returned immediately instead of retrying too early.
|
|
26
|
+
|
|
27
|
+
Writes never retry automatically. That includes uploads, log ingestion, stream creation, folder changes, signing and deletions. A timeout, lost response, malformed successful response or server error can leave a write's result unknown. Such `BcktError` instances have `uncertain=True`.
|
|
28
|
+
|
|
29
|
+
Timeouts apply to individual HTTPX connect, read, write and pool operations. They aren't a deadline for the entire file. In async applications, use your application's cancellation/deadline mechanism if you need a total limit. Cancelling an operation stops waiting, not a write already accepted by the server. Python raises `asyncio.CancelledError` rather than wrapping it.
|
|
30
|
+
|
|
31
|
+
## Recovering a log batch
|
|
32
|
+
|
|
33
|
+
The logger takes an immutable JSON snapshot when you enqueue. It is bounded by event count and encoded bytes. It keeps unsent events when an API request fails and pauses until you make a decision.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from bckt import BcktError
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
logger.flush()
|
|
40
|
+
except BcktError as error:
|
|
41
|
+
print(error.code, logger.stats["queued"])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For a clear rejection, fix the underlying problem, then:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
logger.flush(retry_failed=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If the batch might already have arrived, inspect the stream first. When you accept the possibility of duplicates:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
logger.flush(retry_failed=True, accept_duplicates=True)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or discard only the failed batch and keep later events:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
discarded = logger.discard_failed()
|
|
60
|
+
logger.flush()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use `await` for async flushes. An async flush cancelled during ingestion marks that batch uncertain. Concurrent flush calls are serialized, so they cannot send the same queued events twice. There is no automatic timer, persistence, shutdown hook or silent retry loop. On an exceptional logger context exit, explicitly decide what to do with the remaining queue.
|
|
64
|
+
|
|
65
|
+
## Publishing
|
|
66
|
+
|
|
67
|
+
The distribution name is `bckt-sdk`; the import is `bckt`; the initial version is `0.1.0b1`. PyPI uses PEP 440 prerelease notation, so this is a beta version. The name and publishing permissions must be confirmed on PyPI before the first upload. GitHub ownership does not grant PyPI ownership.
|
|
68
|
+
|
|
69
|
+
Build with `python -m build`. The wheel is the installable package and the `.tar.gz` is the source distribution. Neither contains keys or a virtual environment. Publishing is a separate step; the CI workflow only validates and builds artifacts.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Python reference
|
|
2
|
+
|
|
3
|
+
`Bckt` is synchronous. `AsyncBckt` has the same resource methods with `await`, except `iterate` and `download`, which use `async for` and `async with`. Logger enqueue methods are synchronous in both clients. Values returned by the API retain their original snake_case field names.
|
|
4
|
+
|
|
5
|
+
## Client
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from bckt import Bckt, AsyncBckt
|
|
9
|
+
|
|
10
|
+
client = Bckt(
|
|
11
|
+
api_key="your-server-key",
|
|
12
|
+
timeout=30,
|
|
13
|
+
upload_timeout=300,
|
|
14
|
+
max_retries=2,
|
|
15
|
+
max_retry_delay=30,
|
|
16
|
+
)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Timeouts are seconds per HTTPX network operation, not a total upload deadline. `upload_timeout` also applies to downloads. `max_retries` counts retries after the first GET attempt. No mutation is retried, including access URL creation.
|
|
20
|
+
|
|
21
|
+
`base_url` defaults to `https://api.bckt.io/api/v1`. `upload_base_url` defaults to `https://upload.bckt.io`. Custom endpoints must use HTTPS, except localhost for tests. Endpoint URLs cannot contain credentials, fragments or query strings. `http_transport` accepts an HTTPX transport for testing; the SDK owns and closes it. Environment proxy settings are not picked up automatically.
|
|
22
|
+
|
|
23
|
+
Use `with Bckt(...)` or `async with AsyncBckt(...)`. Otherwise call `close()` or `await aclose()`. Closing a client does not flush separately created loggers. Flush those first. An async client belongs to one asyncio event loop.
|
|
24
|
+
|
|
25
|
+
## Files
|
|
26
|
+
|
|
27
|
+
| Method | Arguments | Result |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `upload_file` | `path`, optional `filename`, `folder`, `content_type`, `private` | Upload result |
|
|
30
|
+
| `upload` | `source`, required `filename`, optional `size`, `folder`, `content_type`, `private` | Upload result |
|
|
31
|
+
| `upload_json` | JSON value, required `filename`, optional `folder`, `private` | Upload result |
|
|
32
|
+
| `create_upload_url` | `filename`, `size`, optional `folder`, `content_type`, `private` | Upload ticket |
|
|
33
|
+
| `list` | Optional filters below | Files, folders and pagination |
|
|
34
|
+
| `iterate` | Same filters, without `page` | Lazy iterator of catalog files |
|
|
35
|
+
| `get_access_url` | File UUID | `url`, `expires_at` |
|
|
36
|
+
| `download` | File UUID | Context manager yielding byte chunks |
|
|
37
|
+
| `download_to` | File UUID, destination path | Number of bytes written |
|
|
38
|
+
| `set_visibility` | File UUID, `private` boolean | Updated file ID and visibility |
|
|
39
|
+
| `delete` | File UUID | `None` |
|
|
40
|
+
| `delete_many` | List of file UUIDs, `concurrency=4` | Ordered individual results |
|
|
41
|
+
|
|
42
|
+
Optional arguments are keyword-only. Uploads default to public. Set `private=True` for private objects. Content type is guessed from the filename unless supplied. Empty uploads are rejected by the API contract. A stream must yield exactly the declared number of bytes. The synchronous client also accepts open binary files. The async client accepts async byte iterators, not synchronous file handles.
|
|
43
|
+
|
|
44
|
+
The upload result contains `success`, `file_key`, `url`, `size`, `is_private` and `moderation_status`. It does not contain a catalog UUID. Look up `id` in the catalog for subsequent management calls. A moderation status of `pending` does not mean review has completed.
|
|
45
|
+
|
|
46
|
+
The upload ticket contains `success`, `upload_url`, `method`, `headers`, `max_bytes` and `expires_at`. Browser code should let the browser set `Content-Length`. The upload server gets the filename from the ticket. Never attach the API key to a ticket upload.
|
|
47
|
+
|
|
48
|
+
`list` and `iterate` accept:
|
|
49
|
+
|
|
50
|
+
| Filter | Default | Values |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `folder` | `""` | Folder path |
|
|
53
|
+
| `search` | `None` | Up to 200 characters |
|
|
54
|
+
| `access` | `"all"` | `all`, `public`, `private` |
|
|
55
|
+
| `type` | `"all"` | `all`, `image`, `video`, `audio`, `other` |
|
|
56
|
+
| `sort` | `"newest"` | `newest`, `oldest`, `name`, `size` |
|
|
57
|
+
| `page` | `1` | Positive integer, only for `list` |
|
|
58
|
+
| `limit` | `25` | 10 through 100 |
|
|
59
|
+
|
|
60
|
+
List responses contain `files`, `folders` and `pagination` with `page`, `per_page`, `total`, `total_pages`. Catalog files include `id`, `public_key`, `original_name`, `folder`, `mime_type`, `size_bytes`, `is_private`, `created_at`, `url`. Byte counts may be strings. Iteration does not recursively visit subfolders or prefetch the next page. It uses offset pagination, so concurrent catalog changes can shift results.
|
|
61
|
+
|
|
62
|
+
Access URLs for public files have `expires_at=null`. Private access links expire and must be treated as credentials. Download contexts always close their connection, even when you stop reading early. They do not retry an interrupted stream or follow redirects. `download_to` removes its incomplete file on failure and never overwrites an existing path.
|
|
63
|
+
|
|
64
|
+
`delete_many` accepts at most 10,000 IDs, deduplicates them in input order and uses between 1 and 16 concurrent requests. Results contain `id` and `success`; failures also contain a `BcktError` under `error`. Deletion is not atomic. Cancelling the async call cannot undo completed deletions.
|
|
65
|
+
|
|
66
|
+
## Folders
|
|
67
|
+
|
|
68
|
+
`client.folders` and `client.files.folders` refer to the same resource.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
client.folders.list()
|
|
72
|
+
client.folders.create("reports/2026")
|
|
73
|
+
client.folders.delete("reports/2026", recursive=True)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Create returns `folder`. List returns `folders` with `folder`, `file_count`, `size_bytes`. Delete returns `deleted_objects`. Folder creation uses the BCKT upload endpoint with authorization. Root paths and dot segments are rejected for mutations. Removing a folder removes its contents; there is no trash operation.
|
|
77
|
+
|
|
78
|
+
## Logs
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
client.logs.streams.list()
|
|
82
|
+
client.logs.streams.create("Orders", "orders")
|
|
83
|
+
client.logs.send("orders", {"message": "Paid", "payload": {"order": 42}})
|
|
84
|
+
client.logs.send_batch("orders", [{"message": "One"}, {"message": "Two"}])
|
|
85
|
+
client.logs.query(
|
|
86
|
+
"orders",
|
|
87
|
+
start="2026-09-19T00:00:00Z",
|
|
88
|
+
end="2026-09-19T23:59:59Z",
|
|
89
|
+
search="Paid",
|
|
90
|
+
limit=200,
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Stream slugs contain 2 to 63 lowercase letters, digits, underscores or hyphens and start with a letter or digit. Stream names contain 2 to 80 characters. Duplicate creation is an API error, not an upsert.
|
|
95
|
+
|
|
96
|
+
An event accepts `timestamp`, `level`, `event_type`, `entity_id`, `message`, `payload`. Default level is `info` and payload is `{}`. Put arbitrary data in `payload`; unknown top-level fields are rejected. Datetimes must carry a timezone. They are sent as UTC ISO timestamps. Batch size is 1 to 1,000 events, up to 9 MiB encoded JSON. Ingestion returns `accepted` and `bytes`.
|
|
97
|
+
|
|
98
|
+
Query requires `start` and `end` (ISO strings or aware Python datetimes), in order and at most 31 days apart. Optional `event_type`, `entity_id`, `search` and `limit` (1 to 1,000). Results contain `events`, `timeline` and `scanned_segments`. The query API has no pagination cursor.
|
|
99
|
+
|
|
100
|
+
## Buffered logs
|
|
101
|
+
|
|
102
|
+
`logs.create_logger(stream, batch_size=100, max_queue_size=5000, max_queue_bytes=8388608)` creates an in-memory queue. It does not start a worker or timer. Schedule `flush()` in your application if you need periodic delivery.
|
|
103
|
+
|
|
104
|
+
- `log(event)`, `debug(message, payload=None)`, `info`, `warn`, `error`: enqueue a snapshot of the event.
|
|
105
|
+
- `flush(retry_failed=False, accept_duplicates=False)`: drain the queue, preserving a failed batch.
|
|
106
|
+
- `discard_failed()`: remove only the failed batch and return how many events were discarded.
|
|
107
|
+
- `stats`: `queued`, `bytes`, `sending`, `paused`, `closed`, `last_error`.
|
|
108
|
+
- `close()`: stop accepting events and flush. A failed close preserves the queue for explicit recovery.
|
|
109
|
+
|
|
110
|
+
Enqueue methods and `discard_failed()` do not need `await` in the async logger. Flush and close do. Each queued event is limited to 1 MiB. Each outgoing logger batch stays within 8 MiB. A full queue raises `QUEUE_FULL`, with no silent drops. No disk persistence or process-exit delivery guarantee is provided.
|
|
111
|
+
|
|
112
|
+
## S3 URLs
|
|
113
|
+
|
|
114
|
+
`s3.create_presigned_url(key, method="PUT", expires_in=None)` supports `GET`, `PUT`, `HEAD`, `DELETE`. An explicit expiry is between 1 and 604,800 seconds; omitted expiry uses the API default. Responses include `method`, `url`, `headers`, `expires_at`, and may include `upload_url` for PUT. A workspace S3 credential is required.
|
|
115
|
+
|
|
116
|
+
## Errors
|
|
117
|
+
|
|
118
|
+
`BcktError` has `code`, `status`, `request_id`, `retry_after` (seconds) and `uncertain`. Values may be `None` when no HTTP response arrived. Local errors include `INVALID_ARGUMENT`, `SIZE_MISMATCH`, `INVALID_RESPONSE`, `TIMEOUT`, `NETWORK_ERROR`, `DOWNLOAD_FAILED`, `QUEUE_FULL`, `LOGGER_CLOSED`, `LOGGER_BUSY`, `UNCERTAIN_BATCH`. API error codes are preserved.
|
|
119
|
+
|
|
120
|
+
Non-JSON-serializable input raises `ValueError`. File opening and local disk failures use normal Python filesystem errors. Async cancellation remains `asyncio.CancelledError`. An uncertain write may already have succeeded. Check its state before repeating it.
|