hippius 0.2.6__py3-none-any.whl → 0.2.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hippius
3
- Version: 0.2.6
3
+ Version: 0.2.8
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
@@ -1,8 +1,8 @@
1
- hippius_sdk/__init__.py,sha256=n3IvS-VlVHsBq7HFCSgQvFcJ4ZTvs8MNfwrETRM6WBM,1391
2
- hippius_sdk/cli.py,sha256=lpiokB5wt5nqcGOkvg62NNWr1l4ky9xCM53d14PyGWw,17907
1
+ hippius_sdk/__init__.py,sha256=qXKCaurObaxcmP718Q9X2nSROm3ZYaetT3sR69fPcSI,1391
2
+ hippius_sdk/cli.py,sha256=pzwoa-X5cwdA_pM-fqUyXZKHgcZODrLe4qHZuCqQMtQ,18210
3
3
  hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
4
- hippius_sdk/cli_handlers.py,sha256=GEjVFKIfXL0MYJiNQSdMovtqv2MheIN9ftq7z4VpPhU,120889
5
- hippius_sdk/cli_parser.py,sha256=fU4kIeNM4an6FtyVX13-u33MmO70PrNzvmPe2ZtPf-0,19576
4
+ hippius_sdk/cli_handlers.py,sha256=LUS-BPPMfvXCLsHiN225nMTlBEHXKxZmUf1YpC-Xyqc,127905
5
+ hippius_sdk/cli_parser.py,sha256=Qh2wgkFBUTPldvGoTQuoNKQl5Vo0x6fPEsPBU5oymP4,20242
6
6
  hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
7
7
  hippius_sdk/client.py,sha256=eYURsq_so3WlEt_JY_u7J0iECFVOKDf5vsnGyR9Kngw,16974
8
8
  hippius_sdk/config.py,sha256=sCWD2remLa-FodvxC2O45tiNSJD7gzv9idIStX9sF_k,21240
@@ -11,7 +11,7 @@ hippius_sdk/ipfs.py,sha256=xq0y87dyKvbwf52OjEAlqFNrl4Ej1Zp5g2rZa75qfN8,71706
11
11
  hippius_sdk/ipfs_core.py,sha256=Lxu2-AWEpE-kiJDcP-cOBEeuboK7i9eNZdjgJWPa4sI,16028
12
12
  hippius_sdk/substrate.py,sha256=HqR2-_9njZZ5UCKgiaGr5L5TGQ_wtj7oyA3sA5sGGyE,47525
13
13
  hippius_sdk/utils.py,sha256=Ur7P_7iVnXXYvbg7a0aVrdN_8NkVxjhdngn8NzR_zpc,7066
14
- hippius-0.2.6.dist-info/METADATA,sha256=OnPIZTwaJ-DtBYDYhVjEwGAoC13_0rSgWs7jDLaIAHQ,29992
15
- hippius-0.2.6.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
16
- hippius-0.2.6.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
17
- hippius-0.2.6.dist-info/RECORD,,
14
+ hippius-0.2.8.dist-info/METADATA,sha256=8XQCTdk2Yw6HGik6V3GSj6S1qt_-yqM00PT5Q5EcBV8,29992
15
+ hippius-0.2.8.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
16
+ hippius-0.2.8.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
17
+ hippius-0.2.8.dist-info/RECORD,,
hippius_sdk/__init__.py CHANGED
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
26
26
  from hippius_sdk.ipfs import IPFSClient
27
27
  from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
28
28
 
29
- __version__ = "0.2.6"
29
+ __version__ = "0.2.8"
30
30
  __all__ = [
31
31
  "HippiusClient",
32
32
  "IPFSClient",
hippius_sdk/cli.py CHANGED
@@ -236,6 +236,15 @@ def main():
236
236
  force=args.force if hasattr(args, "force") else False,
237
237
  )
238
238
 
239
+ elif args.command == "pin":
240
+ return run_async_handler(
241
+ cli_handlers.handle_pin,
242
+ client,
243
+ args.cid,
244
+ publish=not args.no_publish if hasattr(args, "no_publish") else True,
245
+ miner_ids=miner_ids,
246
+ )
247
+
239
248
  elif args.command == "ec-delete":
240
249
  return run_async_handler(
241
250
  cli_handlers.handle_ec_delete,
@@ -371,20 +371,22 @@ async def handle_store(
371
371
  "gateway_url"
372
372
  ] = f"{client.ipfs_client.gateway}/ipfs/{result['cid']}"
373
373
 
374
- # Store on blockchain if miners are provided
375
- if miner_ids:
376
- # Create a file input for blockchain storage
377
- file_input = FileInput(
378
- file_hash=result["cid"], file_name=file_name
379
- )
374
+ # Store on blockchain - miners are optional
375
+ # Create a file input for blockchain storage
376
+ file_input = FileInput(file_hash=result["cid"], file_name=file_name)
380
377
 
381
- # Submit storage request
382
- tx_hash = await client.substrate_client.storage_request(
383
- files=[file_input], miner_ids=miner_id_list
384
- )
378
+ # Submit storage request
379
+ tx_hash = await client.substrate_client.storage_request(
380
+ files=[file_input], miner_ids=miner_id_list
381
+ )
385
382
 
386
- # Add transaction hash to result
387
- result["transaction_hash"] = tx_hash
383
+ # Add transaction hash to result
384
+ result["transaction_hash"] = tx_hash
385
+
386
+ # Add a note about the pinning status command
387
+ log(
388
+ "\n[bold yellow]Note:[/bold yellow] The pinning-status command will show a different CID (metadata) rather than the direct file CID."
389
+ )
388
390
  except Exception as e:
389
391
  warning(f"Failed to publish file globally: {str(e)}")
390
392
 
@@ -575,12 +577,8 @@ async def handle_store_dir(
575
577
  f"Failed to publish file {file_info['name']} globally: {str(e)}"
576
578
  )
577
579
 
578
- # Store on blockchain if miners are provided - this is what requires a password
579
- if (
580
- miner_ids
581
- and hasattr(client, "substrate_client")
582
- and client.substrate_client
583
- ):
580
+ # Store on blockchain if client is available - miners are optional
581
+ if hasattr(client, "substrate_client") and client.substrate_client:
584
582
  # Create a file input for blockchain storage
585
583
  file_input = FileInput(
586
584
  file_hash=result["cid"],
@@ -595,6 +593,11 @@ async def handle_store_dir(
595
593
  # Add transaction hash to result
596
594
  result["transaction_hash"] = tx_hash
597
595
 
596
+ # Add a note about the pinning status command
597
+ log(
598
+ "\n[bold yellow]Note:[/bold yellow] The pinning-status command will show a different CID (metadata) rather than the direct directory CID."
599
+ )
600
+
598
601
  except Exception as e:
599
602
  warning(f"Failed to publish directory globally: {str(e)}")
600
603
 
@@ -739,20 +742,32 @@ async def handle_files(
739
742
  """Handle the files command"""
740
743
  # Get the account address we're querying
741
744
  if account_address is None:
742
- # If no address provided, first try to get from keypair (if available)
745
+ # If no address provided, try these options in order:
746
+ # 1. Keypair from the client (if available)
747
+ # 2. Address from active account
748
+ # 3. Default address from config
749
+
750
+ # Option 1: Try keypair from client
743
751
  if (
744
752
  hasattr(client.substrate_client, "_keypair")
745
753
  and client.substrate_client._keypair is not None
746
754
  ):
747
755
  account_address = client.substrate_client._keypair.ss58_address
748
756
  else:
749
- # Try to get the default address
750
- default_address = get_default_address()
751
- if default_address:
752
- account_address = default_address
753
- else:
754
- has_default = get_default_address() is not None
757
+ # Option 2: Try to get address from active account
758
+ active_account = get_active_account()
759
+ if active_account:
760
+ account_address = get_account_address(active_account)
755
761
 
762
+ # Option 3: If still not found, try default address
763
+ if not account_address:
764
+ default_address = get_default_address()
765
+ if default_address:
766
+ account_address = default_address
767
+
768
+ # If we still don't have an address, show error
769
+ if not account_address:
770
+ has_default = get_default_address() is not None
756
771
  error("No account address provided, and client has no keypair.")
757
772
 
758
773
  if has_default:
@@ -842,7 +857,7 @@ async def handle_pinning_status(
842
857
  cid = pin.get("cid")
843
858
 
844
859
  # Display pin information
845
- log(f"\n{i}. CID: [bold]{cid}[/bold]")
860
+ log(f"\n{i}. Metadata CID: [bold]{cid}[/bold]")
846
861
  log(f" File Name: {pin['file_name']}")
847
862
  status = "Assigned" if pin["is_assigned"] else "Pending"
848
863
  log(f" Status: {status}")
@@ -861,17 +876,49 @@ async def handle_pinning_status(
861
876
 
862
877
  # Show content info if requested
863
878
  if show_contents:
879
+ # Add gateway URL
880
+ gateway_url = f"{client.ipfs_client.gateway}/ipfs/{cid}"
881
+ log(f" Gateway URL: {gateway_url}")
882
+
883
+ # Try to decode the metadata file to get the original file CID
864
884
  try:
865
- content_info = await client.ipfs_client.get_content_info(cid)
885
+ # Fetch the content
886
+ cat_result = await client.ipfs_client.cat(cid)
866
887
 
867
- if content_info:
868
- if "size_formatted" in content_info:
869
- log(f" Size: {content_info['size_formatted']}")
870
- if "gateway_url" in content_info:
871
- log(f" Gateway URL: {content_info['gateway_url']}")
888
+ # Try to parse as JSON
889
+ try:
890
+ # Decode JSON from content
891
+ metadata_json = json.loads(
892
+ cat_result["content"].decode("utf-8")
893
+ )
894
+
895
+ # This should be an array with one or more file entries
896
+ if (
897
+ isinstance(metadata_json, list)
898
+ and len(metadata_json) > 0
899
+ ):
900
+ log(f"\n [bold cyan]Contained files:[/bold cyan]")
901
+ for idx, file_entry in enumerate(metadata_json, 1):
902
+ if isinstance(file_entry, dict):
903
+ original_cid = file_entry.get("cid")
904
+ original_name = file_entry.get("filename")
905
+ if original_cid:
906
+ log(
907
+ f" {idx}. Original CID: [bold green]{original_cid}[/bold green]"
908
+ )
909
+ if original_name:
910
+ log(f" Name: {original_name}")
911
+ log(
912
+ f" Gateway URL: {client.ipfs_client.gateway}/ipfs/{original_cid}"
913
+ )
914
+ except json.JSONDecodeError:
915
+ if verbose:
916
+ log(
917
+ " [yellow]Could not parse metadata as JSON[/yellow]"
918
+ )
872
919
  except Exception as e:
873
920
  if verbose:
874
- warning(f" Error getting content info: {e}")
921
+ warning(f" Error getting original file CIDs: {e}")
875
922
  except Exception as e:
876
923
  warning(f"Error processing pin {i}: {e}")
877
924
  if verbose:
@@ -1713,6 +1760,117 @@ async def handle_delete(client: HippiusClient, cid: str, force: bool = False) ->
1713
1760
  return 0
1714
1761
 
1715
1762
 
1763
+ async def handle_pin(
1764
+ client: HippiusClient, cid: str, publish: bool = True, miner_ids=None
1765
+ ) -> int:
1766
+ """Handle the pin command to pin a CID to IPFS and optionally publish to blockchain"""
1767
+ from rich.panel import Panel
1768
+
1769
+ # First check if this CID exists
1770
+ try:
1771
+ exists_result = await client.exists(cid)
1772
+ if not exists_result["exists"]:
1773
+ error(f"CID [bold cyan]{cid}[/bold cyan] not found on IPFS")
1774
+ return 1
1775
+ except Exception as e:
1776
+ warning(f"Error checking if CID exists: {e}")
1777
+ return 1
1778
+
1779
+ # Create operation title based on publish flag
1780
+ if publish:
1781
+ info(
1782
+ f"Preparing to pin and publish content with CID: [bold cyan]{cid}[/bold cyan]"
1783
+ )
1784
+ operation_title = "Pin & Publish Operation"
1785
+ else:
1786
+ info(f"Preparing to pin content with CID: [bold cyan]{cid}[/bold cyan]")
1787
+ operation_title = "Pin Operation"
1788
+
1789
+ # Display operation details
1790
+ operation_details = [
1791
+ f"CID: [bold cyan]{cid}[/bold cyan]",
1792
+ f"Publishing to blockchain: {'Enabled' if publish else 'Disabled'}",
1793
+ ]
1794
+ print_panel("\n".join(operation_details), title=operation_title)
1795
+
1796
+ # Need to authenticate if publishing to blockchain
1797
+ if publish:
1798
+ try:
1799
+ # Ensure we have a keypair for substrate operations
1800
+ _ = client.substrate_client._ensure_keypair()
1801
+ except Exception as e:
1802
+ warning(f"Failed to initialize blockchain client: {str(e)}")
1803
+ warning("Will continue with pinning but blockchain publishing may fail")
1804
+
1805
+ # Show spinner during pinning
1806
+ with console.status(
1807
+ "[cyan]Pinning content to IPFS...[/cyan]", spinner="dots"
1808
+ ) as status:
1809
+ try:
1810
+ # Pin the content to IPFS
1811
+ pin_result = await client.ipfs_client.pin(cid)
1812
+
1813
+ if not pin_result.get("success", False):
1814
+ error(
1815
+ f"Failed to pin content: {pin_result.get('message', 'Unknown error')}"
1816
+ )
1817
+ return 1
1818
+
1819
+ # If publishing to blockchain, do that now
1820
+ if publish:
1821
+ status.update("[cyan]Publishing content to blockchain...[/cyan]")
1822
+
1823
+ # Create a FileInput object for the substrate client
1824
+ from hippius_sdk.substrate import FileInput
1825
+
1826
+ file_input = FileInput(file_hash=cid, file_name=f"pinned_{cid}")
1827
+
1828
+ # Submit the storage request
1829
+ tx_hash = await client.substrate_client.storage_request(
1830
+ files=[file_input], miner_ids=miner_ids
1831
+ )
1832
+
1833
+ # Create result panel with blockchain details
1834
+ gateway_url = f"{client.ipfs_client.gateway}/ipfs/{cid}"
1835
+ panel_details = [
1836
+ f"Successfully pinned and published: [bold cyan]{cid}[/bold cyan]",
1837
+ f"Gateway URL: [bold cyan]{gateway_url}[/bold cyan]",
1838
+ f"Transaction hash: [bold green]{tx_hash}[/bold green]",
1839
+ "\nThis content is now:",
1840
+ "1. Pinned to your IPFS node",
1841
+ "2. Published to the IPFS network",
1842
+ "3. Stored on the Hippius blockchain",
1843
+ ]
1844
+ console.print(
1845
+ Panel(
1846
+ "\n".join(panel_details),
1847
+ title="Operation Complete",
1848
+ border_style="green",
1849
+ )
1850
+ )
1851
+ else:
1852
+ # Just pinning, no blockchain publishing
1853
+ gateway_url = f"{client.ipfs_client.gateway}/ipfs/{cid}"
1854
+ panel_details = [
1855
+ f"Successfully pinned: [bold cyan]{cid}[/bold cyan]",
1856
+ f"Gateway URL: [bold cyan]{gateway_url}[/bold cyan]",
1857
+ "\nThis content is now pinned to your IPFS node.",
1858
+ "It will remain available as long as your node is running.",
1859
+ ]
1860
+ console.print(
1861
+ Panel(
1862
+ "\n".join(panel_details),
1863
+ title="Pinning Complete",
1864
+ border_style="green",
1865
+ )
1866
+ )
1867
+
1868
+ return 0
1869
+ except Exception as e:
1870
+ error(f"Error during operation: {e}")
1871
+ return 1
1872
+
1873
+
1716
1874
  async def handle_ec_delete(
1717
1875
  client: HippiusClient, metadata_cid: str, force: bool = False
1718
1876
  ) -> int:
hippius_sdk/cli_parser.py CHANGED
@@ -63,13 +63,19 @@ examples:
63
63
 
64
64
  # Erasure code without publishing to global IPFS network
65
65
  hippius erasure-code large_file.avi --no-publish
66
-
66
+
67
67
  # Reconstruct an erasure-coded file
68
68
  hippius reconstruct QmMetadataHash reconstructed_file.mp4
69
-
69
+
70
+ # Pin a CID to IPFS and publish to blockchain
71
+ hippius pin QmHash
72
+
73
+ # Pin a CID to IPFS without publishing to blockchain
74
+ hippius pin QmHash --no-publish
75
+
70
76
  # Delete a file from IPFS and marketplace
71
77
  hippius delete QmHash
72
-
78
+
73
79
  # Delete an erasure-coded file and all its chunks
74
80
  hippius ec-delete QmMetadataHash
75
81
  """,
@@ -250,6 +256,23 @@ def add_storage_commands(subparsers):
250
256
  help="Delete without confirmation prompt",
251
257
  )
252
258
 
259
+ # Pin command
260
+ pin_parser = subparsers.add_parser(
261
+ "pin",
262
+ help="Pin a CID to IPFS and publish to blockchain",
263
+ )
264
+ pin_parser.add_argument("cid", help="CID to pin")
265
+ pin_parser.add_argument(
266
+ "--publish",
267
+ action="store_true",
268
+ help="Publish file to IPFS and store on the blockchain (default)",
269
+ )
270
+ pin_parser.add_argument(
271
+ "--no-publish",
272
+ action="store_true",
273
+ help="Don't publish file to blockchain (local pinning only)",
274
+ )
275
+
253
276
  # Keygen command
254
277
  keygen_parser = subparsers.add_parser(
255
278
  "keygen", help="Generate an encryption key for secure file storage"