hippius 0.2.38__tar.gz → 0.2.40__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 (23) hide show
  1. {hippius-0.2.38 → hippius-0.2.40}/PKG-INFO +1 -1
  2. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/__init__.py +2 -3
  3. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/client.py +6 -1
  4. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/ipfs.py +56 -52
  5. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/ipfs_core.py +9 -4
  6. {hippius-0.2.38 → hippius-0.2.40}/pyproject.toml +1 -1
  7. {hippius-0.2.38 → hippius-0.2.40}/README.md +0 -0
  8. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/cli.py +0 -0
  9. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/cli_assets.py +0 -0
  10. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/cli_handlers.py +0 -0
  11. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/cli_parser.py +0 -0
  12. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/cli_rich.py +0 -0
  13. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/config.py +0 -0
  14. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db/README.md +0 -0
  15. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db/env.db.template +0 -0
  16. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql +0 -0
  17. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql +0 -0
  18. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db/setup_database.sh +0 -0
  19. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/db_utils.py +0 -0
  20. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/errors.py +0 -0
  21. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/key_storage.py +0 -0
  22. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/substrate.py +0 -0
  23. {hippius-0.2.38 → hippius-0.2.40}/hippius_sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.38
3
+ Version: 0.2.40
4
4
  Summary: Python SDK and CLI for Hippius blockchain storage
5
5
  Home-page: https://github.com/thenervelab/hippius-sdk
6
6
  Author: Dubs
@@ -23,15 +23,14 @@ from hippius_sdk.config import (
23
23
  set_encryption_key,
24
24
  set_seed_phrase,
25
25
  )
26
- from hippius_sdk.ipfs import IPFSClient, S3PublishResult, S3PublishPin, S3DownloadResult
26
+ from hippius_sdk.ipfs import IPFSClient, S3PublishResult, S3DownloadResult
27
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
28
28
 
29
- __version__ = "0.2.38"
29
+ __version__ = "0.2.40"
30
30
  __all__ = [
31
31
  "HippiusClient",
32
32
  "IPFSClient",
33
33
  "S3PublishResult",
34
- "S3PublishPin",
35
34
  "S3DownloadResult",
36
35
  "get_config_value",
37
36
  "set_config_value",
@@ -118,7 +118,9 @@ class HippiusClient:
118
118
  """
119
119
  # Use the enhanced IPFSClient method directly with encryption parameter
120
120
  return await self.ipfs_client.upload_file(
121
- file_path, encrypt=encrypt, seed_phrase=seed_phrase
121
+ file_path,
122
+ encrypt=encrypt,
123
+ seed_phrase=seed_phrase,
122
124
  )
123
125
 
124
126
  async def upload_directory(
@@ -522,6 +524,7 @@ class HippiusClient:
522
524
  seed_phrase: str,
523
525
  subaccount_id: str,
524
526
  bucket_name: str,
527
+ file_name: str = None,
525
528
  store_node: str = "http://localhost:5001",
526
529
  pin_node: str = "https://store.hippius.network",
527
530
  substrate_url: str = "wss://rpc.hippius.network",
@@ -535,6 +538,7 @@ class HippiusClient:
535
538
  2. Pins to pin_node (remote) for persistence and backup
536
539
 
537
540
  Args:
541
+ file_name: The original file name.
538
542
  file_path: Path to the file to publish
539
543
  encrypt: Whether to encrypt the file before uploading
540
544
  seed_phrase: Seed phrase for blockchain transaction signing
@@ -565,6 +569,7 @@ class HippiusClient:
565
569
  pin_node,
566
570
  substrate_url,
567
571
  publish,
572
+ file_name=file_name,
568
573
  )
569
574
 
570
575
  async def s3_download(
@@ -1,6 +1,7 @@
1
1
  """
2
2
  IPFS operations for the Hippius SDK.
3
3
  """
4
+
4
5
  import asyncio
5
6
  import base64
6
7
  import hashlib
@@ -129,7 +130,7 @@ class IPFSClient:
129
130
 
130
131
  try:
131
132
  self.client = AsyncIPFSClient(api_url=api_url, gateway=self.gateway)
132
- except httpx.ConnectError as e:
133
+ except httpx.ConnectError:
133
134
  print(
134
135
  f"Warning: Falling back to local IPFS daemon, but still using gateway={self.gateway}"
135
136
  )
@@ -241,6 +242,7 @@ class IPFSClient:
241
242
  encrypt: Optional[bool] = None,
242
243
  max_retries: int = 3,
243
244
  seed_phrase: Optional[str] = None,
245
+ file_name: str = None,
244
246
  ) -> Dict[str, Any]:
245
247
  """
246
248
  Upload a file to IPFS with optional encryption.
@@ -251,6 +253,7 @@ class IPFSClient:
251
253
  encrypt: Whether to encrypt the file (overrides default)
252
254
  max_retries: Maximum number of retry attempts (default: 3)
253
255
  seed_phrase: Optional seed phrase to use for blockchain interactions (uses config if None)
256
+ file_name: The original file name
254
257
 
255
258
  Returns:
256
259
  Dict[str, Any]: Dictionary containing:
@@ -278,7 +281,7 @@ class IPFSClient:
278
281
  )
