ratio1 3.4.88__py3-none-any.whl → 3.4.90__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.
- ratio1/_ver.py +1 -1
- ratio1/const/payload.py +1 -1
- ratio1/ipfs/r1fs.py +739 -26
- {ratio1-3.4.88.dist-info → ratio1-3.4.90.dist-info}/METADATA +1 -1
- {ratio1-3.4.88.dist-info → ratio1-3.4.90.dist-info}/RECORD +8 -8
- {ratio1-3.4.88.dist-info → ratio1-3.4.90.dist-info}/WHEEL +0 -0
- {ratio1-3.4.88.dist-info → ratio1-3.4.90.dist-info}/entry_points.txt +0 -0
- {ratio1-3.4.88.dist-info → ratio1-3.4.90.dist-info}/licenses/LICENSE +0 -0
ratio1/_ver.py
CHANGED
ratio1/const/payload.py
CHANGED
|
@@ -208,7 +208,7 @@ class PAYLOAD_DATA:
|
|
|
208
208
|
NETMON_LAST_SEEN = 'last_seen_sec'
|
|
209
209
|
NETMON_IS_SUPERVISOR = 'is_supervisor'
|
|
210
210
|
NETMON_WHITELIST = 'whitelist'
|
|
211
|
-
NETMON_WHITELIST_MAP = '
|
|
211
|
+
NETMON_WHITELIST_MAP = 'WHITELIST_MAP'
|
|
212
212
|
NETMON_NODE_SECURED = 'secured'
|
|
213
213
|
NETMON_NODE_VERSION = 'version'
|
|
214
214
|
NETMON_NODE_R1FS_ID = 'r1fs_id'
|
ratio1/ipfs/r1fs.py
CHANGED
|
@@ -72,6 +72,7 @@ import random
|
|
|
72
72
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
73
73
|
|
|
74
74
|
DEFAULT_SECRET = "ratio1"
|
|
75
|
+
DEFAULT_FILENAME_JSON = "data.json"
|
|
75
76
|
|
|
76
77
|
from threading import Lock
|
|
77
78
|
|
|
@@ -626,16 +627,49 @@ class R1FSEngine:
|
|
|
626
627
|
data,
|
|
627
628
|
fn=None,
|
|
628
629
|
secret: str = None,
|
|
629
|
-
|
|
630
|
+
nonce: int = None,
|
|
631
|
+
use_tempfile=False,
|
|
630
632
|
show_logs=True,
|
|
631
633
|
raise_on_error=False,
|
|
632
|
-
) ->
|
|
634
|
+
) -> str:
|
|
633
635
|
"""
|
|
634
636
|
Add a JSON object to IPFS.
|
|
637
|
+
|
|
638
|
+
Parameters
|
|
639
|
+
----------
|
|
640
|
+
data : any
|
|
641
|
+
JSON-serializable data to add to IPFS.
|
|
642
|
+
|
|
643
|
+
fn : str, optional
|
|
644
|
+
Filename to use for the JSON data. If None, uses DEFAULT_FILENAME_JSON
|
|
645
|
+
in a unique temporary directory.
|
|
646
|
+
|
|
647
|
+
secret : str, optional
|
|
648
|
+
Passphrase for AES-GCM encryption. Defaults to 'ratio1'.
|
|
649
|
+
|
|
650
|
+
nonce : int, optional
|
|
651
|
+
Nonce for encryption. If None, a random nonce will be generated.
|
|
652
|
+
|
|
653
|
+
use_tempfile : bool, optional
|
|
654
|
+
If True, use a system temporary file with random name. If False, use specified filename in unique directory.
|
|
655
|
+
|
|
656
|
+
show_logs : bool, optional
|
|
657
|
+
Whether to show logs via self.P / self.Pd. Default is True.
|
|
658
|
+
|
|
659
|
+
raise_on_error : bool, optional
|
|
660
|
+
If True, raise an Exception on command errors. Otherwise, logs them. Default is False.
|
|
661
|
+
|
|
662
|
+
Returns
|
|
663
|
+
-------
|
|
664
|
+
str
|
|
665
|
+
The CID of the added JSON file.
|
|
635
666
|
"""
|
|
667
|
+
unique_dir = None
|
|
636
668
|
try:
|
|
637
|
-
json_data = json.dumps(data)
|
|
638
|
-
|
|
669
|
+
json_data = json.dumps(data, sort_keys=True, separators=(',', ':'))
|
|
670
|
+
self.P(f"JSON data: {json_data}")
|
|
671
|
+
|
|
672
|
+
if use_tempfile:
|
|
639
673
|
if show_logs:
|
|
640
674
|
self.Pd("Using tempfile for JSON")
|
|
641
675
|
with tempfile.NamedTemporaryFile(
|
|
@@ -644,21 +678,55 @@ class R1FSEngine:
|
|
|
644
678
|
f.write(json_data)
|
|
645
679
|
fn = f.name
|
|
646
680
|
else:
|
|
647
|
-
|
|
681
|
+
# Use default filename if none specified
|
|
682
|
+
if fn is None:
|
|
683
|
+
fn = DEFAULT_FILENAME_JSON
|
|
684
|
+
|
|
685
|
+
# Create unique temporary directory for the file
|
|
686
|
+
unique_dir = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex)
|
|
687
|
+
os.makedirs(unique_dir, exist_ok=True)
|
|
688
|
+
fn = os.path.join(unique_dir, fn)
|
|
689
|
+
|
|
648
690
|
if show_logs:
|
|
649
|
-
self.Pd(f"Using unique
|
|
691
|
+
self.Pd(f"Using unique directory for JSON: {fn}")
|
|
650
692
|
with open(fn, "w") as f:
|
|
651
693
|
f.write(json_data)
|
|
652
694
|
#end if tempfile
|
|
695
|
+
|
|
696
|
+
if show_logs:
|
|
697
|
+
self.Pd(f"About to call add_file with: file_path={fn}, nonce={nonce}, secret={secret[:10] if secret else 'None'}...")
|
|
698
|
+
|
|
653
699
|
cid = self.add_file(
|
|
654
|
-
file_path=fn,
|
|
700
|
+
file_path=fn,
|
|
701
|
+
secret=secret,
|
|
702
|
+
nonce=nonce,
|
|
703
|
+
show_logs=show_logs,
|
|
655
704
|
raise_on_error=raise_on_error
|
|
656
705
|
)
|
|
706
|
+
|
|
707
|
+
if show_logs:
|
|
708
|
+
self.Pd(f"add_file returned CID: {cid}")
|
|
709
|
+
|
|
657
710
|
return cid
|
|
711
|
+
|
|
658
712
|
except Exception as e:
|
|
659
713
|
if show_logs:
|
|
660
714
|
self.P(f"Error adding JSON to IPFS: {e}", color='r')
|
|
715
|
+
if raise_on_error:
|
|
716
|
+
raise
|
|
661
717
|
return None
|
|
718
|
+
|
|
719
|
+
finally:
|
|
720
|
+
# Clean up temporary files and directories
|
|
721
|
+
if fn and os.path.exists(fn):
|
|
722
|
+
os.remove(fn)
|
|
723
|
+
if show_logs:
|
|
724
|
+
self.Pd(f"Cleaned up temporary JSON file: {fn}")
|
|
725
|
+
|
|
726
|
+
if unique_dir and os.path.exists(unique_dir):
|
|
727
|
+
os.rmdir(unique_dir)
|
|
728
|
+
if show_logs:
|
|
729
|
+
self.Pd(f"Cleaned up temporary directory: {unique_dir}")
|
|
662
730
|
|
|
663
731
|
|
|
664
732
|
def add_yaml(
|
|
@@ -666,7 +734,8 @@ class R1FSEngine:
|
|
|
666
734
|
data,
|
|
667
735
|
fn=None,
|
|
668
736
|
secret: str = None,
|
|
669
|
-
|
|
737
|
+
nonce: int = None,
|
|
738
|
+
tempfile=False,
|
|
670
739
|
show_logs=True,
|
|
671
740
|
raise_on_error=False,
|
|
672
741
|
) -> str:
|
|
@@ -689,7 +758,10 @@ class R1FSEngine:
|
|
|
689
758
|
with open(fn, "w") as f:
|
|
690
759
|
f.write(yaml_data)
|
|
691
760
|
cid = self.add_file(
|
|
692
|
-
file_path=fn,
|
|
761
|
+
file_path=fn,
|
|
762
|
+
secret=secret,
|
|
763
|
+
nonce=nonce,
|
|
764
|
+
show_logs=show_logs,
|
|
693
765
|
raise_on_error=raise_on_error
|
|
694
766
|
)
|
|
695
767
|
return cid
|
|
@@ -704,36 +776,93 @@ class R1FSEngine:
|
|
|
704
776
|
data,
|
|
705
777
|
fn=None,
|
|
706
778
|
secret: str = None,
|
|
707
|
-
|
|
779
|
+
nonce: int = None,
|
|
780
|
+
use_tempfile=False,
|
|
708
781
|
show_logs=True,
|
|
709
782
|
raise_on_error=False,
|
|
710
|
-
) ->
|
|
783
|
+
) -> str:
|
|
711
784
|
"""
|
|
712
785
|
Add a Pickle object to IPFS.
|
|
786
|
+
|
|
787
|
+
Parameters
|
|
788
|
+
----------
|
|
789
|
+
data : any
|
|
790
|
+
Python object to pickle and add to IPFS.
|
|
791
|
+
|
|
792
|
+
fn : str, optional
|
|
793
|
+
Filename to use for the pickle data. If None, generates a unique filename.
|
|
794
|
+
|
|
795
|
+
secret : str, optional
|
|
796
|
+
Passphrase for AES-GCM encryption. Defaults to 'ratio1'.
|
|
797
|
+
|
|
798
|
+
nonce : int, optional
|
|
799
|
+
Nonce for encryption. If None, a random nonce will be generated.
|
|
800
|
+
|
|
801
|
+
use_tempfile : bool, optional
|
|
802
|
+
If True, use a system temporary file with random name. If False, use specified filename.
|
|
803
|
+
|
|
804
|
+
show_logs : bool, optional
|
|
805
|
+
Whether to show logs via self.P / self.Pd. Default is True.
|
|
806
|
+
|
|
807
|
+
raise_on_error : bool, optional
|
|
808
|
+
If True, raise an Exception on command errors. Otherwise, logs them. Default is False.
|
|
809
|
+
|
|
810
|
+
Returns
|
|
811
|
+
-------
|
|
812
|
+
str
|
|
813
|
+
The CID of the added pickle file.
|
|
713
814
|
"""
|
|
714
815
|
try:
|
|
715
816
|
import pickle
|
|
716
|
-
if
|
|
817
|
+
if show_logs:
|
|
818
|
+
self.Pd(f"Pickling data of type: {type(data)}")
|
|
819
|
+
|
|
820
|
+
if use_tempfile:
|
|
717
821
|
if show_logs:
|
|
718
822
|
self.Pd("Using tempfile for Pickle")
|
|
719
823
|
with tempfile.NamedTemporaryFile(mode='wb', suffix='.pkl', delete=False) as f:
|
|
720
824
|
pickle.dump(data, f)
|
|
721
825
|
fn = f.name
|
|
722
826
|
else:
|
|
723
|
-
fn
|
|
724
|
-
|
|
725
|
-
|
|
827
|
+
if fn is None:
|
|
828
|
+
fn = self._get_unique_or_complete_upload_name(fn=fn, suffix=".pkl")
|
|
829
|
+
if show_logs:
|
|
830
|
+
self.Pd(f"Using unique name for pkl: {fn}")
|
|
831
|
+
else:
|
|
832
|
+
if show_logs:
|
|
833
|
+
self.Pd(f"Using provided filename: {fn}")
|
|
726
834
|
with open(fn, "wb") as f:
|
|
727
835
|
pickle.dump(data, f)
|
|
836
|
+
|
|
837
|
+
if show_logs:
|
|
838
|
+
self.Pd(f"About to call add_file with: file_path={fn}, nonce={nonce}, secret={secret[:10] if secret else 'None'}...")
|
|
839
|
+
|
|
728
840
|
cid = self.add_file(
|
|
729
|
-
file_path=fn,
|
|
841
|
+
file_path=fn,
|
|
842
|
+
secret=secret,
|
|
843
|
+
nonce=nonce,
|
|
844
|
+
show_logs=show_logs,
|
|
730
845
|
raise_on_error=raise_on_error
|
|
731
846
|
)
|
|
847
|
+
|
|
848
|
+
if show_logs:
|
|
849
|
+
self.Pd(f"add_file returned CID: {cid}")
|
|
850
|
+
|
|
732
851
|
return cid
|
|
852
|
+
|
|
733
853
|
except Exception as e:
|
|
734
854
|
if show_logs:
|
|
735
855
|
self.P(f"Error adding Pickle to IPFS: {e}", color='r')
|
|
856
|
+
if raise_on_error:
|
|
857
|
+
raise
|
|
736
858
|
return None
|
|
859
|
+
|
|
860
|
+
finally:
|
|
861
|
+
# Clean up temporary files
|
|
862
|
+
if 'fn' in locals() and fn and os.path.exists(fn):
|
|
863
|
+
os.remove(fn)
|
|
864
|
+
if show_logs:
|
|
865
|
+
self.Pd(f"Cleaned up temporary pickle file: {fn}")
|
|
737
866
|
|
|
738
867
|
|
|
739
868
|
@require_ipfs_started
|
|
@@ -801,27 +930,35 @@ class R1FSEngine:
|
|
|
801
930
|
key = self._hash_secret(secret) # mandatory passphrase
|
|
802
931
|
|
|
803
932
|
if nonce is None:
|
|
804
|
-
|
|
933
|
+
nonce_bytes = os.urandom(12) # recommended for GCM
|
|
934
|
+
if show_logs:
|
|
935
|
+
self.Pd(f"Generated random nonce: {nonce_bytes.hex()}")
|
|
805
936
|
else:
|
|
806
|
-
|
|
937
|
+
original_nonce = nonce
|
|
938
|
+
nonce_bytes = random.Random(nonce).randbytes(12)
|
|
807
939
|
|
|
808
940
|
original_basename = os.path.basename(file_path)
|
|
809
941
|
|
|
810
942
|
# JSON metadata storing the original filename
|
|
811
943
|
meta_dict = {"filename": original_basename}
|
|
812
944
|
meta_bytes = json.dumps(meta_dict).encode("utf-8")
|
|
945
|
+
|
|
946
|
+
if show_logs:
|
|
947
|
+
self.Pd(f"Original basename: {original_basename}")
|
|
948
|
+
self.Pd(f"Metadata: {meta_dict}")
|
|
949
|
+
self.Pd(f"Secret hash (first 16 bytes): {key[:16].hex()}")
|
|
813
950
|
|
|
814
951
|
tmp_cipher_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".bin")
|
|
815
952
|
|
|
816
953
|
folder_cid = None
|
|
817
954
|
start_time = time.time()
|
|
818
955
|
try:
|
|
819
|
-
encryptor = Cipher(algorithms.AES(key), modes.GCM(
|
|
956
|
+
encryptor = Cipher(algorithms.AES(key), modes.GCM(nonce_bytes)).encryptor()
|
|
820
957
|
|
|
821
958
|
chunk_size = 1024 * 1024 # 1 MB chunks
|
|
822
959
|
with open(file_path, "rb") as fin, open(tmp_cipher_path, "wb") as fout:
|
|
823
960
|
# [nonce][4-byte-len][metadata][ciphertext][16-byte GCM tag]
|
|
824
|
-
fout.write(
|
|
961
|
+
fout.write(nonce_bytes)
|
|
825
962
|
meta_len = len(meta_bytes)
|
|
826
963
|
fout.write(meta_len.to_bytes(4, "big"))
|
|
827
964
|
# encrypt the metadata and save
|
|
@@ -840,10 +977,12 @@ class R1FSEngine:
|
|
|
840
977
|
tag = encryptor.tag
|
|
841
978
|
fout.write(tag)
|
|
842
979
|
#end with fin, fout
|
|
843
|
-
|
|
980
|
+
|
|
844
981
|
# Now we IPFS-add the ciphertext
|
|
845
982
|
output = self.__run_command(["ipfs", "add", "-q", "-w", tmp_cipher_path], show_logs=show_logs)
|
|
846
983
|
lines = output.strip().split("\n")
|
|
984
|
+
self.P("output lines: ", json.dumps(lines))
|
|
985
|
+
|
|
847
986
|
if not lines:
|
|
848
987
|
raise RuntimeError("No output from 'ipfs add -w -q' for ciphertext.")
|
|
849
988
|
folder_cid = lines[-1].strip()
|
|
@@ -1002,11 +1141,19 @@ class R1FSEngine:
|
|
|
1002
1141
|
|
|
1003
1142
|
# if the folder exists cleanup the content
|
|
1004
1143
|
if os.path.exists(local_folder):
|
|
1005
|
-
if
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1144
|
+
if os.path.isdir(local_folder):
|
|
1145
|
+
if show_logs:
|
|
1146
|
+
files = os.listdir(local_folder)
|
|
1147
|
+
self.Pd(f"Cleaning up {local_folder} with {files}")
|
|
1148
|
+
#end if show_logs
|
|
1149
|
+
shutil.rmtree(local_folder)
|
|
1150
|
+
else:
|
|
1151
|
+
# If it's a file, just remove it
|
|
1152
|
+
if show_logs:
|
|
1153
|
+
self.Pd(f"Removing existing file {local_folder}")
|
|
1154
|
+
#end if show_logs
|
|
1155
|
+
os.remove(local_folder)
|
|
1156
|
+
#end if isdir
|
|
1010
1157
|
#end if local_folder exists
|
|
1011
1158
|
|
|
1012
1159
|
|
|
@@ -1024,6 +1171,14 @@ class R1FSEngine:
|
|
|
1024
1171
|
download_elapsed_time = time.time() - start_time
|
|
1025
1172
|
|
|
1026
1173
|
# Expect exactly one file
|
|
1174
|
+
if not os.path.isdir(local_folder):
|
|
1175
|
+
msg = f"Expected {local_folder} to be a directory after IPFS download, but it's not"
|
|
1176
|
+
if raise_on_error:
|
|
1177
|
+
raise RuntimeError(msg)
|
|
1178
|
+
else:
|
|
1179
|
+
self.P(msg, color='r')
|
|
1180
|
+
return
|
|
1181
|
+
|
|
1027
1182
|
contents = os.listdir(local_folder)
|
|
1028
1183
|
if len(contents) != 1:
|
|
1029
1184
|
msg = f"Expected 1 file in {local_folder}, found {contents}"
|
|
@@ -1106,6 +1261,224 @@ class R1FSEngine:
|
|
|
1106
1261
|
#end if out_path is not None
|
|
1107
1262
|
return out_path
|
|
1108
1263
|
|
|
1264
|
+
@require_ipfs_started
|
|
1265
|
+
def get_pickle(
|
|
1266
|
+
self,
|
|
1267
|
+
cid: str,
|
|
1268
|
+
local_folder: str = None,
|
|
1269
|
+
secret: str = None,
|
|
1270
|
+
timeout: int = None,
|
|
1271
|
+
pin: bool = True,
|
|
1272
|
+
raise_on_error: bool = False,
|
|
1273
|
+
show_logs: bool = True,
|
|
1274
|
+
):
|
|
1275
|
+
"""
|
|
1276
|
+
Retrieve and deserialize a pickle file from R1FS by CID.
|
|
1277
|
+
Uses get_file under the hood to download and decrypt the file, then loads it with pickle.
|
|
1278
|
+
|
|
1279
|
+
Parameters
|
|
1280
|
+
----------
|
|
1281
|
+
cid : str
|
|
1282
|
+
The folder CID (wrapped single file).
|
|
1283
|
+
|
|
1284
|
+
local_folder : str, optional
|
|
1285
|
+
Destination folder. If None, we default to something like self.__downloads_dir/<CID>.
|
|
1286
|
+
|
|
1287
|
+
secret : str, optional
|
|
1288
|
+
Passphrase for AES-GCM. Must not be empty. Defaults to 'ratio1'.
|
|
1289
|
+
|
|
1290
|
+
timeout : int, optional
|
|
1291
|
+
Maximum seconds for the IPFS get. If None, use IPFSCt.TIMEOUT.
|
|
1292
|
+
|
|
1293
|
+
pin : bool, optional
|
|
1294
|
+
If True, we optionally pin the folder. Default True.
|
|
1295
|
+
|
|
1296
|
+
raise_on_error : bool, optional
|
|
1297
|
+
If True, raise an Exception on command errors/timeouts. Otherwise logs them. Default False.
|
|
1298
|
+
|
|
1299
|
+
show_logs : bool, optional
|
|
1300
|
+
If True, logs steps via self.P / self.Pd. Default True.
|
|
1301
|
+
|
|
1302
|
+
Returns
|
|
1303
|
+
-------
|
|
1304
|
+
any
|
|
1305
|
+
The deserialized Python object from the pickle file.
|
|
1306
|
+
|
|
1307
|
+
Raises
|
|
1308
|
+
------
|
|
1309
|
+
ValueError
|
|
1310
|
+
If the secret is empty.
|
|
1311
|
+
|
|
1312
|
+
RuntimeError
|
|
1313
|
+
If multiple or zero files are found in the downloaded folder,
|
|
1314
|
+
or if we fail to parse the JSON metadata.
|
|
1315
|
+
|
|
1316
|
+
Exception
|
|
1317
|
+
If the GCM tag is invalid or the IPFS command times out
|
|
1318
|
+
and raise_on_error=True.
|
|
1319
|
+
|
|
1320
|
+
Examples
|
|
1321
|
+
--------
|
|
1322
|
+
>>> # Simple usage with default passphrase
|
|
1323
|
+
>>> data = engine.get_pickle("QmEncFolderXYZ")
|
|
1324
|
+
>>> print(data)
|
|
1325
|
+
{'key': 'value', 'list': [1, 2, 3]}
|
|
1326
|
+
|
|
1327
|
+
"""
|
|
1328
|
+
import pickle
|
|
1329
|
+
|
|
1330
|
+
# Use get_file to download and decrypt the file
|
|
1331
|
+
file_path = self.get_file(
|
|
1332
|
+
cid=cid,
|
|
1333
|
+
local_folder=local_folder,
|
|
1334
|
+
secret=secret,
|
|
1335
|
+
timeout=timeout,
|
|
1336
|
+
pin=pin,
|
|
1337
|
+
raise_on_error=raise_on_error,
|
|
1338
|
+
show_logs=show_logs,
|
|
1339
|
+
return_absolute_path=True
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
if file_path is None:
|
|
1343
|
+
if raise_on_error:
|
|
1344
|
+
raise RuntimeError(f"Failed to retrieve file for CID {cid}")
|
|
1345
|
+
else:
|
|
1346
|
+
if show_logs:
|
|
1347
|
+
self.P(f"Failed to retrieve file for CID {cid}", color='r')
|
|
1348
|
+
return None
|
|
1349
|
+
|
|
1350
|
+
# Load the pickle file
|
|
1351
|
+
try:
|
|
1352
|
+
with open(file_path, "rb") as f:
|
|
1353
|
+
data = pickle.load(f)
|
|
1354
|
+
|
|
1355
|
+
if show_logs:
|
|
1356
|
+
self.Pd(f"Successfully loaded pickle data from {file_path}")
|
|
1357
|
+
|
|
1358
|
+
# Clean up the temporary file
|
|
1359
|
+
os.remove(file_path)
|
|
1360
|
+
if show_logs:
|
|
1361
|
+
self.Pd(f"Cleaned up temporary pickle file: {file_path}")
|
|
1362
|
+
|
|
1363
|
+
return data
|
|
1364
|
+
|
|
1365
|
+
except Exception as e:
|
|
1366
|
+
msg = f"Error loading pickle file {file_path}: {e}"
|
|
1367
|
+
if raise_on_error:
|
|
1368
|
+
raise RuntimeError(msg)
|
|
1369
|
+
else:
|
|
1370
|
+
if show_logs:
|
|
1371
|
+
self.P(msg, color='r')
|
|
1372
|
+
return None
|
|
1373
|
+
|
|
1374
|
+
@require_ipfs_started
|
|
1375
|
+
def get_json(
|
|
1376
|
+
self,
|
|
1377
|
+
cid: str,
|
|
1378
|
+
local_folder: str = None,
|
|
1379
|
+
secret: str = None,
|
|
1380
|
+
timeout: int = None,
|
|
1381
|
+
pin: bool = True,
|
|
1382
|
+
raise_on_error: bool = False,
|
|
1383
|
+
show_logs: bool = True,
|
|
1384
|
+
):
|
|
1385
|
+
"""
|
|
1386
|
+
Retrieve and deserialize a JSON file from R1FS by CID.
|
|
1387
|
+
Uses get_file under the hood to download and decrypt the file, then loads it with json.
|
|
1388
|
+
|
|
1389
|
+
Parameters
|
|
1390
|
+
----------
|
|
1391
|
+
cid : str
|
|
1392
|
+
The folder CID (wrapped single file).
|
|
1393
|
+
|
|
1394
|
+
local_folder : str, optional
|
|
1395
|
+
Destination folder. If None, we default to something like self.__downloads_dir/<CID>.
|
|
1396
|
+
|
|
1397
|
+
secret : str, optional
|
|
1398
|
+
Passphrase for AES-GCM. Must not be empty. Defaults to 'ratio1'.
|
|
1399
|
+
|
|
1400
|
+
timeout : int, optional
|
|
1401
|
+
Maximum seconds for the IPFS get. If None, use IPFSCt.TIMEOUT.
|
|
1402
|
+
|
|
1403
|
+
pin : bool, optional
|
|
1404
|
+
If True, we optionally pin the folder. Default True.
|
|
1405
|
+
|
|
1406
|
+
raise_on_error : bool, optional
|
|
1407
|
+
If True, raise an Exception on command errors/timeouts. Otherwise logs them. Default False.
|
|
1408
|
+
|
|
1409
|
+
show_logs : bool, optional
|
|
1410
|
+
If True, logs steps via self.P / self.Pd. Default True.
|
|
1411
|
+
|
|
1412
|
+
Returns
|
|
1413
|
+
-------
|
|
1414
|
+
any
|
|
1415
|
+
The deserialized JSON object from the file.
|
|
1416
|
+
|
|
1417
|
+
Raises
|
|
1418
|
+
------
|
|
1419
|
+
ValueError
|
|
1420
|
+
If the secret is empty.
|
|
1421
|
+
|
|
1422
|
+
RuntimeError
|
|
1423
|
+
If multiple or zero files are found in the downloaded folder,
|
|
1424
|
+
or if we fail to parse the JSON metadata.
|
|
1425
|
+
|
|
1426
|
+
Exception
|
|
1427
|
+
If the GCM tag is invalid or the IPFS command times out
|
|
1428
|
+
and raise_on_error=True.
|
|
1429
|
+
|
|
1430
|
+
Examples
|
|
1431
|
+
--------
|
|
1432
|
+
>>> # Simple usage with default passphrase
|
|
1433
|
+
>>> data = engine.get_json("QmEncFolderXYZ")
|
|
1434
|
+
>>> print(data)
|
|
1435
|
+
{'key': 'value', 'list': [1, 2, 3]}
|
|
1436
|
+
|
|
1437
|
+
"""
|
|
1438
|
+
# Use get_file to download and decrypt the file
|
|
1439
|
+
file_path = self.get_file(
|
|
1440
|
+
cid=cid,
|
|
1441
|
+
local_folder=local_folder,
|
|
1442
|
+
secret=secret,
|
|
1443
|
+
timeout=timeout,
|
|
1444
|
+
pin=pin,
|
|
1445
|
+
raise_on_error=raise_on_error,
|
|
1446
|
+
show_logs=show_logs,
|
|
1447
|
+
return_absolute_path=True
|
|
1448
|
+
)
|
|
1449
|
+
|
|
1450
|
+
if file_path is None:
|
|
1451
|
+
if raise_on_error:
|
|
1452
|
+
raise RuntimeError(f"Failed to retrieve file for CID {cid}")
|
|
1453
|
+
else:
|
|
1454
|
+
if show_logs:
|
|
1455
|
+
self.P(f"Failed to retrieve file for CID {cid}", color='r')
|
|
1456
|
+
return None
|
|
1457
|
+
|
|
1458
|
+
# Load the JSON file
|
|
1459
|
+
try:
|
|
1460
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
1461
|
+
data = json.load(f)
|
|
1462
|
+
|
|
1463
|
+
if show_logs:
|
|
1464
|
+
self.Pd(f"Successfully loaded JSON data from {file_path}")
|
|
1465
|
+
|
|
1466
|
+
# Clean up the temporary file
|
|
1467
|
+
os.remove(file_path)
|
|
1468
|
+
if show_logs:
|
|
1469
|
+
self.Pd(f"Cleaned up temporary JSON file: {file_path}")
|
|
1470
|
+
|
|
1471
|
+
return data
|
|
1472
|
+
|
|
1473
|
+
except Exception as e:
|
|
1474
|
+
msg = f"Error loading JSON file {file_path}: {e}"
|
|
1475
|
+
if raise_on_error:
|
|
1476
|
+
raise RuntimeError(msg)
|
|
1477
|
+
else:
|
|
1478
|
+
if show_logs:
|
|
1479
|
+
self.P(msg, color='r')
|
|
1480
|
+
return None
|
|
1481
|
+
|
|
1109
1482
|
|
|
1110
1483
|
@require_ipfs_started
|
|
1111
1484
|
def list_pins(self):
|
|
@@ -1148,6 +1521,346 @@ class R1FSEngine:
|
|
|
1148
1521
|
except Exception as e:
|
|
1149
1522
|
result = False
|
|
1150
1523
|
return result
|
|
1524
|
+
|
|
1525
|
+
def calculate_file_cid(
|
|
1526
|
+
self,
|
|
1527
|
+
file_path: str,
|
|
1528
|
+
nonce: int,
|
|
1529
|
+
secret: str = None,
|
|
1530
|
+
show_logs: bool = True
|
|
1531
|
+
) -> str:
|
|
1532
|
+
"""
|
|
1533
|
+
Calculate the CID (Content Identifier) of a file without adding it to IPFS.
|
|
1534
|
+
This method encrypts the file the same way as add_file and computes the CID
|
|
1535
|
+
that would be generated if the encrypted file were added to IPFS.
|
|
1536
|
+
|
|
1537
|
+
Parameters
|
|
1538
|
+
----------
|
|
1539
|
+
file_path : str
|
|
1540
|
+
Path to the local file to calculate CID for.
|
|
1541
|
+
|
|
1542
|
+
nonce : int
|
|
1543
|
+
Nonce for encryption. Required parameter for deterministic CID calculation.
|
|
1544
|
+
|
|
1545
|
+
secret : str, optional
|
|
1546
|
+
Passphrase for AES-GCM encryption. Defaults to 'ratio1'.
|
|
1547
|
+
|
|
1548
|
+
show_logs : bool, optional
|
|
1549
|
+
Whether to show logs via self.P / self.Pd. Default is True.
|
|
1550
|
+
|
|
1551
|
+
Returns
|
|
1552
|
+
-------
|
|
1553
|
+
str
|
|
1554
|
+
The CID of the encrypted file (e.g., "QmHash...")
|
|
1555
|
+
|
|
1556
|
+
Raises
|
|
1557
|
+
------
|
|
1558
|
+
FileNotFoundError
|
|
1559
|
+
If file_path does not exist.
|
|
1560
|
+
|
|
1561
|
+
ImportError
|
|
1562
|
+
If IPFS is not available or not running.
|
|
1563
|
+
|
|
1564
|
+
ValueError
|
|
1565
|
+
If nonce is None or invalid.
|
|
1566
|
+
|
|
1567
|
+
Examples
|
|
1568
|
+
--------
|
|
1569
|
+
>>> cid = engine.calculate_file_cid("/path/to/file.txt", nonce=12345)
|
|
1570
|
+
>>> print(cid)
|
|
1571
|
+
QmHash123ABC...
|
|
1572
|
+
"""
|
|
1573
|
+
if not os.path.isfile(file_path):
|
|
1574
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
1575
|
+
|
|
1576
|
+
if secret in ["", None]:
|
|
1577
|
+
secret = self.__DEFAULT_SECRET
|
|
1578
|
+
|
|
1579
|
+
# Validate nonce parameter
|
|
1580
|
+
if nonce is None:
|
|
1581
|
+
raise ValueError("nonce parameter is required for deterministic CID calculation")
|
|
1582
|
+
|
|
1583
|
+
# Check file size and throw an error if larger than 2 GB.
|
|
1584
|
+
file_size = os.path.getsize(file_path)
|
|
1585
|
+
if file_size > 2 * 1024 * 1024 * 1024:
|
|
1586
|
+
raise ValueError(f"File {file_path} is too large ({file_size} bytes). Maximum allowed size is 2 GB.")
|
|
1587
|
+
|
|
1588
|
+
key = self._hash_secret(secret)
|
|
1589
|
+
nonce_bytes = random.Random(nonce).randbytes(12)
|
|
1590
|
+
|
|
1591
|
+
original_basename = os.path.basename(file_path)
|
|
1592
|
+
|
|
1593
|
+
# JSON metadata storing the original filename
|
|
1594
|
+
meta_dict = {"filename": original_basename}
|
|
1595
|
+
meta_bytes = json.dumps(meta_dict).encode("utf-8")
|
|
1596
|
+
|
|
1597
|
+
if show_logs:
|
|
1598
|
+
self.Pd(f"Calculating CID for encrypted file: {file_path}")
|
|
1599
|
+
self.Pd(f"Nonce input: {nonce}, Generated nonce bytes: {nonce_bytes.hex()}")
|
|
1600
|
+
self.Pd(f"Original basename: {original_basename}")
|
|
1601
|
+
self.Pd(f"Metadata: {meta_dict}")
|
|
1602
|
+
self.Pd(f"Secret hash (first 16 bytes): {key[:16].hex()}")
|
|
1603
|
+
|
|
1604
|
+
# Create temporary encrypted file (same as in add_file)
|
|
1605
|
+
tmp_cipher_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".bin")
|
|
1606
|
+
|
|
1607
|
+
try:
|
|
1608
|
+
# Encrypt the file content (same as in add_file)
|
|
1609
|
+
encryptor = Cipher(algorithms.AES(key), modes.GCM(nonce_bytes)).encryptor()
|
|
1610
|
+
|
|
1611
|
+
chunk_size = 1024 * 1024 # 1 MB chunks
|
|
1612
|
+
with open(file_path, "rb") as fin, open(tmp_cipher_path, "wb") as fout:
|
|
1613
|
+
# [nonce][4-byte-len][metadata][ciphertext][16-byte GCM tag]
|
|
1614
|
+
fout.write(nonce_bytes)
|
|
1615
|
+
meta_len = len(meta_bytes)
|
|
1616
|
+
fout.write(meta_len.to_bytes(4, "big"))
|
|
1617
|
+
# encrypt the metadata and save
|
|
1618
|
+
enc_meta_data = encryptor.update(meta_bytes)
|
|
1619
|
+
fout.write(enc_meta_data)
|
|
1620
|
+
|
|
1621
|
+
while True:
|
|
1622
|
+
chunk = fin.read(chunk_size)
|
|
1623
|
+
if not chunk:
|
|
1624
|
+
break
|
|
1625
|
+
fout.write(encryptor.update(chunk))
|
|
1626
|
+
#end while there are still bytes to read
|
|
1627
|
+
final_ct = encryptor.finalize()
|
|
1628
|
+
fout.write(final_ct)
|
|
1629
|
+
# Append 16-byte GCM tag
|
|
1630
|
+
tag = encryptor.tag
|
|
1631
|
+
fout.write(tag)
|
|
1632
|
+
#end with fin, fout
|
|
1633
|
+
|
|
1634
|
+
# Use IPFS to calculate the hash without adding the file
|
|
1635
|
+
output = self.__run_command(["ipfs", "add", "--only-hash", "-q", "-w", tmp_cipher_path], show_logs=show_logs)
|
|
1636
|
+
lines = output.strip().split("\n")
|
|
1637
|
+
self.P("output lines: ", json.dumps(lines))
|
|
1638
|
+
if not lines:
|
|
1639
|
+
raise RuntimeError("No output from 'ipfs add --only-hash -q -w' for ciphertext.")
|
|
1640
|
+
|
|
1641
|
+
# Get the CID from IPFS
|
|
1642
|
+
file_cid = lines[0].strip()
|
|
1643
|
+
if show_logs:
|
|
1644
|
+
self.Pd(f"IPFS calculated CID: {file_cid}")
|
|
1645
|
+
|
|
1646
|
+
return file_cid
|
|
1647
|
+
|
|
1648
|
+
finally:
|
|
1649
|
+
# Clean up temporary encrypted file
|
|
1650
|
+
if os.path.exists(tmp_cipher_path):
|
|
1651
|
+
os.remove(tmp_cipher_path)
|
|
1652
|
+
|
|
1653
|
+
def calculate_json_cid(
|
|
1654
|
+
self,
|
|
1655
|
+
data,
|
|
1656
|
+
nonce: int,
|
|
1657
|
+
fn: str = None,
|
|
1658
|
+
secret: str = None,
|
|
1659
|
+
show_logs: bool = True
|
|
1660
|
+
) -> str:
|
|
1661
|
+
"""
|
|
1662
|
+
Calculate the CID (Content Identifier) of JSON data without adding it to IPFS.
|
|
1663
|
+
This method saves the JSON data to a temporary file, then uses calculate_file_cid
|
|
1664
|
+
to compute the CID that would be generated if the encrypted file were added to IPFS.
|
|
1665
|
+
|
|
1666
|
+
Parameters
|
|
1667
|
+
----------
|
|
1668
|
+
data : any
|
|
1669
|
+
JSON-serializable data to calculate CID for.
|
|
1670
|
+
|
|
1671
|
+
nonce : int
|
|
1672
|
+
Nonce for encryption. Required parameter for deterministic CID calculation.
|
|
1673
|
+
|
|
1674
|
+
fn : str, optional
|
|
1675
|
+
Filename to use for the JSON data. If None, uses DEFAULT_FILENAME_JSON.
|
|
1676
|
+
This affects the CID calculation as the filename is included in the metadata.
|
|
1677
|
+
|
|
1678
|
+
secret : str, optional
|
|
1679
|
+
Passphrase for AES-GCM encryption. Defaults to 'ratio1'.
|
|
1680
|
+
|
|
1681
|
+
show_logs : bool, optional
|
|
1682
|
+
Whether to show logs via self.P / self.Pd. Default is True.
|
|
1683
|
+
|
|
1684
|
+
Returns
|
|
1685
|
+
-------
|
|
1686
|
+
str
|
|
1687
|
+
The CID of the encrypted JSON file (e.g., "QmHash...")
|
|
1688
|
+
|
|
1689
|
+
Raises
|
|
1690
|
+
------
|
|
1691
|
+
ImportError
|
|
1692
|
+
If IPFS is not available or not running.
|
|
1693
|
+
|
|
1694
|
+
ValueError
|
|
1695
|
+
If nonce is None or invalid, or if data cannot be JSON serialized.
|
|
1696
|
+
|
|
1697
|
+
Examples
|
|
1698
|
+
--------
|
|
1699
|
+
>>> data = {"name": "test", "value": 123}
|
|
1700
|
+
>>> cid = engine.calculate_json_cid(data, nonce=12345)
|
|
1701
|
+
>>> print(cid)
|
|
1702
|
+
QmHash123ABC...
|
|
1703
|
+
|
|
1704
|
+
>>> # Use custom filename
|
|
1705
|
+
>>> cid = engine.calculate_json_cid(data, nonce=12345, fn="config.json")
|
|
1706
|
+
>>> print(cid)
|
|
1707
|
+
QmHash456DEF...
|
|
1708
|
+
"""
|
|
1709
|
+
# Serialize JSON data
|
|
1710
|
+
try:
|
|
1711
|
+
json_data = json.dumps(data, sort_keys=True, separators=(',', ':'))
|
|
1712
|
+
except (TypeError, ValueError) as e:
|
|
1713
|
+
raise ValueError(f"Data cannot be JSON serialized: {e}")
|
|
1714
|
+
|
|
1715
|
+
self.P(f"JSON data: {json_data}")
|
|
1716
|
+
|
|
1717
|
+
# Use custom filename or default
|
|
1718
|
+
if fn is None:
|
|
1719
|
+
fn = DEFAULT_FILENAME_JSON
|
|
1720
|
+
|
|
1721
|
+
if show_logs:
|
|
1722
|
+
self.Pd(f"Calculating CID for JSON data with {len(json_data)} characters, filename: {fn}")
|
|
1723
|
+
|
|
1724
|
+
# Create temporary file for JSON data with the desired filename in a unique directory
|
|
1725
|
+
unique_dir = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex)
|
|
1726
|
+
os.makedirs(unique_dir, exist_ok=True)
|
|
1727
|
+
tmp_json_path = os.path.join(unique_dir, fn)
|
|
1728
|
+
|
|
1729
|
+
file_cid = None
|
|
1730
|
+
try:
|
|
1731
|
+
# Write JSON data to temporary file
|
|
1732
|
+
with open(tmp_json_path, "w") as f:
|
|
1733
|
+
f.write(json_data)
|
|
1734
|
+
|
|
1735
|
+
if show_logs:
|
|
1736
|
+
self.Pd(f"About to call calculate_file_cid with: file_path={tmp_json_path}, nonce={nonce}, secret={secret[:10] if secret else 'None'}...")
|
|
1737
|
+
|
|
1738
|
+
# Use calculate_file_cid to get the CID
|
|
1739
|
+
file_cid = self.calculate_file_cid(
|
|
1740
|
+
file_path=tmp_json_path,
|
|
1741
|
+
nonce=nonce,
|
|
1742
|
+
secret=secret,
|
|
1743
|
+
show_logs=True # Don't show logs from calculate_file_cid
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
if show_logs:
|
|
1747
|
+
self.Pd(f"calculate_file_cid returned CID: {file_cid}")
|
|
1748
|
+
|
|
1749
|
+
finally:
|
|
1750
|
+
# Clean up temporary JSON file and directory
|
|
1751
|
+
if os.path.exists(tmp_json_path):
|
|
1752
|
+
os.remove(tmp_json_path)
|
|
1753
|
+
if os.path.exists(unique_dir):
|
|
1754
|
+
os.rmdir(unique_dir)
|
|
1755
|
+
|
|
1756
|
+
return file_cid
|
|
1757
|
+
|
|
1758
|
+
def calculate_pickle_cid(
|
|
1759
|
+
self,
|
|
1760
|
+
data,
|
|
1761
|
+
nonce: int,
|
|
1762
|
+
fn: str = None,
|
|
1763
|
+
secret: str = None,
|
|
1764
|
+
show_logs: bool = True
|
|
1765
|
+
) -> str:
|
|
1766
|
+
"""
|
|
1767
|
+
Calculate the CID (Content Identifier) of pickle data without adding it to IPFS.
|
|
1768
|
+
This method saves the pickle data to a temporary file, then uses calculate_file_cid
|
|
1769
|
+
to compute the CID that would be generated if the encrypted file were added to IPFS.
|
|
1770
|
+
|
|
1771
|
+
Parameters
|
|
1772
|
+
----------
|
|
1773
|
+
data : any
|
|
1774
|
+
Python object to pickle and calculate CID for.
|
|
1775
|
+
|
|
1776
|
+
nonce : int
|
|
1777
|
+
Nonce for encryption. Required parameter for deterministic CID calculation.
|
|
1778
|
+
|
|
1779
|
+
fn : str, optional
|
|
1780
|
+
Filename to use for the pickle data. If None, generates a unique filename.
|
|
1781
|
+
This affects the CID calculation as the filename is included in the metadata.
|
|
1782
|
+
|
|
1783
|
+
secret : str, optional
|
|
1784
|
+
Passphrase for AES-GCM encryption. Defaults to 'ratio1'.
|
|
1785
|
+
|
|
1786
|
+
show_logs : bool, optional
|
|
1787
|
+
Whether to show logs via self.P / self.Pd. Default is True.
|
|
1788
|
+
|
|
1789
|
+
Returns
|
|
1790
|
+
-------
|
|
1791
|
+
str
|
|
1792
|
+
The CID of the encrypted pickle file (e.g., "QmHash...")
|
|
1793
|
+
|
|
1794
|
+
Raises
|
|
1795
|
+
------
|
|
1796
|
+
ImportError
|
|
1797
|
+
If IPFS is not available or not running.
|
|
1798
|
+
|
|
1799
|
+
ValueError
|
|
1800
|
+
If nonce is None or invalid, or if data cannot be pickled.
|
|
1801
|
+
|
|
1802
|
+
Examples
|
|
1803
|
+
--------
|
|
1804
|
+
>>> data = {"name": "test", "value": 123}
|
|
1805
|
+
>>> cid = engine.calculate_pickle_cid(data, nonce=12345)
|
|
1806
|
+
>>> print(cid)
|
|
1807
|
+
QmHash123ABC...
|
|
1808
|
+
|
|
1809
|
+
>>> # Use custom filename
|
|
1810
|
+
>>> cid = engine.calculate_pickle_cid(data, nonce=12345, fn="model.pkl")
|
|
1811
|
+
>>> print(cid)
|
|
1812
|
+
QmHash456DEF...
|
|
1813
|
+
"""
|
|
1814
|
+
# Serialize pickle data
|
|
1815
|
+
try:
|
|
1816
|
+
import pickle
|
|
1817
|
+
pickle_data = pickle.dumps(data)
|
|
1818
|
+
except (TypeError, ValueError, pickle.PicklingError) as e:
|
|
1819
|
+
raise ValueError(f"Data cannot be pickled: {e}")
|
|
1820
|
+
|
|
1821
|
+
if show_logs:
|
|
1822
|
+
self.Pd(f"Pickled data of type: {type(data)}, size: {len(pickle_data)} bytes")
|
|
1823
|
+
|
|
1824
|
+
# Use custom filename or generate unique one
|
|
1825
|
+
if fn is None:
|
|
1826
|
+
fn = self._get_unique_or_complete_upload_name(fn=fn, suffix=".pkl")
|
|
1827
|
+
|
|
1828
|
+
if show_logs:
|
|
1829
|
+
self.Pd(f"Calculating CID for pickle data with {len(pickle_data)} bytes, filename: {fn}")
|
|
1830
|
+
|
|
1831
|
+
# Create temporary file for pickle data with the desired filename in a unique directory
|
|
1832
|
+
unique_dir = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex)
|
|
1833
|
+
os.makedirs(unique_dir, exist_ok=True)
|
|
1834
|
+
tmp_pickle_path = os.path.join(unique_dir, fn)
|
|
1835
|
+
|
|
1836
|
+
file_cid = None
|
|
1837
|
+
try:
|
|
1838
|
+
# Write pickle data to temporary file
|
|
1839
|
+
with open(tmp_pickle_path, "wb") as f:
|
|
1840
|
+
f.write(pickle_data)
|
|
1841
|
+
|
|
1842
|
+
if show_logs:
|
|
1843
|
+
self.Pd(f"About to call calculate_file_cid with: file_path={tmp_pickle_path}, nonce={nonce}, secret={secret[:10] if secret else 'None'}...")
|
|
1844
|
+
|
|
1845
|
+
# Use calculate_file_cid to get the CID
|
|
1846
|
+
file_cid = self.calculate_file_cid(
|
|
1847
|
+
file_path=tmp_pickle_path,
|
|
1848
|
+
nonce=nonce,
|
|
1849
|
+
secret=secret,
|
|
1850
|
+
show_logs=True # Don't show logs from calculate_file_cid
|
|
1851
|
+
)
|
|
1852
|
+
|
|
1853
|
+
if show_logs:
|
|
1854
|
+
self.Pd(f"calculate_file_cid returned CID: {file_cid}")
|
|
1855
|
+
|
|
1856
|
+
finally:
|
|
1857
|
+
# Clean up temporary pickle file and directory
|
|
1858
|
+
if os.path.exists(tmp_pickle_path):
|
|
1859
|
+
os.remove(tmp_pickle_path)
|
|
1860
|
+
if os.path.exists(unique_dir):
|
|
1861
|
+
os.rmdir(unique_dir)
|
|
1862
|
+
|
|
1863
|
+
return file_cid
|
|
1151
1864
|
|
|
1152
1865
|
|
|
1153
1866
|
# Start/stop IPFS methods (R1FS API)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ratio1
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.90
|
|
4
4
|
Summary: `ratio1` or Ration1 SDK is the Python SDK required for client app development for the Ratio1 ecosystem
|
|
5
5
|
Project-URL: Homepage, https://github.com/Ratio1/ratio1_sdk
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Ratio1/ratio1_sdk/issues
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
ratio1/__init__.py,sha256=YimqgDbjLuywsf8zCWE0EaUXH4MBUrqLxt0TDV558hQ,632
|
|
2
|
-
ratio1/_ver.py,sha256=
|
|
2
|
+
ratio1/_ver.py,sha256=nbr8aPsp3zz0PIc19Y-oBy7PiewAXGAgyk1132t5yQ0,331
|
|
3
3
|
ratio1/base_decentra_object.py,sha256=iXvAAf6wPnGWzeeiRfwLojVoan-m1e_VsyPzjUQuENo,4492
|
|
4
4
|
ratio1/plugins_manager_mixin.py,sha256=X1JdGLDz0gN1rPnTN_5mJXR8JmqoBFQISJXmPR9yvCo,11106
|
|
5
5
|
ratio1/base/__init__.py,sha256=hACh83_cIv7-PwYMM3bQm2IBmNqiHw-3PAfDfAEKz9A,259
|
|
@@ -48,7 +48,7 @@ ratio1/const/evm_net.py,sha256=9is8Gx2_xtKlWRNAZTNAmmaA33c30xvpBT-zL4AtIuM,16774
|
|
|
48
48
|
ratio1/const/formatter.py,sha256=AW3bWlqf39uaqV4BBUuW95qKYfF2OkkU4f9hy3kSVhM,200
|
|
49
49
|
ratio1/const/heartbeat.py,sha256=Z_n87yI-L4vlBmlHZhkruysEJV5JVbXLQlGI26R42C4,3197
|
|
50
50
|
ratio1/const/misc.py,sha256=VDCwwpf5bl9ltx9rzT2WPVP8B3mZFRufU1tSS5MO240,413
|
|
51
|
-
ratio1/const/payload.py,sha256=
|
|
51
|
+
ratio1/const/payload.py,sha256=lhHzkz6d3G7rVtMCitUHgjT85k4TE58K9Y5JteT_u7c,7910
|
|
52
52
|
ratio1/const/plugins/deeploy_const.py,sha256=8QcIf7zo_gSC6SdHqQVecn4yOUKCPMXzHC9zgGW5-3E,13314
|
|
53
53
|
ratio1/default/__init__.py,sha256=ozU6CMMuWl0LhG8Ae3LrZ65a6tLrptfscVYGf83zjxM,46
|
|
54
54
|
ratio1/default/instance/__init__.py,sha256=JQiqTFm_U5__z4bTlvT033hUOSIDCtrQ-evMrhT6JdA,741
|
|
@@ -70,7 +70,7 @@ ratio1/io_formatter/default/aixp1.py,sha256=MX0TeUR4APA-qN3vUC6uzcz8Pssz5lgrQWo7
|
|
|
70
70
|
ratio1/io_formatter/default/default.py,sha256=gEy78cP2D5s0y8vQh4aHuxqz7D10gGfuiKF311QhrpE,494
|
|
71
71
|
ratio1/ipfs/__init__.py,sha256=vXEDLUNUO6lOTMGa8iQ9Zf7ajIQq9GZuvYraAHt3meE,38
|
|
72
72
|
ratio1/ipfs/ifps_keygen,sha256=PcoYuo4c89_C9FWrKq9K_28ruhKqnxNn1s3nLHiF1tc,879
|
|
73
|
-
ratio1/ipfs/r1fs.py,sha256=
|
|
73
|
+
ratio1/ipfs/r1fs.py,sha256=5Uo_adugcGTg8kVsev9kmyUSSzh9FdM803jPdu8QEWw,72273
|
|
74
74
|
ratio1/ipfs/ipfs_setup/ipfs.service,sha256=isTJQsktPy4i1yaDA9AC1OKdlTYvsCCRRAVX-EmGqAs,248
|
|
75
75
|
ratio1/ipfs/ipfs_setup/launch_service.sh,sha256=GWhZyNqtohLxJg8Q_c8YnNZduu1ddXDU-IFRRMaEyiY,141
|
|
76
76
|
ratio1/ipfs/ipfs_setup/restart.sh,sha256=9xHMgkUoAMI25jeaoDVFbCa_LjojYm3ubljW58RatKE,22
|
|
@@ -105,8 +105,8 @@ ratio1/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_LtMyCY,10
|
|
|
105
105
|
ratio1/utils/config.py,sha256=Elfkl7W4aDMvB5WZLiYlPXrecBncgTxb4hcKhQedMzI,10111
|
|
106
106
|
ratio1/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
|
|
107
107
|
ratio1/utils/oracle_sync/oracle_tester.py,sha256=aJOPcZhtbw1XPqsFG4qYpfv2Taj5-qRXbwJzrPyeXDE,27465
|
|
108
|
-
ratio1-3.4.
|
|
109
|
-
ratio1-3.4.
|
|
110
|
-
ratio1-3.4.
|
|
111
|
-
ratio1-3.4.
|
|
112
|
-
ratio1-3.4.
|
|
108
|
+
ratio1-3.4.90.dist-info/METADATA,sha256=qoggjAXJC2ANieuj4wXCf7bpNYCFmJe4j2KQJIQwSa0,12255
|
|
109
|
+
ratio1-3.4.90.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
110
|
+
ratio1-3.4.90.dist-info/entry_points.txt,sha256=DR_olREzU1egwmgek3s4GfQslBi-KR7lXsd4ap0TFxE,46
|
|
111
|
+
ratio1-3.4.90.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
|
|
112
|
+
ratio1-3.4.90.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|