hippius 0.2.4__tar.gz → 0.2.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.4
3
+ Version: 0.2.5
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
@@ -3,19 +3,30 @@ Hippius SDK - Python interface for Hippius blockchain storage
3
3
  """
4
4
 
5
5
  from hippius_sdk.client import HippiusClient
6
- from hippius_sdk.config import (decrypt_seed_phrase, delete_account,
7
- encrypt_seed_phrase, get_account_address,
8
- get_active_account, get_all_config,
9
- get_config_value, get_encryption_key,
10
- get_seed_phrase, initialize_from_env,
11
- list_accounts, load_config, reset_config,
12
- save_config, set_active_account,
13
- set_config_value, set_encryption_key,
14
- set_seed_phrase)
6
+ from hippius_sdk.config import (
7
+ decrypt_seed_phrase,
8
+ delete_account,
9
+ encrypt_seed_phrase,
10
+ get_account_address,
11
+ get_active_account,
12
+ get_all_config,
13
+ get_config_value,
14
+ get_encryption_key,
15
+ get_seed_phrase,
16
+ initialize_from_env,
17
+ list_accounts,
18
+ load_config,
19
+ reset_config,
20
+ save_config,
21
+ set_active_account,
22
+ set_config_value,
23
+ set_encryption_key,
24
+ set_seed_phrase,
25
+ )
15
26
  from hippius_sdk.ipfs import IPFSClient
16
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
17
28
 
18
- __version__ = "0.2.4"
29
+ __version__ = "0.2.5"
19
30
  __all__ = [
20
31
  "HippiusClient",
21
32
  "IPFSClient",
@@ -113,6 +113,16 @@ def main():
113
113
  encrypt = True if args.encrypt else (False if args.no_encrypt else None)
114
114
  decrypt = True if args.decrypt else (False if args.no_decrypt else None)
115
115
 
116
+ # For erasure-code specifically, the password may have been requested and cached already
117
+ # We need to preserve it if it was set by handle_erasure_code
118
+ if (
119
+ args.command == "erasure-code"
120
+ and hasattr(client.substrate_client, "_seed_phrase")
121
+ and client.substrate_client._seed_phrase
122
+ ):
123
+ # Password has already been handled by the command handler
124
+ pass
125
+
116
126
  # Handle commands with the helper function
117
127
  if args.command == "download":
118
128
  return run_async_handler(
@@ -151,6 +161,7 @@ def main():
151
161
  args.dir_path,
152
162
  miner_ids,
153
163
  encrypt=encrypt,
164
+ publish=not args.no_publish if hasattr(args, "no_publish") else True,
154
165
  )
155
166
 
156
167
  elif args.command == "credits":
@@ -9,6 +9,7 @@ import asyncio
9
9
  import base64
10
10
  import getpass
11
11
  import json
12
+ import math
12
13
  import os
13
14
  import tempfile
14
15
  import time
@@ -44,6 +45,12 @@ from hippius_sdk.cli_rich import (
44
45
  success,
45
46
  warning,
46
47
  )
48
+ from hippius_sdk.errors import (
49
+ HippiusAlreadyDeletedError,
50
+ HippiusFailedIPFSUnpin,
51
+ HippiusFailedSubstrateDelete,
52
+ HippiusMetadataError,
53
+ )
47
54
 
48
55
  try:
49
56
  import nacl.secret
@@ -147,6 +154,12 @@ async def handle_download(
147
154
  f"Size: [bold cyan]{result['size_bytes']:,}[/bold cyan] bytes ([bold cyan]{result['size_formatted']}[/bold cyan])",
148
155
  ]
149
156
 
157
+ # Add details about content type
158
+ if result.get("is_directory", False):
159
+ details.append("[bold green]Content type: Directory[/bold green]")
160
+ else:
161
+ details.append("[bold blue]Content type: File[/bold blue]")
162
+
150
163
  if result.get("decrypted"):
151
164
  details.append("[bold yellow]File was decrypted during download[/bold yellow]")
152
165
 
@@ -358,6 +371,7 @@ async def handle_store_dir(
358
371
  dir_path: str,
359
372
  miner_ids: Optional[List[str]] = None,
360
373
  encrypt: Optional[bool] = None,
374
+ publish: bool = True,
361
375
  ) -> int:
362
376
  """Handle the store directory command"""
363
377
  if not os.path.exists(dir_path):
@@ -417,12 +431,44 @@ async def handle_store_dir(
417
431
  updater = asyncio.create_task(update_progress())
418
432
 
419
433
  try:
434
+ # Upload info message based on publish flag
435
+ if not publish:
436
+ upload_info.append(
437
+ "[bold yellow]Publishing: Disabled (local upload only)[/bold yellow]"
438
+ )
439
+ log(
440
+ "\nUpload will be local only - not publishing to blockchain or pinning to IPFS"
441
+ )
442
+ else:
443
+ upload_info.append(
444
+ "[bold green]Publishing: Enabled (publishing to blockchain)[/bold green]"
445
+ )
446
+
447
+ # Display updated upload information panel
448
+ print_panel("\n".join(upload_info), title="Directory Upload Operation")
449
+
420
450
  # Use the store_directory method
421
451
  result = await client.ipfs_client.upload_directory(
422
452
  dir_path=dir_path,
423
453
  encrypt=encrypt,
424
454
  )
425
455
 
456
+ # Skip publishing to blockchain if publish is False
457
+ if not publish:
458
+ # Remove any blockchain-related data from result to ensure we don't try to use it
459
+ if "transaction_hash" in result:
460
+ del result["transaction_hash"]
461
+ else:
462
+ # If we want to publish, make sure files are pinned globally
463
+ for file_info in result.get("files", []):
464
+ if "cid" in file_info:
465
+ try:
466
+ await client.ipfs_client.publish_global(file_info["cid"])
467
+ except Exception as e:
468
+ warning(
469
+ f"Failed to publish file {file_info['name']} globally: {str(e)}"
470
+ )
471
+
426
472
  # Complete the progress
427
473
  progress.update(task, completed=100)
428
474
  # Cancel the updater task
@@ -461,11 +507,15 @@ async def handle_store_dir(
461
507
  ["Index", "Filename", "CID"],
462
508
  )
463
509
 
464
- # If we stored in the marketplace
465
- if "transaction_hash" in result:
510
+ # If publishing is enabled and we stored in the marketplace
511
+ if publish and "transaction_hash" in result:
466
512
  log(
467
513
  f"\nStored in marketplace. Transaction hash: [bold]{result['transaction_hash']}[/bold]"
468
514
  )
515
+ elif not publish:
516
+ log(
517
+ "\n[yellow]Files were uploaded locally only. No blockchain publication or IPFS pinning.[/yellow]"
518
+ )
469
519
 
470
520
  return 0
471
521
 
@@ -991,6 +1041,36 @@ async def handle_erasure_code(
991
1041
  )
992
1042
  return 1
993
1043
 
1044
+ # Request password early if we're going to publish to the blockchain
1045
+ if publish and client.substrate_client._seed_phrase is None:
1046
+ # First check if we have an encrypted seed phrase that will require a password
1047
+ config = load_config()
1048
+ account_name = client.substrate_client._account_name or get_active_account()
1049
+
1050
+ if account_name and account_name in config["substrate"].get("accounts", {}):
1051
+ account_data = config["substrate"]["accounts"][account_name]
1052
+ is_encoded = account_data.get("seed_phrase_encoded", False)
1053
+
1054
+ if is_encoded:
1055
+ warning("Wallet password will be required for publishing to blockchain")
1056
+ password = getpass.getpass(
1057
+ "Enter password to decrypt seed phrase: \n\n"
1058
+ )
1059
+
1060
+ # Store the password in client for later use
1061
+ client.substrate_client._seed_phrase_password = password
1062
+
1063
+ # Pre-authenticate to ensure the password is correct
1064
+ try:
1065
+ seed_phrase = decrypt_seed_phrase(password, account_name)
1066
+ if not seed_phrase:
1067
+ error("Failed to decrypt seed phrase. Incorrect password?")
1068
+ return 1
1069
+ client.substrate_client._seed_phrase = seed_phrase
1070
+ except Exception as e:
1071
+ error(f"Error decrypting seed phrase: {e}")
1072
+ return 1
1073
+
994
1074
  # Get file size
995
1075
  file_size = os.path.getsize(file_path)
996
1076
  file_name = os.path.basename(file_path)
@@ -1020,11 +1100,18 @@ async def handle_erasure_code(
1020
1100
 
1021
1101
  chunk_size = new_chunk_size
1022
1102
 
1103
+ # Calculate total number of chunks that will be created
1104
+ total_original_chunks = max(1, int(math.ceil(file_size / chunk_size)))
1105
+ total_encoded_chunks = total_original_chunks * m
1106
+ estimated_size_per_chunk = min(chunk_size, file_size / total_original_chunks)
1107
+
1023
1108
  # Create parameter information panel
1024
1109
  param_info = [
1025
1110
  f"File: [bold]{file_name}[/bold] ([bold cyan]{file_size / 1024 / 1024:.2f} MB[/bold cyan])",
1026
1111
  f"Parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold] (need {k} of {m} chunks to reconstruct)",
1027
1112
  f"Chunk size: [bold cyan]{chunk_size / 1024 / 1024:.6f} MB[/bold cyan]",
1113
+ f"Total chunks to be created: [bold yellow]{total_encoded_chunks}[/bold yellow] ({total_original_chunks} original chunks × {m} encoded chunks each)",
1114
+ f"Estimated storage required: [bold magenta]{(total_encoded_chunks * estimated_size_per_chunk) / (1024 * 1024):.2f} MB[/bold magenta]",
1028
1115
  ]
1029
1116
 
1030
1117
  # Add encryption status
@@ -1394,89 +1481,210 @@ async def handle_reconstruct(
1394
1481
 
1395
1482
 
1396
1483
  async def handle_delete(client: HippiusClient, cid: str, force: bool = False) -> int:
1397
- """Handle the delete command"""
1398
- info(f"Preparing to delete file with CID: [bold cyan]{cid}[/bold cyan]")
1484
+ """Handle the delete command for files or directories"""
1485
+ info(f"Preparing to delete content with CID: [bold cyan]{cid}[/bold cyan]")
1486
+
1487
+ # First check if this is a directory
1488
+ try:
1489
+ exists_result = await client.exists(cid)
1490
+ if not exists_result["exists"]:
1491
+ error(f"CID [bold cyan]{cid}[/bold cyan] not found on IPFS")
1492
+ return 1
1493
+ except Exception as e:
1494
+ warning(f"Error checking if CID exists: {e}")
1399
1495
 
1400
1496
  if not force:
1401
- warning("This will cancel storage and remove the file from the marketplace.")
1497
+ warning("This will cancel storage and remove the content from the marketplace.")
1402
1498
  confirm = input("Continue? (y/n): ").strip().lower()
1403
1499
  if confirm != "y":
1404
1500
  log("Deletion cancelled", style="yellow")
1405
1501
  return 0
1406
1502
 
1407
- info("Deleting file from marketplace...")
1408
- result = await client.delete_file(cid)
1409
-
1410
- if result.get("success"):
1411
- success("File successfully deleted")
1503
+ # Show spinner during deletion
1504
+ with console.status("[cyan]Deleting content...[/cyan]", spinner="dots") as status:
1505
+ result = await client.delete_file(cid)
1412
1506
 
1413
- details = []
1414
- if "transaction_hash" in result:
1415
- details.append(
1416
- f"Transaction hash: [bold]{result['transaction_hash']}[/bold]"
1417
- )
1507
+ # Display results
1508
+ is_directory = result.get("is_directory", False)
1509
+ child_files = result.get("child_files", [])
1418
1510
 
1419
- # Create an informative panel with notes
1420
- notes = [
1421
- "1. The file is now unpinned from the marketplace",
1422
- "2. The CID may still resolve temporarily until garbage collection occurs",
1423
- "3. If the file was published to the global IPFS network, it may still be",
1424
- " available through other nodes that pinned it",
1511
+ if is_directory:
1512
+ # Directory deletion
1513
+ details = [
1514
+ f"Successfully deleted directory: [bold cyan]{cid}[/bold cyan]",
1515
+ f"Child files unpinned: [bold]{len(child_files)}[/bold]",
1425
1516
  ]
1426
1517
 
1427
- if details:
1428
- print_panel("\n".join(details), title="Transaction Details")
1518
+ # If there are child files, show them in a table
1519
+ if child_files:
1520
+ table_data = []
1521
+ for i, file in enumerate(
1522
+ child_files[:10], 1
1523
+ ): # Limit to first 10 files if many
1524
+ table_data.append(
1525
+ {
1526
+ "Index": str(i),
1527
+ "Filename": file.get("name", "unknown"),
1528
+ "CID": file.get("cid", "unknown"),
1529
+ }
1530
+ )
1429
1531
 
1430
- print_panel("\n".join(notes), title="Important Notes")
1532
+ if len(child_files) > 10:
1533
+ table_data.append(
1534
+ {
1535
+ "Index": "...",
1536
+ "Filename": f"({len(child_files) - 10} more files)",
1537
+ "CID": "...",
1538
+ }
1539
+ )
1431
1540
 
1432
- return 0
1541
+ print_table(
1542
+ "Unpinned Child Files", table_data, ["Index", "Filename", "CID"]
1543
+ )
1433
1544
  else:
1434
- error(f"Failed to delete file: {result}")
1545
+ # Regular file deletion
1546
+ details = [f"Successfully deleted file: [bold cyan]{cid}[/bold cyan]"]
1547
+
1548
+ if "duration_seconds" in result.get("timing", {}):
1549
+ details.append(
1550
+ f"Deletion completed in [bold green]{result['timing']['duration_seconds']:.2f}[/bold green] seconds"
1551
+ )
1552
+
1553
+ print_panel("\n".join(details), title="Deletion Complete")
1554
+
1555
+ # Create an informative panel with notes
1556
+ notes = [
1557
+ "1. The content is now unpinned from the marketplace",
1558
+ "2. The CID may still resolve temporarily until garbage collection occurs",
1559
+ "3. If the content was published to the global IPFS network, it may still be",
1560
+ " available through other nodes that pinned it",
1561
+ ]
1562
+
1563
+ print_panel("\n".join(notes), title="Important Notes")
1564
+
1565
+ return 0
1435
1566
 
1436
1567
 
1437
1568
  async def handle_ec_delete(
1438
1569
  client: HippiusClient, metadata_cid: str, force: bool = False
1439
1570
  ) -> int:
1440
- """Handle the ec-delete command"""
1441
- info(
1442
- f"Preparing to delete erasure-coded file with metadata CID: [bold cyan]{metadata_cid}[/bold cyan]"
1443
- )
1571
+ """Handle the erasure-code delete command"""
1444
1572
 
1573
+ # Create a stylish header with the CID
1574
+ info(f"Preparing to delete erasure-coded file with metadata CID:")
1575
+ print_panel(f"[bold cyan]{metadata_cid}[/bold cyan]", title="Metadata CID")
1576
+
1577
+ # Confirm the deletion if not forced
1445
1578
  if not force:
1446
- warning("This will delete the metadata and all chunks from the marketplace.")
1447
- confirm = input("Continue? (y/n): ").strip().lower()
1579
+ warning_text = [
1580
+ "This will cancel the storage of this file on the Hippius blockchain.",
1581
+ "The file metadata will be removed from blockchain storage tracking.",
1582
+ "[dim]Note: Only the metadata CID will be canceled; contents may remain on IPFS.[/dim]",
1583
+ ]
1584
+ print_panel("\n".join(warning_text), title="Warning")
1585
+
1586
+ confirm = input("Continue with deletion? (y/n): ").strip().lower()
1448
1587
  if confirm != "y":
1449
1588
  log("Deletion cancelled", style="yellow")
1450
1589
  return 0
1451
1590
 
1452
1591
  try:
1592
+ # First, pre-authenticate the client to get any password prompts out of the way
1593
+ # This accesses the substrate client to trigger authentication
1594
+ if not client.substrate_client._keypair:
1595
+ client.substrate_client._ensure_keypair()
1596
+
1597
+ # Now we can show the spinner after any password prompts
1453
1598
  info("Deleting erasure-coded file from marketplace...")
1454
- result = await client.delete_ec_file(metadata_cid)
1455
1599
 
1456
- if result.get("success"):
1457
- success("Erasure-coded file successfully deleted")
1600
+ # Create a more detailed spinner with phases
1601
+ with console.status(
1602
+ "[cyan]Processing file metadata and chunks...[/cyan]", spinner="dots"
1603
+ ) as status:
1604
+ try:
1605
+ # Use the specialized delete method that now throws specific exceptions
1606
+ await client.delete_ec_file(metadata_cid)
1458
1607
 
1459
- # Show detailed results
1460
- details = []
1461
- chunks_deleted = result.get("chunks_deleted", 0)
1462
- details.append(f"Deleted [bold]{chunks_deleted}[/bold] chunks")
1608
+ # If we get here, deletion was successful
1609
+ deletion_success = True
1610
+ already_deleted = False
1463
1611
 
1464
- if "transaction_hash" in result:
1465
- details.append(
1466
- f"Transaction hash: [bold]{result['transaction_hash']}[/bold]"
1612
+ except HippiusAlreadyDeletedError:
1613
+ # Special case - already deleted
1614
+ deletion_success = False
1615
+ already_deleted = True
1616
+
1617
+ except HippiusFailedSubstrateDelete as e:
1618
+ # Blockchain deletion failed
1619
+ error(f"Blockchain storage cancellation failed: {e}")
1620
+ return 1
1621
+
1622
+ except HippiusFailedIPFSUnpin as e:
1623
+ # IPFS unpinning failed, but blockchain deletion succeeded
1624
+ warning(
1625
+ f"Note: Some IPFS operations failed, but blockchain storage was successfully canceled"
1626
+ )
1627
+ # Consider this a success for the user since the more important blockchain part worked
1628
+ deletion_success = True
1629
+ already_deleted = False
1630
+
1631
+ except HippiusMetadataError as e:
1632
+ # Metadata parsing failed, but we can still continue
1633
+ warning(
1634
+ f"Note: Metadata file was corrupted, but blockchain storage was successfully canceled"
1467
1635
  )
1636
+ # Consider this a success for the user since the blockchain part worked
1637
+ deletion_success = True
1638
+ already_deleted = False
1468
1639
 
1469
- print_panel("\n".join(details), title="Deletion Results")
1640
+ except Exception as e:
1641
+ # Handle any unexpected errors
1642
+ error(f"Unexpected error: {e}")
1643
+ return 1
1470
1644
 
1645
+ # Show the result
1646
+ if deletion_success:
1647
+ # Create a success panel
1648
+ success_panel = [
1649
+ "[bold green]✓[/bold green] Metadata CID canceled from blockchain storage",
1650
+ f"[dim]This file is no longer tracked for storage payments[/dim]",
1651
+ "",
1652
+ "[dim]To purge file data completely:[/dim]",
1653
+ "• Individual chunks may still exist on IPFS and nodes",
1654
+ "• For complete deletion, all chunks should be unpinned manually",
1655
+ ]
1656
+ print_panel(
1657
+ "\n".join(success_panel), title="Storage Cancellation Successful"
1658
+ )
1659
+ return 0
1660
+ elif already_deleted:
1661
+ # Create a panel for the already deleted case
1662
+ already_panel = [
1663
+ "[bold yellow]![/bold yellow] This file has already been deleted from storage",
1664
+ "[dim]The CID was not found in the blockchain storage registry[/dim]",
1665
+ "",
1666
+ "This is expected if:",
1667
+ "• You previously deleted this file",
1668
+ "• The file was deleted by another process",
1669
+ "• The file was never stored in the first place",
1670
+ ]
1671
+ print_panel("\n".join(already_panel), title="Already Deleted")
1672
+ # Return 0 since this is not an error condition
1471
1673
  return 0
1472
1674
  else:
1473
- error(
1474
- f"Failed to delete erasure-coded file: {result.get('message', 'Unknown error')}"
1475
- )
1675
+ # Create an error panel for all other failures
1676
+ error_panel = [
1677
+ "[bold red]×[/bold red] File not found in blockchain storage",
1678
+ "[dim]The metadata CID was not found in the blockchain storage registry[/dim]",
1679
+ "",
1680
+ "Possible reasons:",
1681
+ "• The CID may be incorrect",
1682
+ "• You may not be the owner of this file",
1683
+ ]
1684
+ print_panel("\n".join(error_panel), title="Storage Cancellation Failed")
1476
1685
  return 1
1477
-
1478
1686
  except Exception as e:
1479
- error(f"Failed to delete erasure-coded file: {e}")
1687
+ error(f"Error deleting erasure-coded file: {e}")
1480
1688
  return 1
1481
1689
 
1482
1690
 
@@ -197,12 +197,32 @@ def add_storage_commands(subparsers):
197
197
  "store", help="Upload a file to IPFS and store it on Substrate"
198
198
  )
199
199
  store_parser.add_argument("file_path", help="Path to file to upload")
200
+ store_parser.add_argument(
201
+ "--publish",
202
+ action="store_true",
203
+ help="Publish file to IPFS and store on the blockchain (default)",
204
+ )
205
+ store_parser.add_argument(
206
+ "--no-publish",
207
+ action="store_true",
208
+ help="Don't publish file to IPFS or store on the blockchain (local only)",
209
+ )
200
210
 
201
211
  # Store directory command
202
212
  store_dir_parser = subparsers.add_parser(
203
213
  "store-dir", help="Upload a directory to IPFS and store all files on Substrate"
204
214
  )
205
215
  store_dir_parser.add_argument("dir_path", help="Path to directory to upload")
216
+ store_dir_parser.add_argument(
217
+ "--publish",
218
+ action="store_true",
219
+ help="Publish all files to IPFS and store on the blockchain (default)",
220
+ )
221
+ store_dir_parser.add_argument(
222
+ "--no-publish",
223
+ action="store_true",
224
+ help="Don't publish files to IPFS or store on the blockchain (local only)",
225
+ )
206
226
 
207
227
  # Pinning status command
208
228
  pinning_status_parser = subparsers.add_parser(
@@ -6,8 +6,14 @@ from typing import Any, Dict, List, Optional, Union
6
6
 
7
7
  from rich.console import Console
8
8
  from rich.panel import Panel
9
- from rich.progress import (BarColumn, Progress, SpinnerColumn, TextColumn,
10
- TimeElapsedColumn, TimeRemainingColumn)
9
+ from rich.progress import (
10
+ BarColumn,
11
+ Progress,
12
+ SpinnerColumn,
13
+ TextColumn,
14
+ TimeElapsedColumn,
15
+ TimeRemainingColumn,
16
+ )
11
17
  from rich.table import Table
12
18
  from rich.text import Text
13
19
 
@@ -148,10 +148,11 @@ class HippiusClient:
148
148
  ) -> Dict[str, Any]:
149
149
  """