279
282
 
280
283
  # Get file info before upload
281
- filename = os.path.basename(file_path)
284
+ file_name = file_name if file_name else os.path.basename(file_path)
282
285
  size_bytes = os.path.getsize(file_path)
283
286
 
284
287
  # If encryption is requested, encrypt the file first
@@ -303,7 +306,10 @@ class IPFSClient:
303
306
  # Use the original file for upload
304
307
  upload_path = file_path
305
308
 
306
- result = await self.client.add_file(upload_path)
309
+ result = await self.client.add_file(
310
+ upload_path,
311
+ file_name=file_name,
312
+ )
307
313
  cid = result["Hash"]
308
314
 
309
315
  finally:
@@ -314,7 +320,7 @@ class IPFSClient:
314
320
  # Format the result
315
321
  result = {
316
322
  "cid": cid,
317
- "filename": filename,
323
+ "filename": file_name,
318
324
  "size_bytes": size_bytes,
319
325
  "encrypted": should_encrypt,
320
326
  }
@@ -1411,7 +1417,7 @@ class IPFSClient:
1411
1417
 
1412
1418
  for i, chunk in enumerate(reconstructed_chunks):
1413
1419
  if verbose and i % 10 == 0:
1414
- print(f"Processing chunk {i+1}/{len(reconstructed_chunks)}...")
1420
+ print(f"Processing chunk {i + 1}/{len(reconstructed_chunks)}...")
1415
1421
 
1416
1422
  # For all chunks except the last one, use full chunk size
1417
1423
  if i < len(reconstructed_chunks) - 1:
@@ -1750,8 +1756,9 @@ class IPFSClient:
1750
1756
  # Record the child files that were processed
1751
1757
  result["child_files"] = child_files
1752
1758
  except Exception as e:
1753
- print(f"Warning: Failed to check if CID is a directory: {e}")
1754
- # Continue with regular file unpin
1759
+ print(
1760
+ f"Warning: Failed to check if CID is a directory: {e}"
1761
+ ) # Continue with regular file unpin
1755
1762
 
1756
1763
  # Now unpin the main file/directory
1757
1764
  if unpin:
@@ -1821,7 +1828,8 @@ class IPFSClient:
1821
1828
  cancel_from_blockchain: bool = True,
1822
1829
  parallel_limit: int = 20,
1823
1830
  seed_phrase: Optional[str] = None,
1824
- metadata_timeout: int = 30, # Timeout in seconds for metadata fetch
1831
+ metadata_timeout: int = 30,
1832
+ # Timeout in seconds for metadata fetch
1825
1833
  ) -> bool:
1826
1834
  """
1827
1835
  Delete an erasure-coded file, including all its chunks in parallel.
@@ -1863,8 +1871,7 @@ class IPFSClient:
1863
1871
  except asyncio.TimeoutError:
1864
1872
  print(
1865
1873
  f"Timed out after {metadata_timeout}s waiting for metadata download"
1866
- )
1867
- # We'll continue with blockchain cancellation even without metadata
1874
+ ) # We'll continue with blockchain cancellation even without metadata
1868
1875
  except json.JSONDecodeError as e:
1869
1876
  # If we can't parse the metadata JSON, record the error but continue
1870
1877
  print(f"Error parsing metadata JSON: {e}")
@@ -1969,6 +1976,7 @@ class IPFSClient:
1969
1976
  pin_node: str = "https://store.hippius.network",
1970
1977
  substrate_url: str = "wss://rpc.hippius.network",
1971
1978
  publish: bool = True,
1979
+ file_name: str = None,
1972
1980
  ) -> Union[S3PublishResult, S3PublishPin]:
1973
1981
  """
1974
1982
  Publish a file to IPFS and the Hippius marketplace in one operation.
@@ -1993,6 +2001,7 @@ class IPFSClient:
1993
2001
  pin_node: IPFS node URL for backup pinning (default: remote service)
