hippius 0.2.20__py3-none-any.whl → 0.2.21__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.20.dist-info → hippius-0.2.21.dist-info}/METADATA +1 -1
- {hippius-0.2.20.dist-info → hippius-0.2.21.dist-info}/RECORD +8 -8
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/client.py +24 -6
- hippius_sdk/ipfs.py +124 -88
- hippius_sdk/key_storage.py +3 -3
- {hippius-0.2.20.dist-info → hippius-0.2.21.dist-info}/WHEEL +0 -0
- {hippius-0.2.20.dist-info → hippius-0.2.21.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,10 @@
|
|
1
|
-
hippius_sdk/__init__.py,sha256=
|
1
|
+
hippius_sdk/__init__.py,sha256=GR_6lzziCmPq3cvSasIrT3n2Px3l6Jv1OXAEXDZ0fX0,1474
|
2
2
|
hippius_sdk/cli.py,sha256=aqKOYSBSWt7UhcpFt7wf9yIPJ3bznpsJ6ehOnuZ4usI,18235
|
3
3
|
hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
|
4
4
|
hippius_sdk/cli_handlers.py,sha256=TQNE9os87gRzRKLEO-SIwhFnBtEFMiaSESv-Bu7omfo,128909
|
5
5
|
hippius_sdk/cli_parser.py,sha256=z7UvgWvvy04ey-R56qZiCqYc_9RaNq1rVDkQyXoK3JU,21100
|
6
6
|
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
|
-
hippius_sdk/client.py,sha256=
|
7
|
+
hippius_sdk/client.py,sha256=ktlv-s7H53h4zWcdz1EYf6fAqioohcFAxhRDKtljlEg,22252
|
8
8
|
hippius_sdk/config.py,sha256=Hf_aUYzG9ylzqauA_ABUSSB5mBTYbp-VtB36VQt2XDw,21981
|
9
9
|
hippius_sdk/db/README.md,sha256=okDeI1qgkaZqXSlJ8L0xIE4UpuxO-qEGPIbXUvSHQjU,2030
|
10
10
|
hippius_sdk/db/env.db.template,sha256=_6hEC3IvkzCDOAzG1_yJUKRUfCTMciNaJUicZpMCat4,217
|
@@ -12,12 +12,12 @@ hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql,sha256=mi
|
|
12
12
|
hippius_sdk/db/setup_database.sh,sha256=bDeIiTnMuX0AYCdefAfEI1CyXho6A6kLzufsDZFSXpE,3118
|
13
13
|
hippius_sdk/db_utils.py,sha256=-x0rbN0as7Tn3PJPZBYCgreZe52FLH40ppA1TLxsg90,1851
|
14
14
|
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
15
|
-
hippius_sdk/ipfs.py,sha256=
|
15
|
+
hippius_sdk/ipfs.py,sha256=QjqBYnhqzAiST3CNCCLxwPxEMGaV2iLwk_IBTp740wg,93188
|
16
16
|
hippius_sdk/ipfs_core.py,sha256=eOOgLoyP9mvwndnCjldnTc7z94ImYCXY3nm7JU3e_Mo,12676
|
17
|
-
hippius_sdk/key_storage.py,sha256
|
17
|
+
hippius_sdk/key_storage.py,sha256=oxfRRQXu8XVncUjhKJJ8rsc81JAkaj9MqZEwQa96idc,9474
|
18
18
|
hippius_sdk/substrate.py,sha256=AqfQNl5Qv_s6roOESSdwleX0-VGyu5sh3m5-dYZp5ek,49079
|
19
19
|
hippius_sdk/utils.py,sha256=rJ611yvwKSyiBpYU3w-SuyQxoghMGU-ePuslrPv5H5g,7388
|
20
|
-
hippius-0.2.
|
21
|
-
hippius-0.2.
|
22
|
-
hippius-0.2.
|
23
|
-
hippius-0.2.
|
20
|
+
hippius-0.2.21.dist-info/METADATA,sha256=iBEURRYrrokbmCufUVrac5PlnWErQ72on4cycxevlqU,30088
|
21
|
+
hippius-0.2.21.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
22
|
+
hippius-0.2.21.dist-info/entry_points.txt,sha256=bFAZjW3vndretf9-8s587jA2ebMVI7puhn_lVs8jPc8,149
|
23
|
+
hippius-0.2.21.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, 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.21"
|
30
30
|
__all__ = [
|
31
31
|
"HippiusClient",
|
32
32
|
"IPFSClient",
|
hippius_sdk/client.py
CHANGED
@@ -512,15 +512,26 @@ class HippiusClient:
|
|
512
512
|
)
|
513
513
|
|
514
514
|
async def s3_publish(
|
515
|
-
self,
|
515
|
+
self,
|
516
|
+
file_path: str,
|
517
|
+
encrypt: bool,
|
518
|
+
seed_phrase: str,
|
519
|
+
store_node: str = "http://localhost:5001",
|
520
|
+
pin_node: str = "https://store.hippius.network"
|
516
521
|
) -> S3PublishResult:
|
517
522
|
"""
|
518
523
|
Publish a file to IPFS and the Hippius marketplace in one operation.
|
519
524
|
|
525
|
+
Uses a two-node architecture for optimal performance:
|
526
|
+
1. Uploads to store_node (local) for immediate availability
|
527
|
+
2. Pins to pin_node (remote) for persistence and backup
|
528
|
+
|
520
529
|
Args:
|
521
530
|
file_path: Path to the file to publish
|
522
531
|
encrypt: Whether to encrypt the file before uploading
|
523
532
|
seed_phrase: Seed phrase for blockchain transaction signing
|
533
|
+
store_node: IPFS node URL for initial upload (default: local node)
|
534
|
+
pin_node: IPFS node URL for backup pinning (default: remote service)
|
524
535
|
|
525
536
|
Returns:
|
526
537
|
S3PublishResult: Object containing CID, file info, and transaction hash
|
@@ -531,16 +542,22 @@ class HippiusClient:
|
|
531
542
|
FileNotFoundError: If the file doesn't exist
|
532
543
|
ValueError: If encryption is requested but not available
|
533
544
|
"""
|
534
|
-
return await self.ipfs_client.s3_publish(file_path, encrypt, seed_phrase)
|
545
|
+
return await self.ipfs_client.s3_publish(file_path, encrypt, seed_phrase, store_node, pin_node)
|
535
546
|
|
536
547
|
async def s3_download(
|
537
|
-
self,
|
548
|
+
self,
|
549
|
+
cid: str,
|
550
|
+
output_path: str,
|
551
|
+
seed_phrase: str,
|
552
|
+
auto_decrypt: bool = True,
|
553
|
+
download_node: str = "http://localhost:5001"
|
538
554
|
) -> S3DownloadResult:
|
539
555
|
"""
|
540
556
|
Download a file from IPFS with automatic decryption.
|
541
557
|
|
542
|
-
This method
|
543
|
-
|
558
|
+
This method uses the download_node for immediate availability and automatically
|
559
|
+
manages decryption keys per seed phrase:
|
560
|
+
- Downloads the file from the specified download_node (local by default)
|
544
561
|
- If auto_decrypt=True, attempts to decrypt using stored keys for the seed phrase
|
545
562
|
- Falls back to client encryption key if key storage is not available
|
546
563
|
- Returns the file in decrypted form if decryption succeeds
|
@@ -550,6 +567,7 @@ class HippiusClient:
|
|
550
567
|
output_path: Path where the downloaded file will be saved
|
551
568
|
seed_phrase: Seed phrase to use for retrieving decryption keys
|
552
569
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
570
|
+
download_node: IPFS node URL for download (default: local node)
|
553
571
|
|
554
572
|
Returns:
|
555
573
|
S3DownloadResult: Object containing download info and decryption status
|
@@ -559,4 +577,4 @@ class HippiusClient:
|
|
559
577
|
FileNotFoundError: If the output directory doesn't exist
|
560
578
|
ValueError: If decryption fails
|
561
579
|
"""
|
562
|
-
return await self.ipfs_client.s3_download(cid, output_path, seed_phrase, auto_decrypt)
|
580
|
+
return await self.ipfs_client.s3_download(cid, output_path, seed_phrase, auto_decrypt, download_node)
|
hippius_sdk/ipfs.py
CHANGED
@@ -13,17 +13,18 @@ import tempfile
|
|
13
13
|
import time
|
14
14
|
import uuid
|
15
15
|
from typing import Any, Callable, Dict, List, Optional
|
16
|
-
|
17
|
-
generate_and_store_key_for_seed,
|
18
|
-
get_key_for_seed,
|
19
|
-
is_key_storage_enabled,
|
20
|
-
)
|
16
|
+
|
21
17
|
import httpx
|
22
18
|
from pydantic import BaseModel
|
23
19
|
|
24
20
|
from hippius_sdk.config import get_config_value, get_encryption_key
|
25
21
|
from hippius_sdk.errors import HippiusIPFSError, HippiusSubstrateError
|
26
22
|
from hippius_sdk.ipfs_core import AsyncIPFSClient
|
23
|
+
from hippius_sdk.key_storage import (
|
24
|
+
generate_and_store_key_for_seed,
|
25
|
+
get_key_for_seed,
|
26
|
+
is_key_storage_enabled,
|
27
|
+
)
|
27
28
|
from hippius_sdk.substrate import FileInput, SubstrateClient
|
28
29
|
from hippius_sdk.utils import format_cid, format_size
|
29
30
|
|
@@ -1805,9 +1806,6 @@ class IPFSClient:
|
|
1805
1806
|
"""
|
1806
1807
|
print(f"Starting deletion process for metadata CID: {metadata_cid}")
|
1807
1808
|
|
1808
|
-
# Try to download and process metadata file and chunks
|
1809
|
-
ipfs_failure = False
|
1810
|
-
metadata_error = False
|
1811
1809
|
chunks = []
|
1812
1810
|
|
1813
1811
|
try:
|
@@ -1929,11 +1927,21 @@ class IPFSClient:
|
|
1929
1927
|
return True
|
1930
1928
|
|
1931
1929
|
async def s3_publish(
|
1932
|
-
self,
|
1930
|
+
self,
|
1931
|
+
file_path: str,
|
1932
|
+
encrypt: bool,
|
1933
|
+
seed_phrase: str,
|
1934
|
+
store_node: str = "http://localhost:5001",
|
1935
|
+
pin_node: str = "https://store.hippius.network"
|
1933
1936
|
) -> S3PublishResult:
|
1934
1937
|
"""
|
1935
1938
|
Publish a file to IPFS and the Hippius marketplace in one operation.
|
1936
1939
|
|
1940
|
+
This method uses a two-node architecture for optimal performance:
|
1941
|
+
1. Uploads to store_node (local) for immediate availability
|
1942
|
+
2. Pins to pin_node (remote) for persistence and backup
|
1943
|
+
3. Publishes to substrate marketplace
|
1944
|
+
|
1937
1945
|
This method automatically manages encryption keys per seed phrase:
|
1938
1946
|
- If encrypt=True, it will get or generate an encryption key for the seed phrase
|
1939
1947
|
- Keys are stored in PostgreSQL and versioned (never deleted)
|
@@ -1943,6 +1951,8 @@ class IPFSClient:
|
|
1943
1951
|
file_path: Path to the file to publish
|
1944
1952
|
encrypt: Whether to encrypt the file before uploading
|
1945
1953
|
seed_phrase: Seed phrase for blockchain transaction signing
|
1954
|
+
store_node: IPFS node URL for initial upload (default: local node)
|
1955
|
+
pin_node: IPFS node URL for backup pinning (default: remote service)
|
1946
1956
|
|
1947
1957
|
Returns:
|
1948
1958
|
S3PublishResult: Object containing CID, file info, and transaction hash
|
@@ -2026,18 +2036,22 @@ class IPFSClient:
|
|
2026
2036
|
else None
|
2027
2037
|
)
|
2028
2038
|
|
2029
|
-
#
|
2039
|
+
# Step 1: Upload to store_node (local) for immediate availability
|
2030
2040
|
try:
|
2031
|
-
|
2041
|
+
store_client = AsyncIPFSClient(api_url=store_node)
|
2042
|
+
result = await store_client.add_file(file_path)
|
2032
2043
|
cid = result["Hash"]
|
2044
|
+
logger.info(f"File uploaded to store node {store_node} with CID: {cid}")
|
2033
2045
|
except Exception as e:
|
2034
|
-
raise HippiusIPFSError(f"Failed to
|
2046
|
+
raise HippiusIPFSError(f"Failed to upload file to store node {store_node}: {str(e)}")
|
2035
2047
|
|
2036
|
-
#
|
2048
|
+
# Step 2: Pin to pin_node (remote) for persistence and backup
|
2037
2049
|
try:
|
2038
|
-
|
2050
|
+
pin_client = AsyncIPFSClient(api_url=pin_node)
|
2051
|
+
await pin_client.pin(cid)
|
2052
|
+
logger.info(f"File pinned to backup node {pin_node}")
|
2039
2053
|
except Exception as e:
|
2040
|
-
raise HippiusIPFSError(f"Failed to pin file to
|
2054
|
+
raise HippiusIPFSError(f"Failed to pin file to store node {store_node}: {str(e)}")
|
2041
2055
|
|
2042
2056
|
# Publish to substrate marketplace
|
2043
2057
|
try:
|
@@ -2087,13 +2101,19 @@ class IPFSClient:
|
|
2087
2101
|
)
|
2088
2102
|
|
2089
2103
|
async def s3_download(
|
2090
|
-
self,
|
2104
|
+
self,
|
2105
|
+
cid: str,
|
2106
|
+
output_path: str,
|
2107
|
+
seed_phrase: str,
|
2108
|
+
auto_decrypt: bool = True,
|
2109
|
+
download_node: str = "http://localhost:5001"
|
2091
2110
|
) -> S3DownloadResult:
|
2092
2111
|
"""
|
2093
2112
|
Download a file from IPFS with automatic decryption.
|
2094
2113
|
|
2095
|
-
This method
|
2096
|
-
|
2114
|
+
This method uses the download_node for immediate availability and automatically
|
2115
|
+
manages decryption keys per seed phrase:
|
2116
|
+
- Downloads the file from the specified download_node (local by default)
|
2097
2117
|
- If auto_decrypt=True, attempts to decrypt using stored keys for the seed phrase
|
2098
2118
|
- Falls back to client encryption key if key storage is not available
|
2099
2119
|
- Returns the file in decrypted form if decryption succeeds
|
@@ -2103,6 +2123,7 @@ class IPFSClient:
|
|
2103
2123
|
output_path: Path where the downloaded file will be saved
|
2104
2124
|
seed_phrase: Seed phrase to use for retrieving decryption keys
|
2105
2125
|
auto_decrypt: Whether to attempt automatic decryption (default: True)
|
2126
|
+
download_node: IPFS node URL for download (default: local node)
|
2106
2127
|
|
2107
2128
|
Returns:
|
2108
2129
|
S3DownloadResult: Object containing download info and decryption status
|
@@ -2114,24 +2135,25 @@ class IPFSClient:
|
|
2114
2135
|
"""
|
2115
2136
|
start_time = time.time()
|
2116
2137
|
|
2117
|
-
# Download the file
|
2138
|
+
# Download the file directly from the specified download_node
|
2118
2139
|
try:
|
2119
2140
|
# Create parent directories if they don't exist
|
2120
2141
|
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
2121
2142
|
|
2122
|
-
|
2123
|
-
download_result = await self.download_file(
|
2124
|
-
cid=cid,
|
2125
|
-
output_path=output_path,
|
2126
|
-
skip_directory_check=False,
|
2127
|
-
seed_phrase=seed_phrase,
|
2128
|
-
)
|
2143
|
+
download_client = AsyncIPFSClient(api_url=download_node)
|
2129
2144
|
|
2130
|
-
|
2131
|
-
|
2145
|
+
download_url = f"{download_node.rstrip('/')}/api/v0/cat?arg={cid}"
|
2146
|
+
async with download_client.client.stream("POST", download_url) as response:
|
2147
|
+
response.raise_for_status()
|
2148
|
+
|
2149
|
+
with open(output_path, "wb") as f:
|
2150
|
+
async for chunk in response.aiter_bytes(chunk_size=8192):
|
2151
|
+
f.write(chunk)
|
2152
|
+
|
2153
|
+
logger.info(f"File downloaded from {download_node} with CID: {cid}")
|
2132
2154
|
|
2133
2155
|
except Exception as e:
|
2134
|
-
raise HippiusIPFSError(f"Failed to download file from
|
2156
|
+
raise HippiusIPFSError(f"Failed to download file from {download_node}: {str(e)}")
|
2135
2157
|
|
2136
2158
|
# Get file info after download
|
2137
2159
|
size_bytes = os.path.getsize(output_path)
|
@@ -2154,77 +2176,91 @@ class IPFSClient:
|
|
2154
2176
|
with open(output_path, "rb") as f:
|
2155
2177
|
file_data = f.read()
|
2156
2178
|
|
2157
|
-
|
2158
|
-
|
2179
|
+
# Check if file is empty - this indicates a problem
|
2180
|
+
if len(file_data) == 0:
|
2181
|
+
logger.error(f"Downloaded file is empty (0 bytes) for CID: {cid}")
|
2182
|
+
raise HippiusIPFSError(
|
2183
|
+
f"File not available: Downloaded 0 bytes for CID {cid}. "
|
2184
|
+
f"File may not exist on download node {download_node}. "
|
2185
|
+
f"Download URL: {download_url}"
|
2186
|
+
)
|
2187
|
+
elif len(file_data) < 40: # PyNaCl encrypted data is at least 40 bytes (24-byte nonce + 16-byte auth tag + data)
|
2188
|
+
logger.info(f"File too small to be encrypted ({len(file_data)} bytes), treating as plaintext")
|
2189
|
+
decrypted = False
|
2190
|
+
encryption_key_used = None
|
2191
|
+
else:
|
2192
|
+
# File has content, attempt decryption if requested
|
2193
|
+
decryption_attempted = False
|
2194
|
+
decryption_successful = False
|
2159
2195
|
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
|
2196
|
+
if key_storage_available:
|
2197
|
+
# Try to get the encryption key for this seed phrase
|
2198
|
+
try:
|
2199
|
+
existing_key_b64 = await get_key_for_seed(seed_phrase)
|
2164
2200
|
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2201
|
+
if existing_key_b64:
|
2202
|
+
logger.debug("Found encryption key for seed phrase, attempting decryption")
|
2203
|
+
decryption_attempted = True
|
2204
|
+
encryption_key_used = existing_key_b64
|
2169
2205
|
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2206
|
+
# Attempt decryption with the stored key
|
2207
|
+
try:
|
2208
|
+
import nacl.secret
|
2173
2209
|
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2210
|
+
encryption_key_bytes = base64.b64decode(existing_key_b64)
|
2211
|
+
box = nacl.secret.SecretBox(encryption_key_bytes)
|
2212
|
+
decrypted_data = box.decrypt(file_data)
|
2177
2213
|
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2214
|
+
# Write the decrypted data back to the file
|
2215
|
+
with open(output_path, "wb") as f:
|
2216
|
+
f.write(decrypted_data)
|
2181
2217
|
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2218
|
+
decryption_successful = True
|
2219
|
+
decrypted = True
|
2220
|
+
size_bytes = len(decrypted_data) # Update size to decrypted size
|
2221
|
+
logger.info("Successfully decrypted file using stored key")
|
2186
2222
|
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2223
|
+
except Exception as decrypt_error:
|
2224
|
+
logger.debug(f"Decryption failed with stored key: {decrypt_error}")
|
2225
|
+
# Continue to try fallback decryption
|
2226
|
+
else:
|
2227
|
+
logger.debug("No encryption key found for seed phrase")
|
2192
2228
|
|
2193
|
-
|
2194
|
-
|
2229
|
+
except Exception as e:
|
2230
|
+
logger.debug(f"Error retrieving key from storage: {e}")
|
2195
2231
|
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2232
|
+
# If key storage decryption failed or wasn't available, try client encryption key
|
2233
|
+
if not decryption_successful and self.encryption_available:
|
2234
|
+
logger.debug("Attempting decryption with client encryption key")
|
2235
|
+
decryption_attempted = True
|
2200
2236
|
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2237
|
+
try:
|
2238
|
+
decrypted_data = self.decrypt_data(file_data)
|
2239
|
+
|
2240
|
+
# Write the decrypted data back to the file
|
2241
|
+
with open(output_path, "wb") as f:
|
2242
|
+
f.write(decrypted_data)
|
2243
|
+
|
2244
|
+
decryption_successful = True
|
2245
|
+
decrypted = True
|
2246
|
+
size_bytes = len(decrypted_data) # Update size to decrypted size
|
2247
|
+
|
2248
|
+
# Store the encryption key for the result
|
2249
|
+
encryption_key_used = (
|
2250
|
+
base64.b64encode(self.encryption_key).decode("utf-8")
|
2251
|
+
if self.encryption_key
|
2252
|
+
else None
|
2253
|
+
)
|
2254
|
+
logger.info("Successfully decrypted file using client encryption key")
|
2219
2255
|
|
2220
|
-
|
2221
|
-
|
2256
|
+
except Exception as decrypt_error:
|
2257
|
+
logger.debug(f"Decryption failed with client key: {decrypt_error}")
|
2222
2258
|
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2259
|
+
# Log final decryption status
|
2260
|
+
if decryption_attempted and not decryption_successful:
|
2261
|
+
logger.info("File may not be encrypted or decryption keys don't match")
|
2262
|
+
elif not decryption_attempted:
|
2263
|
+
logger.debug("No decryption attempted - no keys available")
|
2228
2264
|
|
2229
2265
|
return S3DownloadResult(
|
2230
2266
|
cid=cid,
|
hippius_sdk/key_storage.py
CHANGED
@@ -234,7 +234,7 @@ _default_storage = None
|
|
234
234
|
def is_key_storage_enabled() -> bool:
|
235
235
|
"""
|
236
236
|
Check if key storage is enabled and available.
|
237
|
-
|
237
|
+
|
238
238
|
Returns True if:
|
239
239
|
1. Explicitly enabled in config, OR
|
240
240
|
2. asyncpg is available (key_storage extra installed) AND not explicitly disabled
|
@@ -243,11 +243,11 @@ def is_key_storage_enabled() -> bool:
|
|
243
243
|
config_value = get_config_value("key_storage", "enabled", None)
|
244
244
|
if config_value is False:
|
245
245
|
return False
|
246
|
-
|
246
|
+
|
247
247
|
# If explicitly enabled, return True
|
248
248
|
if config_value is True:
|
249
249
|
return True
|
250
|
-
|
250
|
+
|
251
251
|
# If not set in config, auto-detect based on asyncpg availability
|
252
252
|
# This allows users who install [key_storage] extra to use it without manual config
|
253
253
|
return ASYNCPG_AVAILABLE
|
File without changes
|
File without changes
|