python-blossom 1.0.3__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.3/src/python_blossom.egg-info → python_blossom-1.1.0}/PKG-INFO +27 -5
- {python_blossom-1.0.3 → python_blossom-1.1.0}/README.md +24 -3
- {python_blossom-1.0.3 → python_blossom-1.1.0}/pyproject.toml +5 -2
- {python_blossom-1.0.3 → python_blossom-1.1.0}/requirements-dev.txt +1 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/requirements.txt +1 -1
- {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom/__init__.py +2 -0
- 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.3 → python_blossom-1.1.0/src/python_blossom.egg-info}/PKG-INFO +27 -5
- {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/SOURCES.txt +3 -0
- {python_blossom-1.0.3 → 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.3/src/python_blossom/client.py +0 -625
- {python_blossom-1.0.3 → python_blossom-1.1.0}/MANIFEST.in +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/setup.cfg +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom/errors.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/dependency_links.txt +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/top_level.txt +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_delete_blob.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_fetch_server_list.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_generate_server_list_event.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_get_blob.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_head_media.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_head_upload.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_integrity.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_list_blobs.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_media_upload.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_mirror_blob.py +0 -0
- {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_publish_server_list_event.py +0 -0
- {python_blossom-1.0.3 → 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"
|
|
@@ -105,6 +106,27 @@ event_id = client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
|
105
106
|
print(f"Published: {event_id}")
|
|
106
107
|
```
|
|
107
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
|
+
|
|
108
130
|
## Publishing Media to Nostr with Redundancy
|
|
109
131
|
|
|
110
132
|
Here's how to upload a photo to multiple Blossom servers and publish it to Nostr with automatic failover:
|
|
@@ -199,13 +221,13 @@ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
|
|
|
199
221
|
print(DEFAULT_TIMEOUT) # 30.0
|
|
200
222
|
```
|
|
201
223
|
|
|
202
|
-
The `timeout` value
|
|
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:
|
|
203
225
|
|
|
204
226
|
```python
|
|
205
227
|
client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
|
|
206
228
|
```
|
|
207
229
|
|
|
208
|
-
On timeout, `
|
|
230
|
+
On timeout, `httpx.TimeoutException` is raised. Network failures raise `httpx.TransportError`; HTTP error responses raise `BlossomError` subclasses (see below).
|
|
209
231
|
|
|
210
232
|
## API Methods
|
|
211
233
|
|
|
@@ -243,7 +265,7 @@ On timeout, `requests.exceptions.Timeout` is raised.
|
|
|
243
265
|
- Returns blob descriptor with `sha256`, `url`, `size`, `type`
|
|
244
266
|
|
|
245
267
|
**`upload_to_all(data=None, file_path=None, mime_type=None, description=None, use_auth=True)`**
|
|
246
|
-
- Upload blob to all default servers
|
|
268
|
+
- Upload blob to all default servers (concurrently with `AsyncBlossomClient`, one after another with `BlossomClient`)
|
|
247
269
|
- Uses servers configured during client initialization
|
|
248
270
|
- Returns dict mapping server URLs to results/errors
|
|
249
271
|
|
|
@@ -78,6 +78,27 @@ event_id = client.publish_server_list_event(relays=['wss://relay.damus.io'])
|
|
|
78
78
|
print(f"Published: {event_id}")
|
|
79
79
|
```
|
|
80
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
|
+
|
|
81
102
|
## Publishing Media to Nostr with Redundancy
|
|
82
103
|
|
|
83
104
|
Here's how to upload a photo to multiple Blossom servers and publish it to Nostr with automatic failover:
|
|
@@ -172,13 +193,13 @@ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
|
|
|
172
193
|
print(DEFAULT_TIMEOUT) # 30.0
|
|
173
194
|
```
|
|
174
195
|
|
|
175
|
-
The `timeout` value
|
|
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:
|
|
176
197
|
|
|
177
198
|
```python
|
|
178
199
|
client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
|
|
179
200
|
```
|
|
180
201
|
|
|
181
|
-
On timeout, `
|
|
202
|
+
On timeout, `httpx.TimeoutException` is raised. Network failures raise `httpx.TransportError`; HTTP error responses raise `BlossomError` subclasses (see below).
|
|
182
203
|
|
|
183
204
|
## API Methods
|
|
184
205
|
|
|
@@ -216,7 +237,7 @@ On timeout, `requests.exceptions.Timeout` is raised.
|
|
|
216
237
|
- Returns blob descriptor with `sha256`, `url`, `size`, `type`
|
|
217
238
|
|
|
218
239
|
**`upload_to_all(data=None, file_path=None, mime_type=None, description=None, use_auth=True)`**
|
|
219
|
-
- Upload blob to all default servers
|
|
240
|
+
- Upload blob to all default servers (concurrently with `AsyncBlossomClient`, one after another with `BlossomClient`)
|
|
220
241
|
- Uses servers configured during client initialization
|
|
221
242
|
- Returns dict mapping server URLs to results/errors
|
|
222
243
|
|
|
@@ -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
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,6 +31,7 @@ from .errors import (
|
|
|
30
31
|
|
|
31
32
|
__all__ = [
|
|
32
33
|
"BlossomClient",
|
|
34
|
+
"AsyncBlossomClient",
|
|
33
35
|
"Blob",
|
|
34
36
|
"DEFAULT_TIMEOUT",
|
|
35
37
|
"BlossomError",
|