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.
Files changed (31) hide show
  1. {python_blossom-1.0.2/src/python_blossom.egg-info → python_blossom-1.1.0}/PKG-INFO +61 -12
  2. {python_blossom-1.0.2 → python_blossom-1.1.0}/README.md +58 -10
  3. {python_blossom-1.0.2 → python_blossom-1.1.0}/pyproject.toml +5 -2
  4. {python_blossom-1.0.2 → python_blossom-1.1.0}/requirements-dev.txt +1 -0
  5. {python_blossom-1.0.2 → python_blossom-1.1.0}/requirements.txt +1 -1
  6. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom/__init__.py +4 -1
  7. python_blossom-1.1.0/src/python_blossom/_base.py +666 -0
  8. python_blossom-1.1.0/src/python_blossom/async_client.py +204 -0
  9. python_blossom-1.1.0/src/python_blossom/client.py +265 -0
  10. {python_blossom-1.0.2 → python_blossom-1.1.0/src/python_blossom.egg-info}/PKG-INFO +61 -12
  11. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/SOURCES.txt +3 -0
  12. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/requires.txt +2 -1
  13. python_blossom-1.1.0/tests/test_async_client.py +29 -0
  14. python_blossom-1.0.2/src/python_blossom/client.py +0 -620
  15. {python_blossom-1.0.2 → python_blossom-1.1.0}/MANIFEST.in +0 -0
  16. {python_blossom-1.0.2 → python_blossom-1.1.0}/setup.cfg +0 -0
  17. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom/errors.py +0 -0
  18. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/dependency_links.txt +0 -0
  19. {python_blossom-1.0.2 → python_blossom-1.1.0}/src/python_blossom.egg-info/top_level.txt +0 -0
  20. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_delete_blob.py +0 -0
  21. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_fetch_server_list.py +0 -0
  22. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_generate_server_list_event.py +0 -0
  23. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_get_blob.py +0 -0
  24. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_head_media.py +0 -0
  25. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_head_upload.py +0 -0
  26. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_integrity.py +0 -0
  27. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_list_blobs.py +0 -0
  28. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_media_upload.py +0 -0
  29. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_mirror_blob.py +0 -0
  30. {python_blossom-1.0.2 → python_blossom-1.1.0}/tests/test_publish_server_list_event.py +0 -0
  31. {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.2
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: requests<3.0.0,>=2.28.0
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 (coming soon):
47
+ Install from PyPI:
47
48
  ```bash
48
49
  pip install python-blossom
49
50
  ```
50
51
 
51
- Or install locally with pipenv:
52
+ Or install from GitHub:
52
53
  ```bash
53
- pipenv install
54
+ pip install git+https://github.com/Jxck-S/python-blossom.git
54
55
  ```
55
56
 
56
- For development:
57
- ```bash
58
- pipenv install --dev
59
- ```
57
+
60
58
 
61
59
  ## Quick Start
62
60
 
63
61
  ```python
64
- from blossom_python import BlossomClient
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 in parallel
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 blossom_python.errors import BlobNotFound, TooManyRequests
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 (coming soon):
19
+ Install from PyPI:
20
20
  ```bash
21
21
  pip install python-blossom
22
22
  ```
23
23
 
24
- Or install locally with pipenv:
24
+ Or install from GitHub:
25
25
  ```bash
26
- pipenv install
26
+ pip install git+https://github.com/Jxck-S/python-blossom.git
27
27
  ```
28
28
 
29
- For development:
30
- ```bash
31
- pipenv install --dev
32
- ```
29
+
33
30
 
34
31
  ## Quick Start
35
32
 
36
33
  ```python
37
- from blossom_python import BlossomClient
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 in parallel
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 blossom_python.errors import BlobNotFound, TooManyRequests
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.2"
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
- "requests>=2.28.0,<3.0.0",
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"
@@ -4,6 +4,7 @@
4
4
  # Testing
5
5
  pytest>=7.0
6
6
  pytest-cov>=4.0
7
+ pytest-asyncio>=0.23
7
8
 
8
9
  # Code quality
9
10
  black>=23.0
@@ -1,5 +1,5 @@
1
1
  # Production dependencies
2
- requests>=2.28.0,<3.0.0
2
+ httpx>=0.27.0,<1.0.0
3
3
  pynostr[websocket-client]>=0.3.0
4
4
 
5
5
  # Note: pynostr[websocket-client] includes the websocket-client extra
@@ -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",