150
150
  Download a file from IPFS with optional decryption.
151
+ Supports downloading directories - in that case, a directory structure will be created.
151
152
 
152
153
  Args:
153
154
  cid: Content Identifier (CID) of the file to download
154
- output_path: Path where the downloaded file will be saved
155
+ output_path: Path where the downloaded file/directory will be saved
155
156
  decrypt: Whether to decrypt the file (overrides default)
156
157
 
157
158
  Returns:
@@ -162,6 +163,7 @@ class HippiusClient:
162
163
  - size_formatted: Human-readable file size
163
164
  - elapsed_seconds: Time taken for the download
164
165
  - decrypted: Whether the file was decrypted
166
+ - is_directory: Whether the download was a directory
165
167
 
166
168
  Raises:
167
169
  requests.RequestException: If the download fails
@@ -436,7 +438,7 @@ class HippiusClient:
436
438
  metadata_cid: str,
437
439
  cancel_from_blockchain: bool = True,
438
440
  parallel_limit: int = 20,
439
- ) -> Dict[str, Any]:
441
+ ) -> bool:
440
442
  """
441
443
  Delete an erasure-coded file, including all its chunks in parallel.
442
444
 
@@ -446,7 +448,7 @@ class HippiusClient:
446
448
  parallel_limit: Maximum number of concurrent deletion operations
447
449
 
448
450
  Returns:
449
- Dict containing the result of the operation
451
+ True or false if failed.
450
452
 
451
453
  Raises:
452
454
  RuntimeError: If deletion fails completely
@@ -0,0 +1,77 @@
1
+ """
2
+ Custom exceptions for the Hippius SDK.
3
+ """
4
+
5
+
6
+ class HippiusError(Exception):
7
+ """Base exception for all Hippius-specific errors."""
8
+
9
+ pass
10
+
11
+
12
+ class HippiusSubstrateError(HippiusError):
13
+ """Base exception for Substrate-related errors."""
14
+
15
+ pass
16
+
17
+
18
+ class HippiusIPFSError(HippiusError):
19
+ """Base exception for IPFS-related errors."""
20
+
21
+ pass
22
+
23
+
24
+ # Specific blockchain errors
25
+ class HippiusNotFoundError(HippiusSubstrateError):
26
+ """Raised when a resource is not found on the blockchain."""
27
+
28
+ pass
29
+
30
+
31
+ class HippiusAlreadyDeletedError(HippiusSubstrateError):
32
+ """Raised when trying to delete a file that's already deleted from the blockchain."""
33
+
34
+ pass
35
+
36
+
37
+ class HippiusSubstrateConnectionError(HippiusSubstrateError):
38
+ """Raised when there's an issue connecting to the Substrate node."""
39
+
40
+ pass
41
+
42
+
43
+ class HippiusSubstrateAuthError(HippiusSubstrateError):
44
+ """Raised when there's an authentication issue with the Substrate client."""
45
+
46
+ pass
47
+
48
+
49
+ class HippiusFailedSubstrateDelete(HippiusSubstrateError):
50
+ """Raised when deletion from blockchain storage fails."""
51
+
52
+ pass
53
+
54
+
55
+ # IPFS-specific errors
56
+ class HippiusIPFSConnectionError(HippiusIPFSError):
57
+ """Raised when there's an issue connecting to IPFS."""
58
+
59
+ pass
60
+
61
+
62
+ class HippiusFailedIPFSUnpin(HippiusIPFSError):
63
+ """Raised when unpinning from IPFS fails."""
64
+
65
+ pass
66
+
67
+
68
+ class HippiusMetadataError(HippiusIPFSError):
69
+ """Raised when there's an issue with the metadata file."""
70
+
71
+ pass
72
+
73
+
74
+ class HippiusInvalidCIDError(HippiusIPFSError):
75
+ """Raised when an invalid CID is provided."""
76
+
77
+ pass