python-blossom 1.0.2__tar.gz → 1.0.3__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 (27) hide show
  1. {python_blossom-1.0.2/src/python_blossom.egg-info → python_blossom-1.0.3}/PKG-INFO +37 -10
  2. {python_blossom-1.0.2 → python_blossom-1.0.3}/README.md +36 -9
  3. {python_blossom-1.0.2 → python_blossom-1.0.3}/pyproject.toml +1 -1
  4. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom/__init__.py +2 -1
  5. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom/client.py +15 -10
  6. {python_blossom-1.0.2 → python_blossom-1.0.3/src/python_blossom.egg-info}/PKG-INFO +37 -10
  7. {python_blossom-1.0.2 → python_blossom-1.0.3}/MANIFEST.in +0 -0
  8. {python_blossom-1.0.2 → python_blossom-1.0.3}/requirements-dev.txt +0 -0
  9. {python_blossom-1.0.2 → python_blossom-1.0.3}/requirements.txt +0 -0
  10. {python_blossom-1.0.2 → python_blossom-1.0.3}/setup.cfg +0 -0
  11. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom/errors.py +0 -0
  12. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom.egg-info/SOURCES.txt +0 -0
  13. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom.egg-info/dependency_links.txt +0 -0
  14. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom.egg-info/requires.txt +0 -0
  15. {python_blossom-1.0.2 → python_blossom-1.0.3}/src/python_blossom.egg-info/top_level.txt +0 -0
  16. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_delete_blob.py +0 -0
  17. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_fetch_server_list.py +0 -0
  18. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_generate_server_list_event.py +0 -0
  19. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_get_blob.py +0 -0
  20. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_head_media.py +0 -0
  21. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_head_upload.py +0 -0
  22. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_integrity.py +0 -0
  23. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_list_blobs.py +0 -0
  24. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_media_upload.py +0 -0
  25. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_mirror_blob.py +0 -0
  26. {python_blossom-1.0.2 → python_blossom-1.0.3}/tests/test_publish_server_list_event.py +0 -0
  27. {python_blossom-1.0.2 → python_blossom-1.0.3}/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.0.3
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
@@ -43,25 +43,22 @@ Implements core BUDs:
43
43
 
44
44
  ## Installation
45
45
 
46
- Install from PyPI (coming soon):
46
+ Install from PyPI:
47
47
  ```bash
48
48
  pip install python-blossom
49
49
  ```
50
50
 
51
- Or install locally with pipenv:
51
+ Or install from GitHub:
52
52
  ```bash
53
- pipenv install
53
+ pip install git+https://github.com/Jxck-S/python-blossom.git
54
54
  ```
55
55
 
56
- For development:
57
- ```bash
58
- pipenv install --dev
59
- ```
56
+
60
57
 
61
58
  ## Quick Start
62
59
 
63
60
  ```python
64
- from blossom_python import BlossomClient
61
+ from python_blossom import BlossomClient
65
62
 
66
63
  nsec = 'nsec....' # Your Nostr private key (nsec)
67
64
  servers = [
@@ -71,6 +68,8 @@ servers = [
71
68
  ]
72
69
 
73
70
  client = BlossomClient(nsec=nsec, default_servers=servers)
71
+ # Optional: set a custom request timeout (default is 30 seconds)
72
+ # client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
74
73
 
75
74
  # Upload blob data
76
75
  result = client.upload_blob(servers[0], data=b'blob data', mime_type='image/png')
@@ -180,6 +179,34 @@ relay_manager.close_all_relay_connections()
180
179
 
181
180
  ```
182
181
 
182
+ ## Timeout Configuration
183
+
184
+ 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.
185
+
186
+ ```python
187
+ from python_blossom import BlossomClient, DEFAULT_TIMEOUT
188
+
189
+ # Use the default (30s)
190
+ client = BlossomClient(nsec=nsec, default_servers=servers)
191
+
192
+ # Set a shorter timeout for all requests made by this client
193
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
194
+
195
+ # Disable timeout entirely (not recommended)
196
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
197
+
198
+ # Inspect the default
199
+ print(DEFAULT_TIMEOUT) # 30.0
200
+ ```
201
+
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:
203
+
204
+ ```python
205
+ client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
206
+ ```
207
+
208
+ On timeout, `requests.exceptions.Timeout` is raised.
209
+
183
210
  ## API Methods
184
211
 
185
212
  ### Upload & Download
@@ -282,7 +309,7 @@ Raises `BlossomError` or specific subclasses on errors:
282
309
  - `BlossomError`: Other HTTP errors
283
310
 
284
311
  ```python
285
- from blossom_python.errors import BlobNotFound, TooManyRequests
312
+ from python_blossom.errors import BlobNotFound, TooManyRequests
286
313
 
287
314
  try:
288
315
  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')
@@ -153,6 +152,34 @@ relay_manager.close_all_relay_connections()
153
152
 
154
153
  ```
155
154
 
155
+ ## Timeout Configuration
156
+
157
+ 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.
158
+
159
+ ```python
160
+ from python_blossom import BlossomClient, DEFAULT_TIMEOUT
161
+
162
+ # Use the default (30s)
163
+ client = BlossomClient(nsec=nsec, default_servers=servers)
164
+
165
+ # Set a shorter timeout for all requests made by this client
166
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
167
+
168
+ # Disable timeout entirely (not recommended)
169
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
170
+
171
+ # Inspect the default
172
+ print(DEFAULT_TIMEOUT) # 30.0
173
+ ```
174
+
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:
176
+
177
+ ```python
178
+ client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
179
+ ```
180
+
181
+ On timeout, `requests.exceptions.Timeout` is raised.
182
+
156
183
  ## API Methods
157
184
 
158
185
  ### Upload & Download
@@ -255,7 +282,7 @@ Raises `BlossomError` or specific subclasses on errors:
255
282
  - `BlossomError`: Other HTTP errors
256
283
 