1994
2002
  substrate_url: the substrate url to connect to for the storage request
1995
2003
  publish: Whether to publish to blockchain (True) or just upload to IPFS (False)
2004
+ file_name: The original file name.
1996
2005
 
1997
2006
  Returns:
1998
2007
  S3PublishResult: Object containing CID, file info, and transaction hash when publish=True
@@ -2089,7 +2098,10 @@ class IPFSClient:
2089
2098
  # Step 1: Upload to store_node (local) for immediate availability
2090
2099
  try:
2091
2100
  store_client = AsyncIPFSClient(api_url=store_node)
2092
- result = await store_client.add_file(file_path)
2101
+ result = await store_client.add_file(
2102
+ file_path,
2103
+ file_name=file_name,
2104
+ )
2093
2105
  cid = result["Hash"]
2094
2106
  logger.info(f"File uploaded to store node {store_node} with CID: {cid}")
2095
2107
  except Exception as e:
@@ -2112,7 +2124,8 @@ class IPFSClient:
2112
2124
  try:
2113
2125
  # Pass the seed phrase directly to avoid password prompts for encrypted config
2114
2126
  substrate_client = SubstrateClient(
2115
- seed_phrase=seed_phrase, url=substrate_url
2127
+ seed_phrase=seed_phrase,
2128
+ url=substrate_url,
2116
2129
  )
