hippius 0.2.40__tar.gz → 0.2.41__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.
- {hippius-0.2.40 → hippius-0.2.41}/PKG-INFO +1 -1
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/__init__.py +1 -1
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/client.py +42 -30
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/ipfs.py +148 -73
- {hippius-0.2.40 → hippius-0.2.41}/pyproject.toml +1 -1
- {hippius-0.2.40 → hippius-0.2.41}/README.md +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/cli.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/cli_assets.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/cli_handlers.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/cli_parser.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/cli_rich.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/config.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db/README.md +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db/env.db.template +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db/setup_database.sh +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/db_utils.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/errors.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/ipfs_core.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/key_storage.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/substrate.py +0 -0
- {hippius-0.2.40 → hippius-0.2.41}/hippius_sdk/utils.py +0 -0
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
|
|
26
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.
|
29
|
+
__version__ = "0.2.41"
|
30
30
|
__all__ = [
|
31
31
|
"HippiusClient",
|
32
32
|
"IPFSClient",
|
@@ -3,7 +3,8 @@ Main client for the Hippius SDK.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import base64
|
6
|
-
|
6
|
+
import os
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Union, AsyncIterator
|
7
8
|
|
8
9
|
import nacl.secret
|
9
10
|
import nacl.utils
|
@@ -519,7 +520,7 @@ class HippiusClient:
|
|
519
520
|
|
520
521
|
async def s3_publish(
|
521
522
|
self,
|
522
|
-
|
523
|
+
content: Union[str, bytes, os.PathLike],
|
523
524
|
encrypt: bool,
|
524
525
|
seed_phrase: str,
|
525
526
|
subaccount_id: str,
|
@@ -531,15 +532,15 @@ class HippiusClient:
|
|
531
532
|
publish: bool = True,
|
532
533
|
) -> Union[S3PublishResult, S3PublishPin]:
|
533
534
|
"""
|
534
|
-
Publish
|
535
|
+
Publish content to IPFS and the Hippius marketplace in one operation.
|
535
536
|
|
536
537
|
Uses a two-node architecture for optimal performance:
|
537
538
|
1. Uploads to store_node (local) for immediate availability
|
538
539
|
2. Pins to pin_node (remote) for persistence and backup
|
539
540
|
|
540
541
|
Args:
|
541
|
-
|
542
|
-
|
542
|
+
content: Either a file path (str/PathLike) or bytes content to publish
|
543
|
+
file_name: The original file name (required if content is bytes)
|
543
544
|
encrypt: Whether to encrypt the file before uploading
|
544
545
|
seed_phrase: Seed phrase for blockchain transaction signing
|
545
546
|
subaccount_id: The subaccount/account identifier
|
@@ -560,47 +561,51 @@ class HippiusClient:
|
|
560
561
|
ValueError: If encryption is requested but not available
|
561
562
|
"""
|
562
563
|
return await self.ipfs_client.s3_publish(
|
563
|
-
|
564
|
-
encrypt,
|
565
|
-
seed_phrase,
|
566
|
-
subaccount_id,
|
567
|
-
bucket_name,
|
568
|
-
store_node,
|
569
|
-
pin_node,
|
570
|
-
substrate_url,
|
571
|
-
publish,
|
564
|
+
content=content,
|
565
|
+
encrypt=encrypt,
|
566
|
+
seed_phrase=seed_phrase,
|
567
|
+
subaccount_id=subaccount_id,
|
568
|
+
bucket_name=bucket_name,
|
569
|
+
store_node=store_node,
|
570
|
+
pin_node=pin_node,
|
571
|
+
substrate_url=substrate_url,
|
572
|
+
publish=publish,
|
572
573
|
file_name=file_name,
|
573
574
|
)
|
574
575
|
|
575
576
|
async def s3_download(
|
576
577
|
self,
|
577
578
|
cid: str,
|
578
|
-
output_path: str,
|
579
|
-
subaccount_id: str,
|
580
|
-
bucket_name: str,
|
579
|
+
output_path: Optional[str] = None,
|
580
|
+
subaccount_id: Optional[str] = None,
|
581
|
+
bucket_name: Optional[str] = None,
|
581
582
|
auto_decrypt: bool = True,
|
582
583
|
download_node: str = "http://localhost:5001",
|
583
|
-
|
584
|
+
return_bytes: bool = False,
|
585
|
+
streaming: bool = False,
|
586
|
+
) -> Union[S3DownloadResult, bytes, AsyncIterator[bytes]]:
|
584
587
|
"""
|
585
|
-
Download
|
588
|
+
Download content from IPFS with flexible output options and automatic decryption.
|
586
589
|
|
587
|
-
This method
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
- Falls back to client encryption key if key storage is not available
|
592
|
-
- Returns the file in decrypted form if decryption succeeds
|
590
|
+
This method provides multiple output modes:
|
591
|
+
1. File output: Downloads to specified path (default mode)
|
592
|
+
2. Bytes output: Returns decrypted bytes in memory (return_bytes=True)
|
593
|
+
3. Streaming output: Returns raw streaming iterator from IPFS node (streaming=True)
|
593
594
|
|
594
595
|
Args:
|
595
596
|
cid: Content Identifier (CID) of the file to download
|
596
|
-
output_path: Path where the downloaded file will be saved
|
597
|
-
subaccount_id: The subaccount/account identifier
|
598
|
-
bucket_name: The bucket name for key isolation
|
597
|
+
output_path: Path where the downloaded file will be saved (None for bytes/streaming)
|
598
|
+
subaccount_id: The subaccount/account identifier (required for decryption)
|
599
|
+
bucket_name: The bucket name for key isolation (required for decryption)
|
599
600
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
600
601
|
download_node: IPFS node URL for download (default: local node)
|
602
|
+
return_bytes: If True, return bytes instead of saving to file
|
603
|
+
streaming: If True, return raw streaming iterator from IPFS (no decryption)
|
601
604
|
|
602
605
|
Returns:
|
603
|
-
S3DownloadResult:
|
606
|
+
S3DownloadResult: Download info and decryption status (default)
|
607
|
+
bytes: Raw decrypted content when return_bytes=True
|
608
|
+
AsyncIterator[bytes]: Raw streaming iterator when streaming=True
|
604
609
|
|
605
610
|
Raises:
|
606
611
|
HippiusIPFSError: If IPFS download fails
|
@@ -608,5 +613,12 @@ class HippiusClient:
|
|
608
613
|
ValueError: If decryption fails
|
609
614
|
"""
|
610
615
|
return await self.ipfs_client.s3_download(
|
611
|
-
cid,
|
616
|
+
cid=cid,
|
617
|
+
output_path=output_path,
|
618
|
+
subaccount_id=subaccount_id,
|
619
|
+
bucket_name=bucket_name,
|
620
|
+
auto_decrypt=auto_decrypt,
|
621
|
+
download_node=download_node,
|
622
|
+
return_bytes=return_bytes,
|
623
|
+
streaming=streaming,
|
612
624
|
)
|
@@ -13,7 +13,7 @@ import shutil
|
|
13
13
|
import tempfile
|
14
14
|
import time
|
15
15
|
import uuid
|
16
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Union, AsyncIterator
|
17
17
|
|
18
18
|
import httpx
|
19
19
|
from pydantic import BaseModel
|
@@ -1967,7 +1967,7 @@ class IPFSClient:
|
|
1967
1967
|
|
1968
1968
|
async def s3_publish(
|
1969
1969
|
self,
|
1970
|
-
|
1970
|
+
content: Union[str, bytes, os.PathLike],
|
1971
1971
|
encrypt: bool,
|
1972
1972
|
seed_phrase: str,
|
1973
1973
|
subaccount_id: str,
|
@@ -1979,7 +1979,7 @@ class IPFSClient:
|
|
1979
1979
|
file_name: str = None,
|
1980
1980
|
) -> Union[S3PublishResult, S3PublishPin]:
|
1981
1981
|
"""
|
1982
|
-
Publish
|
1982
|
+
Publish content to IPFS and the Hippius marketplace in one operation.
|
1983
1983
|
|
1984
1984
|
This method uses a two-node architecture for optimal performance:
|
1985
1985
|
1. Uploads to store_node (local) for immediate availability
|
@@ -1992,8 +1992,8 @@ class IPFSClient:
|
|
1992
1992
|
- Always uses the most recent key for an account+bucket combination
|
1993
1993
|
|
1994
1994
|
Args:
|
1995
|
-
|
1996
|
-
encrypt: Whether to encrypt the
|
1995
|
+
content: Either a file path (str/PathLike) or bytes content to publish
|
1996
|
+
encrypt: Whether to encrypt the content before uploading
|
1997
1997
|
seed_phrase: Seed phrase for blockchain transaction signing
|
1998
1998
|
subaccount_id: The subaccount/account identifier
|
1999
1999
|
bucket_name: The bucket name for key isolation
|
@@ -2001,25 +2001,47 @@ class IPFSClient:
|
|
2001
2001
|
pin_node: IPFS node URL for backup pinning (default: remote service)
|
2002
2002
|
substrate_url: the substrate url to connect to for the storage request
|
2003
2003
|
publish: Whether to publish to blockchain (True) or just upload to IPFS (False)
|
2004
|
-
file_name: The original file name
|
2004
|
+
file_name: The original file name (required if content is bytes)
|
2005
2005
|
|
2006
2006
|
Returns:
|
2007
2007
|
S3PublishResult: Object containing CID, file info, and transaction hash when publish=True
|
2008
|
-
S3PublishPin: Object containing CID, subaccount,
|
2008
|
+
S3PublishPin: Object containing CID, subaccount, content info, pin_node, substrate_url when publish=False
|
2009
2009
|
|
2010
2010
|
Raises:
|
2011
2011
|
HippiusIPFSError: If IPFS operations (add or pin) fail
|
2012
2012
|
HippiusSubstrateError: If substrate call fails
|
2013
|
-
FileNotFoundError: If the file doesn't exist
|
2014
|
-
ValueError: If encryption is requested but not available
|
2013
|
+
FileNotFoundError: If the file doesn't exist (when content is a path)
|
2014
|
+
ValueError: If encryption is requested but not available, or if file_name is missing for bytes content
|
2015
2015
|
"""
|
2016
|
-
#
|
2017
|
-
|
2018
|
-
|
2016
|
+
# Determine if content is a file path or bytes
|
2017
|
+
is_file_path = isinstance(content, (str, os.PathLike))
|
2018
|
+
|
2019
|
+
if is_file_path:
|
2020
|
+
# Handle file path input
|
2021
|
+
file_path = str(content)
|
2022
|
+
if not os.path.exists(file_path):
|
2023
|
+
raise FileNotFoundError(f"File {file_path} not found")
|
2024
|
+
|
2025
|
+
# Get file info
|
2026
|
+
filename = file_name or os.path.basename(file_path)
|
2027
|
+
size_bytes = os.path.getsize(file_path)
|
2028
|
+
|
2029
|
+
# Read file content into memory
|
2030
|
+
with open(file_path, "rb") as f:
|
2031
|
+
content_bytes = f.read()
|
2032
|
+
else:
|
2033
|
+
# Handle bytes input
|
2034
|
+
if not isinstance(content, bytes):
|
2035
|
+
raise ValueError(
|
2036
|
+
f"Content must be str, PathLike, or bytes, got {type(content)}"
|
2037
|
+
)
|
2019
2038
|
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2039
|
+
if not file_name:
|
2040
|
+
raise ValueError("file_name is required when content is bytes")
|
2041
|
+
|
2042
|
+
filename = file_name
|
2043
|
+
content_bytes = content
|
2044
|
+
size_bytes = len(content_bytes)
|
2023
2045
|
|
2024
2046
|
# Handle encryption if requested with automatic key management
|
2025
2047
|
encryption_key_used = None
|
@@ -2057,19 +2079,11 @@ class IPFSClient:
|
|
2057
2079
|
encryption_key_bytes = base64.b64decode(new_key_b64)
|
2058
2080
|
encryption_key_used = new_key_b64
|
2059
2081
|
|
2060
|
-
# Read file content into memory
|
2061
|
-
with open(file_path, "rb") as f:
|
2062
|
-
file_data = f.read()
|
2063
|
-
|
2064
2082
|
# Encrypt the data using the key from key storage
|
2065
2083
|
import nacl.secret
|
2066
2084
|
|
2067
2085
|
box = nacl.secret.SecretBox(encryption_key_bytes)
|
2068
|
-
|
2069
|
-
|
2070
|
-
# Overwrite the original file with encrypted data
|
2071
|
-
with open(file_path, "wb") as f:
|
2072
|
-
f.write(encrypted_data)
|
2086
|
+
content_bytes = box.encrypt(content_bytes)
|
2073
2087
|
else:
|
2074
2088
|
# Fallback to the original encryption system if key_storage is not available
|
2075
2089
|
if not self.encryption_available:
|
@@ -2077,16 +2091,8 @@ class IPFSClient:
|
|
2077
2091
|
"Encryption requested but not available. Either install key storage with 'pip install hippius_sdk[key_storage]' or configure an encryption key with 'hippius keygen --save'"
|
2078
2092
|
)
|
2079
2093
|
|
2080
|
-
# Read file content into memory
|
2081
|
-
with open(file_path, "rb") as f:
|
2082
|
-
file_data = f.read()
|
2083
|
-
|
2084
2094
|
# Encrypt the data using the client's encryption key
|
2085
|
-
|
2086
|
-
|
2087
|
-
# Overwrite the original file with encrypted data
|
2088
|
-
with open(file_path, "wb") as f:
|
2089
|
-
f.write(encrypted_data)
|
2095
|
+
content_bytes = self.encrypt_data(content_bytes)
|
2090
2096
|
|
2091
2097
|
# Store the encryption key for the result
|
2092
2098
|
encryption_key_used = (
|
@@ -2098,15 +2104,15 @@ class IPFSClient:
|
|
2098
2104
|
# Step 1: Upload to store_node (local) for immediate availability
|
2099
2105
|
try:
|
2100
2106
|
store_client = AsyncIPFSClient(api_url=store_node)
|
2101
|
-
result = await store_client.
|
2102
|
-
|
2103
|
-
|
2107
|
+
result = await store_client.add_bytes(
|
2108
|
+
content_bytes,
|
2109
|
+
filename=filename,
|
2104
2110
|
)
|
2105
2111
|
cid = result["Hash"]
|
2106
|
-
logger.info(f"
|
2112
|
+
logger.info(f"Content uploaded to store node {store_node} with CID: {cid}")
|
2107
2113
|
except Exception as e:
|
2108
2114
|
raise HippiusIPFSError(
|
2109
|
-
f"Failed to upload
|
2115
|
+
f"Failed to upload content to store node {store_node}: {str(e)}"
|
2110
2116
|
)
|
2111
2117
|
|
2112
2118
|
# Step 2: Pin to pin_node (remote) for persistence and backup
|
@@ -2116,7 +2122,7 @@ class IPFSClient:
|
|
2116
2122
|
logger.info(f"File pinned to backup node {pin_node}")
|
2117
2123
|
except Exception as e:
|
2118
2124
|
raise HippiusIPFSError(
|
2119
|
-
f"Failed to pin
|
2125
|
+
f"Failed to pin content to pin node {pin_node}: {str(e)}"
|
2120
2126
|
)
|
2121
2127
|
|
2122
2128
|
# Conditionally publish to substrate marketplace based on publish flag
|
@@ -2174,7 +2180,7 @@ class IPFSClient:
|
|
2174
2180
|
return S3PublishPin(
|
2175
2181
|
cid=cid,
|
2176
2182
|
subaccount=subaccount_id,
|
2177
|
-
file_path=
|
2183
|
+
file_path=filename,
|
2178
2184
|
pin_node=pin_node,
|
2179
2185
|
substrate_url=substrate_url,
|
2180
2186
|
)
|
@@ -2182,44 +2188,75 @@ class IPFSClient:
|
|
2182
2188
|
async def s3_download(
|
2183
2189
|
self,
|
2184
2190
|
cid: str,
|
2185
|
-
output_path: str,
|
2186
|
-
subaccount_id: str,
|
2187
|
-
bucket_name: str,
|
2191
|
+
output_path: Optional[str] = None,
|
2192
|
+
subaccount_id: Optional[str] = None,
|
2193
|
+
bucket_name: Optional[str] = None,
|
2188
2194
|
auto_decrypt: bool = True,
|
2189
2195
|
download_node: str = "http://localhost:5001",
|
2190
|
-
|
2196
|
+
return_bytes: bool = False,
|
2197
|
+
streaming: bool = False,
|
2198
|
+
) -> Union[S3DownloadResult, bytes, AsyncIterator[bytes]]:
|
2191
2199
|
"""
|
2192
|
-
Download
|
2200
|
+
Download content from IPFS with flexible output options and automatic decryption.
|
2193
2201
|
|
2194
|
-
This method
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
- Falls back to client encryption key if key storage is not available
|
2199
|
-
- Returns the file in decrypted form if decryption succeeds
|
2202
|
+
This method provides multiple output modes:
|
2203
|
+
1. File output: Downloads to specified path (default mode)
|
2204
|
+
2. Bytes output: Returns decrypted bytes in memory (return_bytes=True)
|
2205
|
+
3. Streaming output: Returns raw streaming iterator from IPFS node (streaming=True)
|
2200
2206
|
|
2201
2207
|
Args:
|
2202
2208
|
cid: Content Identifier (CID) of the file to download
|
2203
|
-
output_path: Path where the downloaded file will be saved
|
2204
|
-
subaccount_id: The subaccount/account identifier
|
2205
|
-
bucket_name: The bucket name for key isolation
|
2209
|
+
output_path: Path where the downloaded file will be saved (None for bytes/streaming)
|
2210
|
+
subaccount_id: The subaccount/account identifier (required for decryption)
|
2211
|
+
bucket_name: The bucket name for key isolation (required for decryption)
|
2206
2212
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
2207
2213
|
download_node: IPFS node URL for download (default: local node)
|
2214
|
+
return_bytes: If True, return bytes instead of saving to file
|
2215
|
+
streaming: If True, return raw streaming iterator from IPFS (no decryption)
|
2208
2216
|
|
2209
2217
|
Returns:
|
2210
|
-
S3DownloadResult:
|
2218
|
+
S3DownloadResult: Download info and decryption status (default)
|
2219
|
+
bytes: Raw decrypted content when return_bytes=True
|
2220
|
+
AsyncIterator[bytes]: Raw streaming iterator when streaming=True
|
2211
2221
|
|
2212
2222
|
Raises:
|
2213
2223
|
HippiusIPFSError: If IPFS download fails
|
2214
2224
|
FileNotFoundError: If the output directory doesn't exist
|
2215
|
-
ValueError: If decryption fails
|
2225
|
+
ValueError: If decryption fails or invalid parameter combinations
|
2216
2226
|
"""
|
2227
|
+
# Validate parameter combinations
|
2228
|
+
if streaming and return_bytes:
|
2229
|
+
raise ValueError("Cannot specify both streaming and return_bytes")
|
2230
|
+
|
2231
|
+
if streaming and (auto_decrypt or subaccount_id or bucket_name):
|
2232
|
+
logger.warning(
|
2233
|
+
"streaming=True ignores decryption parameters - returns raw encrypted stream"
|
2234
|
+
)
|
2235
|
+
|
2236
|
+
if streaming:
|
2237
|
+
# Return raw streaming iterator from IPFS node - no processing
|
2238
|
+
# _get_ipfs_stream is an async generator, return it directly
|
2239
|
+
async def streaming_wrapper():
|
2240
|
+
async for chunk in self._get_ipfs_stream(cid, download_node):
|
2241
|
+
yield chunk
|
2242
|
+
return streaming_wrapper()
|
2243
|
+
|
2217
2244
|
start_time = time.time()
|
2218
2245
|
|
2246
|
+
# Validate required parameters for decryption
|
2247
|
+
if auto_decrypt and (not subaccount_id or not bucket_name):
|
2248
|
+
raise ValueError(
|
2249
|
+
"subaccount_id and bucket_name are required when auto_decrypt=True"
|
2250
|
+
)
|
2251
|
+
|
2252
|
+
if not return_bytes and not output_path:
|
2253
|
+
raise ValueError("output_path is required when not using return_bytes mode")
|
2254
|
+
|
2219
2255
|
# Download the file directly into memory from the specified download_node
|
2220
2256
|
try:
|
2221
|
-
# Create parent directories if they don't exist
|
2222
|
-
|
2257
|
+
# Create parent directories if they don't exist (only for file output mode)
|
2258
|
+
if not return_bytes:
|
2259
|
+
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
2223
2260
|
|
2224
2261
|
download_client = AsyncIPFSClient(api_url=download_node)
|
2225
2262
|
|
@@ -2357,20 +2394,58 @@ class IPFSClient:
|
|
2357
2394
|
elif not decryption_attempted:
|
2358
2395
|
logger.debug("No decryption attempted - no keys available")
|
2359
2396
|
|
2360
|
-
#
|
2361
|
-
|
2362
|
-
|
2397
|
+
# Handle output based on mode
|
2398
|
+
if return_bytes:
|
2399
|
+
# Return bytes directly
|
2400
|
+
return final_data
|
2401
|
+
else:
|
2402
|
+
# Write the final data (encrypted or decrypted) to disk once
|
2403
|
+
with open(output_path, "wb") as f:
|
2404
|
+
f.write(final_data)
|
2363
2405
|
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2406
|
+
# Get final file info
|
2407
|
+
size_bytes = len(final_data)
|
2408
|
+
elapsed_time = time.time() - start_time
|
2367
2409
|
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
2375
|
-
|
2376
|
-
|
2410
|
+
return S3DownloadResult(
|
2411
|
+
cid=cid,
|
2412
|
+
output_path=output_path,
|
2413
|
+
size_bytes=size_bytes,
|
2414
|
+
size_formatted=self.format_size(size_bytes),
|
2415
|
+
elapsed_seconds=round(elapsed_time, 2),
|
2416
|
+
decrypted=decrypted,
|
2417
|
+
encryption_key=encryption_key_used,
|
2418
|
+
)
|
2419
|
+
|
2420
|
+
async def _get_ipfs_stream(
|
2421
|
+
self, cid: str, download_node: str
|
2422
|
+
) -> AsyncIterator[bytes]:
|
2423
|
+
"""
|
2424
|
+
Get a raw streaming iterator from IPFS node.
|
2425
|
+
|
2426
|
+
This method returns the raw encrypted stream directly from the IPFS node
|
2427
|
+
without any processing, decryption, or temporary file operations.
|
2428
|
+
|
2429
|
+
Args:
|
2430
|
+
cid: Content Identifier (CID) of the file to stream
|
2431
|
+
download_node: IPFS node URL for download
|
2432
|
+
|
2433
|
+
Returns:
|
2434
|
+
AsyncIterator[bytes]: Raw streaming iterator from IPFS
|
2435
|
+
|
2436
|
+
Raises:
|
2437
|
+
HippiusIPFSError: If IPFS stream fails
|
2438
|
+
"""
|
2439
|
+
try:
|
2440
|
+
download_client = AsyncIPFSClient(api_url=download_node)
|
2441
|
+
download_url = f"{download_node.rstrip('/')}/api/v0/cat?arg={cid}"
|
2442
|
+
|
2443
|
+
async with download_client.client.stream("POST", download_url) as response:
|
2444
|
+
response.raise_for_status()
|
2445
|
+
logger.info(f"Started streaming from {download_node} for CID: {cid}")
|
2446
|
+
|
2447
|
+
async for chunk in response.aiter_bytes(chunk_size=8192):
|
2448
|
+
yield chunk
|
2449
|
+
|
2450
|
+
except Exception as e:
|
2451
|
+
raise HippiusIPFSError(f"Failed to stream from {download_node}: {str(e)}")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|