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.
Files changed (31) hide show
  1. {python_blossom-1.0.3/src/python_blossom.egg-info → python_blossom-1.1.0}/PKG-INFO +27 -5
  2. {python_blossom-1.0.3 → python_blossom-1.1.0}/README.md +24 -3
  3. {python_blossom-1.0.3 → python_blossom-1.1.0}/pyproject.toml +5 -2
  4. {python_blossom-1.0.3 → python_blossom-1.1.0}/requirements-dev.txt +1 -0
  5. {python_blossom-1.0.3 → python_blossom-1.1.0}/requirements.txt +1 -1
  6. {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom/__init__.py +2 -0
  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.3 → python_blossom-1.1.0/src/python_blossom.egg-info}/PKG-INFO +27 -5
  11. {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/SOURCES.txt +3 -0
  12. {python_blossom-1.0.3 → 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.3/src/python_blossom/client.py +0 -625
  15. {python_blossom-1.0.3 → python_blossom-1.1.0}/MANIFEST.in +0 -0
  16. {python_blossom-1.0.3 → python_blossom-1.1.0}/setup.cfg +0 -0
  17. {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom/errors.py +0 -0
  18. {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/dependency_links.txt +0 -0
  19. {python_blossom-1.0.3 → python_blossom-1.1.0}/src/python_blossom.egg-info/top_level.txt +0 -0
  20. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_delete_blob.py +0 -0
  21. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_fetch_server_list.py +0 -0
  22. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_generate_server_list_event.py +0 -0
  23. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_get_blob.py +0 -0
  24. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_head_media.py +0 -0
  25. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_head_upload.py +0 -0
  26. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_integrity.py +0 -0
  27. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_list_blobs.py +0 -0
  28. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_media_upload.py +0 -0
  29. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_mirror_blob.py +0 -0
  30. {python_blossom-1.0.3 → python_blossom-1.1.0}/tests/test_publish_server_list_event.py +0 -0
  31. {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
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"
@@ -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 is passed directly to `requests` and applies to both the connect and read phases of every HTTP call. To set separate connect vs. read timeouts, pass a tuple:
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, `requests.exceptions.Timeout` is raised.
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 in parallel
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 is passed directly to `requests` and applies to both the connect and read phases of every HTTP call. To set separate connect vs. read timeouts, pass a tuple:
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, `requests.exceptions.Timeout` is raised.
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 in parallel
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.3"
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
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",