python-blossom 1.0.2__tar.gz → 1.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.
- {python_blossom-1.0.2/src/python_blossom.egg-info → python_blossom-1.1.0}/PKG-INFO +61 -12
- {python_blossom-1.0.2 → python_blossom-1.1.0}/README.md +58 -10
- {python_blossom-1.0.2 → python_blossom-1.1.0}/pyproject.toml +5 -2
- {python_blossom-1.0.2 → python_blossom-1.1.0}/requirements-dev.txt +1 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/requirements.txt +1 -1
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom/__init__.py +4 -1
- python_blossom-1.1.0/src/python_blossom/_base.py +666 -0
- python_blossom-1.1.0/src/python_blossom/async_client.py +204 -0
- python_blossom-1.1.0/src/python_blossom/client.py +265 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0/src/python_blossom.egg-info}/PKG-INFO +61 -12
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/SOURCES.txt +3 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/requires.txt +2 -1
- python_blossom-1.1.0/tests/test_async_client.py +29 -0
- python_blossom-1.0.2/src/python_blossom/client.py +0 -620
- {python_blossom-1.0.2 → python_blossom-1.1.0}/MANIFEST.in +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/setup.cfg +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom/errors.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/dependency_links.txt +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/top_level.txt +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_delete_blob.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_fetch_server_list.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_generate_server_list_event.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_get_blob.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_head_media.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_head_upload.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_integrity.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_list_blobs.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_media_upload.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_mirror_blob.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_publish_server_list_event.py +0 -0
- {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_upload_blob.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-blossom
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: High-level Python client for the Blossom protocol (blob storage via Nostr auth)
|
|
5
5
|
Author: Jxck-S
|
|
6
6
|
License: MIT
|
|
@@ -16,11 +16,12 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
17
|
Requires-Python: >=3.11
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
20
20
|
Requires-Dist: pynostr[websocket-client]>=0.3.0
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
25
|
Requires-Dist: black>=23.0; extra == "dev"
|
|
25
26
|
Requires-Dist: isort>=5.12; extra == "dev"
|
|
26
27
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
@@ -43,25 +44,22 @@ Implements core BUDs:
|
|
|
43
44
|
|
|
44
45
|
## Installation
|
|
45
46
|
|
|
46
|
-
Install from PyPI
|
|
47
|
+
Install from PyPI:
|
|
47
48
|
```bash
|
|
48
49
|
pip install python-blossom
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
Or install
|
|
52
|
+
Or install from GitHub:
|
|
52
53
|
```bash
|
|
53
|
-
|
|
54
|
+
pip install git+https://github.com/Jxck-S/python-blossom.git
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
pipenv install --dev
|
|
59
|
-
```
|
|
57
|
+
|
|
60
58
|
|
|
61
59
|
## Quick Start
|
|
62
60
|
|
|
63
61
|
```python
|
|
64
|
-
from
|
|
62
|
+
from python_blossom import BlossomClient
|
|
65
63
|
|
|
66
64
|
nsec = 'nsec....' # Your Nostr private key (nsec)
|
|
67
65
|
servers = [
|
|
@@ -71,6 +69,8 @@ servers = [
|
|
|
71
69
|
]
|
|
72
70
|
|
|
73
71
|
client = BlossomClient(nsec=nsec, default_servers=servers)
|
|
72
|
+
# Optional: set a custom request timeout (default is 30 seconds)
|
|
73
|
+
# client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
|
|
74
74
|
|
|
75
75
|
# Upload blob data
|
|
76
76
|
result = client.upload_blob(servers[0], data=b'blob data', mime_type='image/png')
|
|
@@ -106,6 +106,27 @@ event_id = client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
|
106
106
|
print(f"Published: {event_id}")
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
## Async Client
|
|
110
|
+
|
|
111
|
+
`AsyncBlossomClient` has the same methods and return values as `BlossomClient`, just awaitable. Use it inside asyncio apps (bots, web servers) so uploads don't block the event loop, and to upload to many servers at once:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import asyncio
|
|
115
|
+
from python_blossom import AsyncBlossomClient
|
|
116
|
+
|
|
117
|
+
async def main():
|
|
118
|
+
async with AsyncBlossomClient(nsec=nsec, default_servers=servers) as client:
|
|
119
|
+
# Uploads to every server concurrently (file is read, hashed and signed once)
|
|
120
|
+
results = await client.upload_to_all(file_path='photo.jpg', max_concurrency=5)
|
|
121
|
+
|
|
122
|
+
blob = await client.get_blob(servers[0], results[servers[0]]['sha256'])
|
|
123
|
+
await client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
124
|
+
|
|
125
|
+
asyncio.run(main())
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Both clients keep a pooled HTTP connection. Use them as context managers (`with` / `async with`) or call `client.close()` / `await client.aclose()` when you're done. To customise the HTTP layer (proxies, retries, custom transports), pass your own `httpx.Client` / `httpx.AsyncClient` as `http_client=`.
|
|
129
|
+
|
|
109
130
|
## Publishing Media to Nostr with Redundancy
|
|
110
131
|
|
|
111
132
|
Here's how to upload a photo to multiple Blossom servers and publish it to Nostr with automatic failover:
|
|
@@ -180,6 +201,34 @@ relay_manager.close_all_relay_connections()
|
|
|
180
201
|
|
|
181
202
|
```
|
|
182
203
|
|
|
204
|
+
## Timeout Configuration
|
|
205
|
+
|
|
206
|
+
All HTTP requests use a default timeout of **30 seconds** (configurable per-client). Without a timeout, a slow or unresponsive Blossom server can cause the calling thread to hang indefinitely.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from python_blossom import BlossomClient, DEFAULT_TIMEOUT
|
|
210
|
+
|
|
211
|
+
# Use the default (30s)
|
|
212
|
+
client = BlossomClient(nsec=nsec, default_servers=servers)
|
|
213
|
+
|
|
214
|
+
# Set a shorter timeout for all requests made by this client
|
|
215
|
+
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
|
|
216
|
+
|
|
217
|
+
# Disable timeout entirely (not recommended)
|
|
218
|
+
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
|
|
219
|
+
|
|
220
|
+
# Inspect the default
|
|
221
|
+
print(DEFAULT_TIMEOUT) # 30.0
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The `timeout` value applies to every HTTP call made by the client (sync and async). To set separate connect vs. read timeouts, pass a tuple, or an `httpx.Timeout` for full control:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
On timeout, `httpx.TimeoutException` is raised. Network failures raise `httpx.TransportError`; HTTP error responses raise `BlossomError` subclasses (see below).
|
|
231
|
+
|
|
183
232
|
## API Methods
|
|
184
233
|
|
|
185
234
|
### Upload & Download
|
|
@@ -216,7 +265,7 @@ relay_manager.close_all_relay_connections()
|
|
|
216
265
|
- Returns blob descriptor with `sha256`, `url`, `size`, `type`
|
|
217
266
|
|
|
218
267
|
**`upload_to_all(data=None, file_path=None, mime_type=None, description=None, use_auth=True)`**
|
|
219
|
-
- Upload blob to all default servers
|
|
268
|
+
- Upload blob to all default servers (concurrently with `AsyncBlossomClient`, one after another with `BlossomClient`)
|
|
220
269
|
- Uses servers configured during client initialization
|
|
221
270
|
- Returns dict mapping server URLs to results/errors
|
|
222
271
|
|
|
@@ -282,7 +331,7 @@ Raises `BlossomError` or specific subclasses on errors:
|
|
|
282
331
|
- `BlossomError`: Other HTTP errors
|
|
283
332
|
|
|
284
333
|
```python
|
|
285
|
-
from
|
|
334
|
+
from python_blossom.errors import BlobNotFound, TooManyRequests
|
|
286
335
|
|
|
287
336
|
try:
|
|
288
337
|
blob = client.get_blob(server, sha256)
|
|
@@ -16,25 +16,22 @@ Implements core BUDs:
|
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
|
-
Install from PyPI
|
|
19
|
+
Install from PyPI:
|
|
20
20
|
```bash
|
|
21
21
|
pip install python-blossom
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Or install
|
|
24
|
+
Or install from GitHub:
|
|
25
25
|
```bash
|
|
26
|
-
|
|
26
|
+
pip install git+https://github.com/Jxck-S/python-blossom.git
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
pipenv install --dev
|
|
32
|
-
```
|
|
29
|
+
|
|
33
30
|
|
|
34
31
|
## Quick Start
|
|
35
32
|
|
|
36
33
|
```python
|
|
37
|
-
from
|
|
34
|
+
from python_blossom import BlossomClient
|
|
38
35
|
|
|
39
36
|
nsec = 'nsec....' # Your Nostr private key (nsec)
|
|
40
37
|
servers = [
|
|
@@ -44,6 +41,8 @@ servers = [
|
|
|
44
41
|
]
|
|
45
42
|
|
|
46
43
|
client = BlossomClient(nsec=nsec, default_servers=servers)
|
|
44
|
+
# Optional: set a custom request timeout (default is 30 seconds)
|
|
45
|
+
# client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
|
|
47
46
|
|
|
48
47
|
# Upload blob data
|
|
49
48
|
result = client.upload_blob(servers[0], data=b'blob data', mime_type='image/png')
|
|
@@ -79,6 +78,27 @@ event_id = client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
|
79
78
|
print(f"Published: {event_id}")
|
|
80
79
|
```
|
|
81
80
|
|
|
81
|
+
## Async Client
|
|
82
|
+
|
|
83
|
+
`AsyncBlossomClient` has the same methods and return values as `BlossomClient`, just awaitable. Use it inside asyncio apps (bots, web servers) so uploads don't block the event loop, and to upload to many servers at once:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
from python_blossom import AsyncBlossomClient
|
|
88
|
+
|
|
89
|
+
async def main():
|
|
90
|
+
async with AsyncBlossomClient(nsec=nsec, default_servers=servers) as client:
|
|
91
|
+
# Uploads to every server concurrently (file is read, hashed and signed once)
|
|
92
|
+
results = await client.upload_to_all(file_path='photo.jpg', max_concurrency=5)
|
|
93
|
+
|
|
94
|
+
blob = await client.get_blob(servers[0], results[servers[0]]['sha256'])
|
|
95
|
+
await client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Both clients keep a pooled HTTP connection. Use them as context managers (`with` / `async with`) or call `client.close()` / `await client.aclose()` when you're done. To customise the HTTP layer (proxies, retries, custom transports), pass your own `httpx.Client` / `httpx.AsyncClient` as `http_client=`.
|
|
101
|
+
|
|
82
102
|
## Publishing Media to Nostr with Redundancy
|
|
83
103
|
|
|
84
104
|
Here's how to upload a photo to multiple Blossom servers and publish it to Nostr with automatic failover:
|
|
@@ -153,6 +173,34 @@ relay_manager.close_all_relay_connections()
|
|
|
153
173
|
|
|
154
174
|
```
|
|
155
175
|
|
|
176
|
+
## Timeout Configuration
|
|
177
|
+
|
|
178
|
+
All HTTP requests use a default timeout of **30 seconds** (configurable per-client). Without a timeout, a slow or unresponsive Blossom server can cause the calling thread to hang indefinitely.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from python_blossom import BlossomClient, DEFAULT_TIMEOUT
|
|
182
|
+
|
|
183
|
+
# Use the default (30s)
|
|
184
|
+
client = BlossomClient(nsec=nsec, default_servers=servers)
|
|
185
|
+
|
|
186
|
+
# Set a shorter timeout for all requests made by this client
|
|
187
|
+
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
|
|
188
|
+
|
|
189
|
+
# Disable timeout entirely (not recommended)
|
|
190
|
+
client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
|
|
191
|
+
|
|
192
|
+
# Inspect the default
|
|
193
|
+
print(DEFAULT_TIMEOUT) # 30.0
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The `timeout` value applies to every HTTP call made by the client (sync and async). To set separate connect vs. read timeouts, pass a tuple, or an `httpx.Timeout` for full control:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
On timeout, `httpx.TimeoutException` is raised. Network failures raise `httpx.TransportError`; HTTP error responses raise `BlossomError` subclasses (see below).
|
|
203
|
+
|
|
156
204
|
## API Methods
|
|
157
205
|
|
|
158
206
|
### Upload & Download
|
|
@@ -189,7 +237,7 @@ relay_manager.close_all_relay_connections()
|
|
|
189
237
|
- Returns blob descriptor with `sha256`, `url`, `size`, `type`
|
|
190
238
|
|
|
191
239
|
**`upload_to_all(data=None, file_path=None, mime_type=None, description=None, use_auth=True)`**
|
|
192
|
-
- Upload blob to all default servers
|
|
240
|
+
- Upload blob to all default servers (concurrently with `AsyncBlossomClient`, one after another with `BlossomClient`)
|
|
193
241
|
- Uses servers configured during client initialization
|
|
194
242
|
- Returns dict mapping server URLs to results/errors
|
|
195
243
|
|
|
@@ -255,7 +303,7 @@ Raises `BlossomError` or specific subclasses on errors:
|
|
|
255
303
|
- `BlossomError`: Other HTTP errors
|
|
256
304
|
|
|
257
305
|
```python
|
|
258
|
-
from
|
|
306
|
+
from python_blossom.errors import BlobNotFound, TooManyRequests
|
|
259
307
|
|
|
260
308
|
try:
|
|
261
309
|
blob = client.get_blob(server, sha256)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-blossom"
|
|
7
|
-
version = "1.0
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "High-level Python client for the Blossom protocol (blob storage via Nostr auth)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
|
-
"
|
|
25
|
+
"httpx>=0.27.0,<1.0.0",
|
|
26
26
|
"pynostr[websocket-client]>=0.3.0",
|
|
27
27
|
]
|
|
28
28
|
|
|
@@ -30,6 +30,7 @@ dependencies = [
|
|
|
30
30
|
dev = [
|
|
31
31
|
"pytest>=7.0",
|
|
32
32
|
"pytest-cov>=4.0",
|
|
33
|
+
"pytest-asyncio>=0.23",
|
|
33
34
|
"black>=23.0",
|
|
34
35
|
"isort>=5.12",
|
|
35
36
|
"mypy>=1.0",
|
|
@@ -53,6 +54,8 @@ include-package-data = true
|
|
|
53
54
|
testpaths = ["tests"]
|
|
54
55
|
python_files = "test_*.py"
|
|
55
56
|
addopts = "-v --strict-markers"
|
|
57
|
+
asyncio_mode = "auto"
|
|
58
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
56
59
|
|
|
57
60
|
[tool.mypy]
|
|
58
61
|
python_version = "3.11"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from .client import BlossomClient, Blob
|
|
1
|
+
from .client import BlossomClient, Blob, DEFAULT_TIMEOUT
|
|
2
|
+
from .async_client import AsyncBlossomClient
|
|
2
3
|
from .errors import (
|
|
3
4
|
BlossomError,
|
|
4
5
|
BadRequest,
|
|
@@ -30,7 +31,9 @@ from .errors import (
|
|
|
30
31
|
|
|
31
32
|
__all__ = [
|
|
32
33
|
"BlossomClient",
|
|
34
|
+
"AsyncBlossomClient",
|
|
33
35
|
"Blob",
|
|
36
|
+
"DEFAULT_TIMEOUT",
|
|
34
37
|
"BlossomError",
|
|
35
38
|
"BadRequest",
|
|
36
39
|
"InvalidHash",
|