2117
2130
  logger.info(
2118
2131
  f"Submitting storage request to substrate for file: {filename}, CID: {cid}"
@@ -2203,7 +2216,7 @@ class IPFSClient:
2203
2216
  """
2204
2217
  start_time = time.time()
2205
2218
 
2206
- # Download the file directly from the specified download_node
2219
+ # Download the file directly into memory from the specified download_node
2207
2220
  try:
2208
2221
  # Create parent directories if they don't exist
2209
2222
  os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
@@ -2211,41 +2224,31 @@ class IPFSClient:
2211
2224
  download_client = AsyncIPFSClient(api_url=download_node)
2212
2225
 
2213
2226
  download_url = f"{download_node.rstrip('/')}/api/v0/cat?arg={cid}"
2227
+
2228
+ # Download file into memory
2229
+ file_data = bytearray()
2214
2230
  async with download_client.client.stream("POST", download_url) as response:
2215
2231
  response.raise_for_status()
2232
+ async for chunk in response.aiter_bytes(chunk_size=8192):
2233
+ file_data.extend(chunk)
2216
2234
 
2217
- with open(output_path, "wb") as f:
2218
- async for chunk in response.aiter_bytes(chunk_size=8192):
2219
- f.write(chunk)
2220
-
2221
- logger.info(f"File downloaded from {download_node} with CID: {cid}")
2235
+ # Convert to bytes for consistency
2236
+ file_data = bytes(file_data)
2237
+ logger.info(
2238
+ f"File downloaded from {download_node} with CID: {cid} ({len(file_data)} bytes)"
2239
+ )
2222
2240
 
2223
2241
  except Exception as e:
2224
2242
  raise HippiusIPFSError(
2225
2243
  f"Failed to download file from {download_node}: {str(e)}"
2226
2244
  )
2227
2245
 
2228
- # Get file info after download
2229
- size_bytes = os.path.getsize(output_path)
2230
- elapsed_time = time.time() - start_time
2231
-
2232
2246
  # Attempt automatic decryption if requested
2233
2247
  decrypted = False
2234
2248
  encryption_key_used = None
2249
+ final_data = file_data # This will hold either encrypted or decrypted data
2235
2250
 
2236
2251
  if auto_decrypt:
2237
- # Check if key storage is enabled and available
2238
- try:
2239
- key_storage_available = is_key_storage_enabled()
2240
- logger.debug(f"Key storage enabled: {key_storage_available}")
2241
- except ImportError:
2242
- logger.debug("Key storage module not available")
2243
- key_storage_available = False
2244
-
2245
- # Read the downloaded file content
2246
- with open(output_path, "rb") as f:
2247
- file_data = f.read()
2248
-
2249
2252
  # Check if file is empty - this indicates a problem
2250
2253
  if len(file_data) == 0:
2251
2254
  logger.error(f"Downloaded file is empty (0 bytes) for CID: {cid}")
@@ -2263,6 +2266,14 @@ class IPFSClient:
2263
2266
  decrypted = False
2264
2267
  encryption_key_used = None
2265
2268
  else:
2269
+ # Check if key storage is enabled and available
2270
+ try:
2271
+ key_storage_available = is_key_storage_enabled()
2272
+ logger.debug(f"Key storage enabled: {key_storage_available}")
2273
+ except ImportError:
2274
+ logger.debug("Key storage module not available")
2275
+ key_storage_available = False
2276
+
2266
2277
  # File has content, attempt decryption if requested
2267
2278
  decryption_attempted = False
2268
2279
  decryption_successful = False
@@ -2292,17 +2303,10 @@ class IPFSClient:
2292
2303
  existing_key_b64
2293
2304
  )
2294
2305
  box = nacl.secret.SecretBox(encryption_key_bytes)
2295
- decrypted_data = box.decrypt(file_data)
2296
-
2297
- # Write the decrypted data back to the file
2298
- with open(output_path, "wb") as f:
2299
- f.write(decrypted_data)
2306
+ final_data = box.decrypt(file_data)
2300
2307
 
2301
2308
  decryption_successful = True
2302
2309
  decrypted = True
2303
- size_bytes = len(
2304
- decrypted_data
2305
- ) # Update size to decrypted size
2306
2310
  logger.info(
2307
2311
  "Successfully decrypted file using stored key"
2308
2312
  )
@@ -2310,8 +2314,7 @@ class IPFSClient:
2310
2314
  except Exception as decrypt_error:
2311
2315
  logger.debug(
2312
2316
  f"Decryption failed with stored key: {decrypt_error}"
2313
- )
2314
- # Continue to try fallback decryption
2317
+ ) # Continue to try fallback decryption
2315
2318
  else:
2316
2319
  logger.debug(
2317
2320
  "No encryption key found for account+bucket combination"
@@ -2326,17 +2329,10 @@ class IPFSClient:
2326
2329
  decryption_attempted = True
2327
2330
 
2328
2331
  try:
2329
- decrypted_data = self.decrypt_data(file_data)
2330
-
2331
- # Write the decrypted data back to the file
2332
- with open(output_path, "wb") as f:
2333
- f.write(decrypted_data)
2332
+ final_data = self.decrypt_data(file_data)
2334
2333
 
2335
2334
  decryption_successful = True
2336
2335
  decrypted = True
2337
- size_bytes = len(
2338
- decrypted_data
2339
- ) # Update size to decrypted size
2340
2336
 
2341
2337
  # Store the encryption key for the result
2342
2338
  encryption_key_used = (
@@ -2361,6 +2357,14 @@ class IPFSClient:
2361
2357
  elif not decryption_attempted:
2362
2358
  logger.debug("No decryption attempted - no keys available")
2363
2359
 
2360
+ # Write the final data (encrypted or decrypted) to disk once
2361
+ with open(output_path, "wb") as f:
2362
+ f.write(final_data)
2363
+
2364
+ # Get final file info
2365
+ size_bytes = len(final_data)
2366
+ elapsed_time = time.time() - start_time
2367
+
2364
2368
  return S3DownloadResult(
2365
2369
  cid=cid,
2366
2370
  output_path=output_path,
@@ -48,11 +48,16 @@ class AsyncIPFSClient:
48
48
  async def __aexit__(self, exc_type, exc_val, exc_tb):
49
49
  await self.close()
50
50
 
51
- async def add_file(self, file_path: str) -> Dict[str, Any]:
51
+ async def add_file(
52
+ self,
53
+ file_path: str,
54
+ file_name: str = None,
55
+ ) -> Dict[str, Any]:
52
56
  """
53
57
  Add a file to IPFS.
54
58
 
55
59
  Args:
60
+ file_name: Name of the file
56
61
  file_path: Path to the file to add
57
62
 
58
63
  Returns:
@@ -60,9 +65,9 @@ class AsyncIPFSClient:
60
65
  """
61
66
  with open(file_path, "rb") as f:
62
67
  file_content = f.read()
63
- filename = os.path.basename(file_path)
64
- # Specify file with name and content type to ensure consistent handling
65
- files = {"file": (filename, file_content, "application/octet-stream")}
68
+ file_name = file_name if file_name else os.path.basename(file_path)
69
+ files = {"file": (file_name, file_content, "application/octet-stream")}
70
+
66
71
  # Explicitly set wrap-with-directory=false to prevent wrapping in directory
67
72
  response = await self.client.post(
68
73
  f"{self.api_url}/api/v0/add?wrap-with-directory=false&cid-version=1",
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "hippius"
3
- version = "0.2.38"
3
+ version = "0.2.40"
4
4
  description = "Python SDK and CLI for Hippius blockchain storage"
5
5
  authors = ["Dubs <dubs@dubs.rs>"]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes