hippius 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- {hippius-0.2.2.dist-info → hippius-0.2.3.dist-info}/METADATA +8 -7
- hippius-0.2.3.dist-info/RECORD +16 -0
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/cli.py +277 -2628
- hippius_sdk/cli_assets.py +8 -0
- hippius_sdk/cli_handlers.py +2370 -0
- hippius_sdk/cli_parser.py +602 -0
- hippius_sdk/cli_rich.py +253 -0
- hippius_sdk/client.py +56 -8
- hippius_sdk/config.py +1 -1
- hippius_sdk/ipfs.py +372 -16
- hippius_sdk/ipfs_core.py +22 -1
- hippius_sdk/substrate.py +215 -525
- hippius_sdk/utils.py +84 -2
- hippius-0.2.2.dist-info/RECORD +0 -12
- {hippius-0.2.2.dist-info → hippius-0.2.3.dist-info}/WHEEL +0 -0
- {hippius-0.2.2.dist-info → hippius-0.2.3.dist-info}/entry_points.txt +0 -0
hippius_sdk/ipfs.py
CHANGED
@@ -10,7 +10,7 @@ import shutil
|
|
10
10
|
import tempfile
|
11
11
|
import time
|
12
12
|
import uuid
|
13
|
-
from typing import Any, Dict, List, Optional
|
13
|
+
from typing import Any, Callable, Dict, List, Optional
|
14
14
|
|
15
15
|
import httpx
|
16
16
|
import requests
|
@@ -66,7 +66,7 @@ class IPFSClient:
|
|
66
66
|
"""
|
67
67
|
# Load configuration values if not explicitly provided
|
68
68
|
if gateway is None:
|
69
|
-
gateway = get_config_value("ipfs", "gateway", "https://
|
69
|
+
gateway = get_config_value("ipfs", "gateway", "https://get.hippius.network")
|
70
70
|
|
71
71
|
if api_url is None:
|
72
72
|
api_url = get_config_value(
|
@@ -84,11 +84,12 @@ class IPFSClient:
|
|
84
84
|
self.base_url = api_url
|
85
85
|
|
86
86
|
try:
|
87
|
-
self.client = AsyncIPFSClient(api_url)
|
87
|
+
self.client = AsyncIPFSClient(api_url=api_url, gateway=self.gateway)
|
88
88
|
except httpx.ConnectError as e:
|
89
|
-
print(
|
90
|
-
|
91
|
-
|
89
|
+
print(
|
90
|
+
f"Warning: Falling back to local IPFS daemon, but still using gateway={self.gateway}"
|
91
|
+
)
|
92
|
+
self.client = AsyncIPFSClient(gateway=self.gateway)
|
92
93
|
|
93
94
|
self._initialize_encryption(encrypt_by_default, encryption_key)
|
94
95
|
|
@@ -483,8 +484,6 @@ class IPFSClient:
|
|
483
484
|
|
484
485
|
# Download the file with retry logic
|
485
486
|
retries = 0
|
486
|
-
last_error = None
|
487
|
-
|
488
487
|
while retries < max_retries:
|
489
488
|
try:
|
490
489
|
# Download the file
|
@@ -505,7 +504,6 @@ class IPFSClient:
|
|
505
504
|
|
506
505
|
except (requests.exceptions.RequestException, IOError) as e:
|
507
506
|
# Save the error and retry
|
508
|
-
last_error = e
|
509
507
|
retries += 1
|
510
508
|
|
511
509
|
if retries < max_retries:
|
@@ -742,6 +740,7 @@ class IPFSClient:
|
|
742
740
|
encrypt: Optional[bool] = None,
|
743
741
|
max_retries: int = 3,
|
744
742
|
verbose: bool = True,
|
743
|
+
progress_callback: Optional[Callable[[str, int, int], None]] = None,
|
745
744
|
) -> Dict[str, Any]:
|
746
745
|
"""
|
747
746
|
Split a file using erasure coding, then upload the chunks to IPFS.
|
@@ -759,6 +758,8 @@ class IPFSClient:
|
|
759
758
|
encrypt: Whether to encrypt the file before encoding (defaults to self.encrypt_by_default)
|
760
759
|
max_retries: Maximum number of retry attempts for IPFS uploads
|
761
760
|
verbose: Whether to print progress information
|
761
|
+
progress_callback: Optional callback function for progress updates
|
762
|
+
Function receives (stage_name, current, total)
|
762
763
|
|
763
764
|
Returns:
|
764
765
|
dict: Metadata including the original file info and chunk information
|
@@ -977,6 +978,16 @@ class IPFSClient:
|
|
977
978
|
# Create a semaphore to limit concurrent uploads
|
978
979
|
semaphore = asyncio.Semaphore(batch_size)
|
979
980
|
|
981
|
+
# Track total uploads for progress reporting
|
982
|
+
total_chunks = len(all_chunk_info)
|
983
|
+
|
984
|
+
# Initialize progress tracking if callback provided
|
985
|
+
if progress_callback:
|
986
|
+
progress_callback("upload", 0, total_chunks)
|
987
|
+
|
988
|
+
if verbose:
|
989
|
+
print(f"Uploading {total_chunks} erasure-coded chunks to IPFS...")
|
990
|
+
|
980
991
|
# Define upload task for a single chunk
|
981
992
|
async def upload_chunk(chunk_info):
|
982
993
|
nonlocal chunk_uploads
|
@@ -988,13 +999,19 @@ class IPFSClient:
|
|
988
999
|
)
|
989
1000
|
chunk_info["cid"] = chunk_cid
|
990
1001
|
chunk_uploads += 1
|
1002
|
+
|
1003
|
+
# Update progress through callback
|
1004
|
+
if progress_callback:
|
1005
|
+
progress_callback("upload", chunk_uploads, total_chunks)
|
1006
|
+
|
991
1007
|
if verbose and chunk_uploads % 10 == 0:
|
992
|
-
print(
|
993
|
-
f" Uploaded {chunk_uploads}/{len(chunks) * m} chunks"
|
994
|
-
)
|
1008
|
+
print(f" Uploaded {chunk_uploads}/{total_chunks} chunks")
|
995
1009
|
return chunk_info
|
996
1010
|
except Exception as e:
|
997
|
-
|
1011
|
+
if verbose:
|
1012
|
+
print(
|
1013
|
+
f"Error uploading chunk {chunk_info['name']}: {str(e)}"
|
1014
|
+
)
|
998
1015
|
return None
|
999
1016
|
|
1000
1017
|
# Create tasks for all chunk uploads
|
@@ -1042,7 +1059,7 @@ class IPFSClient:
|
|
1042
1059
|
temp_dir: str = None,
|
1043
1060
|
max_retries: int = 3,
|
1044
1061
|
verbose: bool = True,
|
1045
|
-
) ->
|
1062
|
+
) -> Dict:
|
1046
1063
|
"""
|
1047
1064
|
Reconstruct a file from erasure-coded chunks using its metadata.
|
1048
1065
|
|
@@ -1054,7 +1071,7 @@ class IPFSClient:
|
|
1054
1071
|
verbose: Whether to print progress information
|
1055
1072
|
|
1056
1073
|
Returns:
|
1057
|
-
|
1074
|
+
Dict: containing file reconstruction info.
|
1058
1075
|
|
1059
1076
|
Raises:
|
1060
1077
|
ValueError: If reconstruction fails
|
@@ -1347,7 +1364,10 @@ class IPFSClient:
|
|
1347
1364
|
print(f"Reconstruction complete in {total_time:.2f} seconds!")
|
1348
1365
|
print(f"File saved to: {output_file}")
|
1349
1366
|
|
1350
|
-
return
|
1367
|
+
return {
|
1368
|
+
"output_path": output_file,
|
1369
|
+
"size_bytes": size_processed,
|
1370
|
+
}
|
1351
1371
|
|
1352
1372
|
finally:
|
1353
1373
|
# Clean up temporary directory if we created it
|
@@ -1365,6 +1385,7 @@ class IPFSClient:
|
|
1365
1385
|
substrate_client=None,
|
1366
1386
|
max_retries: int = 3,
|
1367
1387
|
verbose: bool = True,
|
1388
|
+
progress_callback: Optional[Callable[[str, int, int], None]] = None,
|
1368
1389
|
) -> Dict[str, Any]:
|
1369
1390
|
"""
|
1370
1391
|
Erasure code a file, upload the chunks to IPFS, and store in the Hippius marketplace.
|
@@ -1381,6 +1402,8 @@ class IPFSClient:
|
|
1381
1402
|
substrate_client: SubstrateClient to use (or None to create one)
|
1382
1403
|
max_retries: Maximum number of retry attempts
|
1383
1404
|
verbose: Whether to print progress information
|
1405
|
+
progress_callback: Optional callback function for progress updates
|
1406
|
+
Function receives (stage_name, current, total)
|
1384
1407
|
|
1385
1408
|
Returns:
|
1386
1409
|
dict: Result including metadata CID and transaction hash
|
@@ -1398,6 +1421,7 @@ class IPFSClient:
|
|
1398
1421
|
encrypt=encrypt,
|
1399
1422
|
max_retries=max_retries,
|
1400
1423
|
verbose=verbose,
|
1424
|
+
progress_callback=progress_callback,
|
1401
1425
|
)
|
1402
1426
|
|
1403
1427
|
# Step 2: Create substrate client if we need it
|
@@ -1472,3 +1496,335 @@ class IPFSClient:
|
|
1472
1496
|
print(f"Error storing files in marketplace: {str(e)}")
|
1473
1497
|
# Return the metadata even if storage fails
|
1474
1498
|
return {"metadata": metadata, "metadata_cid": metadata_cid, "error": str(e)}
|
1499
|
+
|
1500
|
+
async def delete_file(
|
1501
|
+
self, cid: str, cancel_from_blockchain: bool = True
|
1502
|
+
) -> Dict[str, Any]:
|
1503
|
+
"""
|
1504
|
+
Delete a file from IPFS and optionally cancel its storage on the blockchain.
|
1505
|
+
|
1506
|
+
Args:
|
1507
|
+
cid: Content Identifier (CID) of the file to delete
|
1508
|
+
cancel_from_blockchain: Whether to also cancel the storage request from the blockchain
|
1509
|
+
|
1510
|
+
Returns:
|
1511
|
+
Dict containing the result of the operation
|
1512
|
+
"""
|
1513
|
+
result = {
|
1514
|
+
"cid": cid,
|
1515
|
+
"unpin_result": None,
|
1516
|
+
"blockchain_result": None,
|
1517
|
+
"timing": {
|
1518
|
+
"start_time": time.time(),
|
1519
|
+
"end_time": None,
|
1520
|
+
"duration_seconds": None,
|
1521
|
+
},
|
1522
|
+
}
|
1523
|
+
|
1524
|
+
# First, unpin from IPFS
|
1525
|
+
try:
|
1526
|
+
print(f"Unpinning file from IPFS: {cid}")
|
1527
|
+
try:
|
1528
|
+
# Try to check if file exists in IPFS before unpinning
|
1529
|
+
await self.exists(cid)
|
1530
|
+
except Exception as exists_e:
|
1531
|
+
print(f"ERROR: Error checking file existence: {exists_e}")
|
1532
|
+
|
1533
|
+
unpin_result = await self.client.unpin(cid)
|
1534
|
+
result["unpin_result"] = unpin_result
|
1535
|
+
print("Successfully unpinned from IPFS")
|
1536
|
+
except Exception as e:
|
1537
|
+
print(f"Warning: Failed to unpin file from IPFS: {e}")
|
1538
|
+
raise
|
1539
|
+
|
1540
|
+
# Then, if requested, cancel from blockchain
|
1541
|
+
if cancel_from_blockchain:
|
1542
|
+
try:
|
1543
|
+
# Create a substrate client
|
1544
|
+
print(f"DEBUG: Creating SubstrateClient for blockchain cancellation...")
|
1545
|
+
substrate_client = SubstrateClient()
|
1546
|
+
print(
|
1547
|
+
f"DEBUG: Substrate client created with URL: {substrate_client.url}"
|
1548
|
+
)
|
1549
|
+
print(f"DEBUG: Calling cancel_storage_request with CID: {cid}")
|
1550
|
+
|
1551
|
+
tx_hash = await substrate_client.cancel_storage_request(cid)
|
1552
|
+
print(f"DEBUG: Received transaction hash: {tx_hash}")
|
1553
|
+
|
1554
|
+
# Check the return value - special cases for when blockchain cancellation isn't available
|
1555
|
+
if tx_hash == "no-blockchain-cancellation-available":
|
1556
|
+
print(
|
1557
|
+
"Blockchain cancellation not available, but IPFS unpinning was successful"
|
1558
|
+
)
|
1559
|
+
result["blockchain_result"] = {
|
1560
|
+
"status": "not_available",
|
1561
|
+
"message": "Blockchain cancellation not available, but IPFS unpinning was successful",
|
1562
|
+
}
|
1563
|
+
elif tx_hash.startswith("ipfs-unpinned-only"):
|
1564
|
+
error_msg = tx_hash.replace("ipfs-unpinned-only-", "")
|
1565
|
+
print(
|
1566
|
+
f"IPFS unpinning successful, but blockchain cancellation failed: {error_msg}"
|
1567
|
+
)
|
1568
|
+
result["blockchain_result"] = {
|
1569
|
+
"status": "failed",
|
1570
|
+
"error": error_msg,
|
1571
|
+
"message": "IPFS unpinning successful, but blockchain cancellation failed",
|
1572
|
+
}
|
1573
|
+
else:
|
1574
|
+
# Standard successful transaction
|
1575
|
+
result["blockchain_result"] = {
|
1576
|
+
"transaction_hash": tx_hash,
|
1577
|
+
"status": "success",
|
1578
|
+
}
|
1579
|
+
print(f"Successfully canceled storage request from blockchain")
|
1580
|
+
print(
|
1581
|
+
f"DEBUG: Blockchain cancellation succeeded with transaction hash: {tx_hash}"
|
1582
|
+
)
|
1583
|
+
except Exception as e:
|
1584
|
+
print(f"Warning: Failed to cancel storage from blockchain: {e}")
|
1585
|
+
print(
|
1586
|
+
f"DEBUG: Blockchain cancellation exception: {type(e).__name__}: {str(e)}"
|
1587
|
+
)
|
1588
|
+
if hasattr(e, "__dict__"):
|
1589
|
+
print(f"DEBUG: Exception attributes: {e.__dict__}")
|
1590
|
+
result["blockchain_error"] = str(e)
|
1591
|
+
|
1592
|
+
# Calculate timing
|
1593
|
+
result["timing"]["end_time"] = time.time()
|
1594
|
+
result["timing"]["duration_seconds"] = (
|
1595
|
+
result["timing"]["end_time"] - result["timing"]["start_time"]
|
1596
|
+
)
|
1597
|
+
|
1598
|
+
return result
|
1599
|
+
|
1600
|
+
async def delete_ec_file(
|
1601
|
+
self,
|
1602
|
+
metadata_cid: str,
|
1603
|
+
cancel_from_blockchain: bool = True,
|
1604
|
+
parallel_limit: int = 20,
|
1605
|
+
) -> Dict[str, Any]:
|
1606
|
+
"""
|
1607
|
+
Delete an erasure-coded file, including all its chunks in parallel.
|
1608
|
+
|
1609
|
+
Args:
|
1610
|
+
metadata_cid: CID of the metadata file for the erasure-coded file
|
1611
|
+
cancel_from_blockchain: Whether to cancel storage from blockchain
|
1612
|
+
parallel_limit: Maximum number of concurrent deletion operations
|
1613
|
+
|
1614
|
+
Returns:
|
1615
|
+
Dict containing the result of the operation
|
1616
|
+
"""
|
1617
|
+
result = {
|
1618
|
+
"metadata_cid": metadata_cid,
|
1619
|
+
"deleted_chunks": [],
|
1620
|
+
"failed_chunks": [],
|
1621
|
+
"blockchain_result": None,
|
1622
|
+
"timing": {
|
1623
|
+
"start_time": time.time(),
|
1624
|
+
"end_time": None,
|
1625
|
+
"duration_seconds": None,
|
1626
|
+
},
|
1627
|
+
}
|
1628
|
+
|
1629
|
+
# Track deletions for reporting
|
1630
|
+
deleted_chunks_lock = asyncio.Lock()
|
1631
|
+
failed_chunks_lock = asyncio.Lock()
|
1632
|
+
|
1633
|
+
# First, get the metadata to find all chunks
|
1634
|
+
try:
|
1635
|
+
print(f"Downloading metadata file (CID: {metadata_cid})...")
|
1636
|
+
start_time = time.time()
|
1637
|
+
metadata_content = await self.client.cat(metadata_cid)
|
1638
|
+
metadata = json.loads(metadata_content.decode("utf-8"))
|
1639
|
+
metadata_download_time = time.time() - start_time
|
1640
|
+
|
1641
|
+
print(f"Metadata downloaded in {metadata_download_time:.2f} seconds")
|
1642
|
+
|
1643
|
+
# Extract chunk CIDs
|
1644
|
+
chunks = []
|
1645
|
+
total_chunks = 0
|
1646
|
+
|
1647
|
+
for chunk_data in metadata.get("chunks", []):
|
1648
|
+
for ec_chunk in chunk_data.get("ec_chunks", []):
|
1649
|
+
chunk_cid = ec_chunk.get("cid")
|
1650
|
+
if chunk_cid:
|
1651
|
+
chunks.append(chunk_cid)
|
1652
|
+
total_chunks += 1
|
1653
|
+
|
1654
|
+
print(f"Found {total_chunks} chunks to delete")
|
1655
|
+
|
1656
|
+
# Create a semaphore to limit concurrent operations
|
1657
|
+
sem = asyncio.Semaphore(parallel_limit)
|
1658
|
+
|
1659
|
+
# Define the chunk deletion function
|
1660
|
+
async def delete_chunk(chunk_cid):
|
1661
|
+
async with sem:
|
1662
|
+
try:
|
1663
|
+
print(f"Unpinning chunk: {chunk_cid}")
|
1664
|
+
await self.client.unpin(chunk_cid)
|
1665
|
+
|
1666
|
+
# Record success
|
1667
|
+
async with deleted_chunks_lock:
|
1668
|
+
result["deleted_chunks"].append(chunk_cid)
|
1669
|
+
|
1670
|
+
# Cancel from blockchain if requested
|
1671
|
+
if cancel_from_blockchain:
|
1672
|
+
try:
|
1673
|
+
substrate_client = SubstrateClient()
|
1674
|
+
tx_hash = await substrate_client.cancel_storage_request(
|
1675
|
+
chunk_cid
|
1676
|
+
)
|
1677
|
+
|
1678
|
+
# Add blockchain result
|
1679
|
+
if "chunk_results" not in result["blockchain_result"]:
|
1680
|
+
result["blockchain_result"] = {}
|
1681
|
+
result["blockchain_result"]["chunk_results"] = []
|
1682
|
+
|
1683
|
+
# Handle special return values from cancel_storage_request
|
1684
|
+
if tx_hash == "no-blockchain-cancellation-available":
|
1685
|
+
result["blockchain_result"]["chunk_results"].append(
|
1686
|
+
{
|
1687
|
+
"cid": chunk_cid,
|
1688
|
+
"status": "not_available",
|
1689
|
+
"message": "Blockchain cancellation not available",
|
1690
|
+
}
|
1691
|
+
)
|
1692
|
+
elif tx_hash.startswith("ipfs-unpinned-only"):
|
1693
|
+
error_msg = tx_hash.replace(
|
1694
|
+
"ipfs-unpinned-only-", ""
|
1695
|
+
)
|
1696
|
+
result["blockchain_result"]["chunk_results"].append(
|
1697
|
+
{
|
1698
|
+
"cid": chunk_cid,
|
1699
|
+
"status": "failed",
|
1700
|
+
"error": error_msg,
|
1701
|
+
}
|
1702
|
+
)
|
1703
|
+
else:
|
1704
|
+
# Standard successful transaction
|
1705
|
+
result["blockchain_result"]["chunk_results"].append(
|
1706
|
+
{
|
1707
|
+
"cid": chunk_cid,
|
1708
|
+
"transaction_hash": tx_hash,
|
1709
|
+
"status": "success",
|
1710
|
+
}
|
1711
|
+
)
|
1712
|
+
except Exception as e:
|
1713
|
+
print(
|
1714
|
+
f"Warning: Failed to cancel blockchain storage for chunk {chunk_cid}: {e}"
|
1715
|
+
)
|
1716
|
+
|
1717
|
+
if "chunk_results" not in result["blockchain_result"]:
|
1718
|
+
result["blockchain_result"] = {}
|
1719
|
+
result["blockchain_result"]["chunk_results"] = []
|
1720
|
+
|
1721
|
+
result["blockchain_result"]["chunk_results"].append(
|
1722
|
+
{
|
1723
|
+
"cid": chunk_cid,
|
1724
|
+
"error": str(e),
|
1725
|
+
"status": "failed",
|
1726
|
+
}
|
1727
|
+
)
|
1728
|
+
|
1729
|
+
return True
|
1730
|
+
except Exception as e:
|
1731
|
+
error_msg = f"Failed to delete chunk {chunk_cid}: {e}"
|
1732
|
+
print(f"Warning: {error_msg}")
|
1733
|
+
|
1734
|
+
# Record failure
|
1735
|
+
async with failed_chunks_lock:
|
1736
|
+
result["failed_chunks"].append(
|
1737
|
+
{"cid": chunk_cid, "error": str(e)}
|
1738
|
+
)
|
1739
|
+
|
1740
|
+
return False
|
1741
|
+
|
1742
|
+
# Start deleting chunks in parallel
|
1743
|
+
print(
|
1744
|
+
f"Starting parallel deletion of {total_chunks} chunks with max {parallel_limit} concurrent operations"
|
1745
|
+
)
|
1746
|
+
delete_tasks = [delete_chunk(cid) for cid in chunks]
|
1747
|
+
await asyncio.gather(*delete_tasks)
|
1748
|
+
|
1749
|
+
# Delete the metadata file itself
|
1750
|
+
print(f"Unpinning metadata file: {metadata_cid}")
|
1751
|
+
response = await self.client.unpin(metadata_cid)
|
1752
|
+
|
1753
|
+
print(">>>", response)
|
1754
|
+
raise SystemExit
|
1755
|
+
|
1756
|
+
# Cancel metadata from blockchain if requested
|
1757
|
+
if cancel_from_blockchain:
|
1758
|
+
try:
|
1759
|
+
print(f"Canceling blockchain storage request for metadata file...")
|
1760
|
+
substrate_client = SubstrateClient()
|
1761
|
+
tx_hash = await substrate_client.cancel_storage_request(
|
1762
|
+
metadata_cid
|
1763
|
+
)
|
1764
|
+
|
1765
|
+
# Handle special return values from cancel_storage_request
|
1766
|
+
if tx_hash == "no-blockchain-cancellation-available":
|
1767
|
+
print(
|
1768
|
+
"Blockchain cancellation not available for metadata, but IPFS unpinning was successful"
|
1769
|
+
)
|
1770
|
+
result["blockchain_result"] = {
|
1771
|
+
"status": "not_available",
|
1772
|
+
"message": "Blockchain cancellation not available, but IPFS unpinning was successful",
|
1773
|
+
}
|
1774
|
+
elif tx_hash.startswith("ipfs-unpinned-only"):
|
1775
|
+
error_msg = tx_hash.replace("ipfs-unpinned-only-", "")
|
1776
|
+
print(
|
1777
|
+
f"IPFS unpinning successful, but blockchain cancellation failed for metadata: {error_msg}"
|
1778
|
+
)
|
1779
|
+
result["blockchain_result"] = {
|
1780
|
+
"status": "failed",
|
1781
|
+
"error": error_msg,
|
1782
|
+
"message": "IPFS unpinning successful, but blockchain cancellation failed",
|
1783
|
+
}
|
1784
|
+
else:
|
1785
|
+
# Standard successful transaction
|
1786
|
+
result["blockchain_result"] = {
|
1787
|
+
"metadata_transaction_hash": tx_hash,
|
1788
|
+
"status": "success",
|
1789
|
+
}
|
1790
|
+
print(
|
1791
|
+
f"Successfully canceled blockchain storage for metadata file"
|
1792
|
+
)
|
1793
|
+
except Exception as e:
|
1794
|
+
print(
|
1795
|
+
f"Warning: Failed to cancel blockchain storage for metadata file: {e}"
|
1796
|
+
)
|
1797
|
+
|
1798
|
+
if not result["blockchain_result"]:
|
1799
|
+
result["blockchain_result"] = {}
|
1800
|
+
|
1801
|
+
result["blockchain_result"]["metadata_error"] = str(e)
|
1802
|
+
result["blockchain_result"]["status"] = "failed"
|
1803
|
+
|
1804
|
+
# Calculate and record timing information
|
1805
|
+
end_time = time.time()
|
1806
|
+
duration = end_time - result["timing"]["start_time"]
|
1807
|
+
|
1808
|
+
result["timing"]["end_time"] = end_time
|
1809
|
+
result["timing"]["duration_seconds"] = duration
|
1810
|
+
|
1811
|
+
deleted_count = len(result["deleted_chunks"])
|
1812
|
+
failed_count = len(result["failed_chunks"])
|
1813
|
+
|
1814
|
+
print(f"Deletion complete in {duration:.2f} seconds!")
|
1815
|
+
print(f"Successfully deleted: {deleted_count}/{total_chunks} chunks")
|
1816
|
+
|
1817
|
+
if failed_count > 0:
|
1818
|
+
print(f"Failed to delete: {failed_count}/{total_chunks} chunks")
|
1819
|
+
|
1820
|
+
return result
|
1821
|
+
except Exception as e:
|
1822
|
+
# Record end time even if there was an error
|
1823
|
+
result["timing"]["end_time"] = time.time()
|
1824
|
+
result["timing"]["duration_seconds"] = (
|
1825
|
+
result["timing"]["end_time"] - result["timing"]["start_time"]
|
1826
|
+
)
|
1827
|
+
|
1828
|
+
error_msg = f"Error deleting erasure-coded file: {e}"
|
1829
|
+
print(f"Error: {error_msg}")
|
1830
|
+
raise RuntimeError(error_msg)
|
hippius_sdk/ipfs_core.py
CHANGED
@@ -11,7 +11,9 @@ class AsyncIPFSClient:
|
|
11
11
|
"""
|
12
12
|
|
13
13
|
def __init__(
|
14
|
-
self,
|
14
|
+
self,
|
15
|
+
api_url: str = "http://localhost:5001",
|
16
|
+
gateway: str = "https://get.hippius.network",
|
15
17
|
):
|
16
18
|
# Handle multiaddr format
|
17
19
|
if api_url and api_url.startswith("/"):
|
@@ -119,6 +121,25 @@ class AsyncIPFSClient:
|
|
119
121
|
response.raise_for_status()
|
120
122
|
return response.json()
|
121
123
|
|
124
|
+
async def unpin(self, cid: str) -> Dict[str, Any]:
|
125
|
+
"""
|
126
|
+
Unpin content by CID.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
cid: Content Identifier to unpin
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
Response from the IPFS node
|
133
|
+
"""
|
134
|
+
pin_ls_url = f"{self.api_url}/api/v0/pin/ls?arg={cid}"
|
135
|
+
pin_ls_response = await self.client.post(pin_ls_url)
|
136
|
+
pin_ls_response.raise_for_status()
|
137
|
+
response = await self.client.post(f"{self.api_url}/api/v0/pin/rm?arg={cid}")
|
138
|
+
|
139
|
+
response.raise_for_status()
|
140
|
+
result = response.json()
|
141
|
+
return result
|
142
|
+
|
122
143
|
async def ls(self, cid: str) -> Dict[str, Any]:
|
123
144
|
"""
|
124
145
|
List objects linked to the specified CID.
|