257
284
  ```python
258
- from blossom_python.errors import BlobNotFound, TooManyRequests
285
+ from python_blossom.errors import BlobNotFound, TooManyRequests
259
286
 
260
287
  try:
261
288
  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.0.3"
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"}
@@ -1,4 +1,4 @@
1
- from .client import BlossomClient, Blob
1
+ from .client import BlossomClient, Blob, DEFAULT_TIMEOUT
2
2
  from .errors import (
3
3
  BlossomError,
4
4
  BadRequest,
@@ -31,6 +31,7 @@ from .errors import (
31
31
  __all__ = [
32
32
  "BlossomClient",
33
33
  "Blob",
34
+ "DEFAULT_TIMEOUT",
34
35
  "BlossomError",
35
36
  "BadRequest",
36
37
  "InvalidHash",
@@ -16,6 +16,7 @@ from pynostr.event import Event
16
16
  AUTH_KIND = 24242 # Authorization events (BUD-01,02,04,06)
17
17
  SERVER_LIST_KIND = 10063 # BUD-03 User Server List
18
18
  DEFAULT_EXPIRATION_SECONDS = 3600
19
+ DEFAULT_TIMEOUT: float = 30.0
19
20
 
20
21
  @dataclass
21
22
  class Blob:
@@ -83,14 +84,18 @@ class BlossomClient:
83
84
  - BUD-09: PUT /report [TODO - not yet implemented]
84
85
  """
85
86
 
86
- def __init__(self, nsec: Optional[str] = None, default_servers: Optional[List[str]] = None, expiration_seconds: int = DEFAULT_EXPIRATION_SECONDS):
87
+ def __init__(self, nsec: Optional[str] = None, default_servers: Optional[List[str]] = None,
88
+ expiration_seconds: int = DEFAULT_EXPIRATION_SECONDS,
89
+ timeout: float = DEFAULT_TIMEOUT):
87
90
  """Initialize client.
88
91
 
89
92
  :param nsec: Private key in any format (nsec, hex, or other NIP-19 encodings). If omitted, only public endpoints can be used.
90
93
  :param default_servers: Ordered list of Blossom server base URLs (no trailing slash).
91
94
  :param expiration_seconds: Expiration time for auth events.
95
+ :param timeout: Timeout in seconds for all HTTP requests. Defaults to DEFAULT_TIMEOUT (30s).
92
96
  """
93
97
  self.expiration_seconds = expiration_seconds
98
+ self.timeout = timeout
94
99
  self.default_servers = default_servers or []
95
100
  self._priv: Optional[PrivateKey] = self._normalize_private_key(nsec) if nsec else None
96
101
  self.pubkey_hex: Optional[str] = self._priv.public_key.hex() if self._priv else None
@@ -267,7 +272,7 @@ class BlossomClient:
267
272
  if use_auth:
268
273
  headers.update(self._auth_header("upload", [body_hash], content=description or f"Upload {file_path or 'blob'}"))
269
274
  url = self._full_url(server, 'upload')
270
- resp = requests.put(url, headers=headers, data=data)
275
+ resp = requests.put(url, headers=headers, data=data, timeout=self.timeout)
271
276
  return self._handle_response(resp)
272
277
 
273
278
  # BUD-01: GET /<sha256>
@@ -285,7 +290,7 @@ class BlossomClient:
285
290
  headers = {}
286
291
  if use_auth:
287
292
  headers.update(self._auth_header("get", [sha256]))
288
- resp = requests.get(self._full_url(server, path), headers=headers)
293
+ resp = requests.get(self._full_url(server, path), headers=headers, timeout=self.timeout)
289
294
  content = self._handle_response(resp)
290
295
  if isinstance(content, dict):
291
296
  raise BlossomError("Expected binary blob, got JSON")
@@ -297,7 +302,7 @@ class BlossomClient:
297
302
  headers = {}
298
303
  if use_auth:
299
304
  headers.update(self._auth_header("get", [sha256]))
300
- resp = requests.head(self._full_url(server, path), headers=headers)
305
+ resp = requests.head(self._full_url(server, path), headers=headers, timeout=self.timeout)
301
306
  if resp.status_code >= 400:
302
307
  reason = resp.headers.get('X-Reason') or resp.text
303
308
  raise BlossomError(f"HTTP {resp.status_code}: {reason}")
@@ -329,7 +334,7 @@ class BlossomClient:
329
334
  if use_auth:
330
335
  headers.update(self._auth_header("list"))
331
336
  url = self._full_url(server, f"list/{target_pubkey}")
332
- resp = requests.get(url, headers=headers, params=params)
337
+ resp = requests.get(url, headers=headers, params=params, timeout=self.timeout)
333
338
  data = self._handle_response(resp)
334
339
  if isinstance(data, bytes):
335
340
  raise BlossomError("Expected JSON list, got bytes")
@@ -340,7 +345,7 @@ class BlossomClient:
340
345
  # BUD-02: DELETE /<sha256>
341
346
  def delete_blob(self, server: str, sha256: str, description: Optional[str] = None) -> Dict[str, Any]:
342
347
  headers = self._auth_header("delete", [sha256], content=description or f"Delete {sha256[:8]}")
343
- resp = requests.delete(self._full_url(server, sha256), headers=headers)
348
+ resp = requests.delete(self._full_url(server, sha256), headers=headers, timeout=self.timeout)
344
349
  data = self._handle_response(resp)
345
350
  if isinstance(data, bytes):
346
351
  # Some servers may return empty body
@@ -352,7 +357,7 @@ class BlossomClient:
352
357
  headers = self._auth_header("upload", [sha256], server_url=server, content=description or f"Mirror {sha256[:8]}")
353
358
  url = self._full_url(server, 'mirror')
354
359
  body = json.dumps({"url": source_url})
355
- resp = requests.put(url, headers=headers, data=body)
360
+ resp = requests.put(url, headers=headers, data=body, timeout=self.timeout)
356
361
  data = self._handle_response(resp)
357
362
  if isinstance(data, bytes):
358
363
  raise BlossomError("Expected JSON blob descriptor")
@@ -384,7 +389,7 @@ class BlossomClient:
384
389
  if use_auth:
385
390
  # Include the blob hash in x tags for the auth event
386
391
  headers.update(self._auth_header("upload", [sha256], content="Check upload requirements"))
387
- resp = requests.head(self._full_url(server, 'upload'), headers=headers)
392
+ resp = requests.head(self._full_url(server, 'upload'), headers=headers, timeout=self.timeout)
388
393
  if resp.status_code >= 400:
389
394
  reason = resp.headers.get('X-Reason') or resp.text
390
395
  error = get_error_from_status(resp.status_code, reason)
@@ -400,7 +405,7 @@ class BlossomClient:
400
405
  headers = {"Content-Type": mime_type}
401
406
  if use_auth:
402
407
  headers.update(self._auth_header("media", [body_hash], content=description or "Media upload"))
403
- resp = requests.put(self._full_url(server, 'media'), headers=headers, data=data)
408
+ resp = requests.put(self._full_url(server, 'media'), headers=headers, data=data, timeout=self.timeout)
404
409
  data_resp = self._handle_response(resp)
405
410
  if isinstance(data_resp, bytes):
406
411
  raise BlossomError("Expected JSON blob descriptor")
@@ -431,7 +436,7 @@ class BlossomClient:
431
436
  if use_auth:
432
437
  # Include the blob hash in x tags for the auth event
433
438
  headers.update(self._auth_header("media", [sha256], content="Check media optimization support"))
434
- resp = requests.head(self._full_url(server, 'media'), headers=headers)
439
+ resp = requests.head(self._full_url(server, 'media'), headers=headers, timeout=self.timeout)
435
440
  if resp.status_code >= 400:
436
441
  reason = resp.headers.get('X-Reason') or resp.text
437
442
  error = get_error_from_status(resp.status_code, reason)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-blossom
3
- Version: 1.0.2
3
+ Version: 1.0.3
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
@@ -43,25 +43,22 @@ Implements core BUDs:
43
43
 
44
44
  ## Installation
45
45
 
46
- Install from PyPI (coming soon):
46
+ Install from PyPI:
47
47
  ```bash
48
48
  pip install python-blossom
49
49
  ```
50
50
 
51
- Or install locally with pipenv:
51
+ Or install from GitHub:
52
52
  ```bash
53
- pipenv install
53
+ pip install git+https://github.com/Jxck-S/python-blossom.git
54
54
  ```
55
55
 
56
- For development:
57
- ```bash
58
- pipenv install --dev
59
- ```
56
+
60
57
 
61
58
  ## Quick Start
62
59
 
63
60
  ```python
64
- from blossom_python import BlossomClient
61
+ from python_blossom import BlossomClient
65
62
 
66
63
  nsec = 'nsec....' # Your Nostr private key (nsec)
67
64
  servers = [
@@ -71,6 +68,8 @@ servers = [
71
68
  ]
72
69
 
73
70
  client = BlossomClient(nsec=nsec, default_servers=servers)
71
+ # Optional: set a custom request timeout (default is 30 seconds)
72
+ # client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
74
73
 
75
74
  # Upload blob data
76
75
  result = client.upload_blob(servers[0], data=b'blob data', mime_type='image/png')
@@ -180,6 +179,34 @@ relay_manager.close_all_relay_connections()
180
179
 
181
180
  ```
182
181
 
182
+ ## Timeout Configuration
183
+
184
+ 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.
185
+
186
+ ```python
187
+ from python_blossom import BlossomClient, DEFAULT_TIMEOUT
188
+
189
+ # Use the default (30s)
190
+ client = BlossomClient(nsec=nsec, default_servers=servers)
191
+
192
+ # Set a shorter timeout for all requests made by this client
193
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=10)
194
+
195
+ # Disable timeout entirely (not recommended)
196
+ client = BlossomClient(nsec=nsec, default_servers=servers, timeout=None)
197
+
198
+ # Inspect the default
199
+ print(DEFAULT_TIMEOUT) # 30.0
200
+ ```
201
+
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:
203
+
204
+ ```python
205
+ client = BlossomClient(nsec=nsec, timeout=(5, 30)) # 5s connect, 30s read
206
+ ```
207
+
208
+ On timeout, `requests.exceptions.Timeout` is raised.
209
+
183
210
  ## API Methods
184
211
 
185
212
  ### Upload & Download
@@ -282,7 +309,7 @@ Raises `BlossomError` or specific subclasses on errors:
282
309
  - `BlossomError`: Other HTTP errors
283
310
 
284
311
  ```python
285
- from blossom_python.errors import BlobNotFound, TooManyRequests
312
+ from python_blossom.errors import BlobNotFound, TooManyRequests
286
313
 
287
314
  try:
288
315
  blob = client.get_blob(server, sha256)
File without changes