sdata 0.24.0__tar.gz → 0.25.2__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.
- {sdata-0.24.0/sdata.egg-info → sdata-0.25.2}/PKG-INFO +25 -6
- {sdata-0.24.0 → sdata-0.25.2}/README.md +1 -1
- sdata-0.25.2/pyproject.toml +3 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/__init__.py +1 -1
- {sdata-0.24.0 → sdata-0.25.2}/sdata/data.py +77 -19
- sdata-0.25.2/sdata/iolib/hashtree.py +264 -0
- sdata-0.25.2/sdata/iolib/json1sqlitestore.py +596 -0
- sdata-0.25.2/sdata/iolib/jsonsqlitestore.py +691 -0
- sdata-0.25.2/sdata/iolib/owncloudfs.py +105 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/vault.py +17 -4
- {sdata-0.24.0 → sdata-0.25.2}/sdata/metadata.py +3 -3
- sdata-0.25.2/sdata/node.py +145 -0
- sdata-0.25.2/sdata/parameter.py +411 -0
- sdata-0.25.2/sdata/parameters_deprecated.py +394 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/suuid.py +18 -0
- {sdata-0.24.0 → sdata-0.25.2/sdata.egg-info}/PKG-INFO +25 -6
- {sdata-0.24.0 → sdata-0.25.2}/sdata.egg-info/SOURCES.txt +11 -0
- sdata-0.25.2/tests/iolib/test_hasttree.py +67 -0
- sdata-0.25.2/tests/iolib/test_jsonsqlitestore.py +156 -0
- sdata-0.25.2/tests/test_parameter.py +118 -0
- {sdata-0.24.0 → sdata-0.25.2}/LICENSE-MIT +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/MANIFEST.in +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/blob.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/default.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/dictionary.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/mapping.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/merge.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/attrdict/mixins.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_common.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_dump.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_exceptions.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_exif.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_insert.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_load.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_remove.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_transplant.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/_webp.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/piexif/helper.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/collections/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/collections/rangebucketmap.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/collections/rangemap.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/collections/rangeset.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/range/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/range/cut.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/ranger/range/range.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/semver.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/simple_graph_db/LICENSE +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/simple_graph_db/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/simple_graph_db/database.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/simple_graph_db/schema.sql +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sobol_seq.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sortedcontainers/LICENSE +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sortedcontainers/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sortedcontainers/sorteddict.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sortedcontainers/sortedlist.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sortedcontainers/sortedset.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/sqlitedict.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/timeflake/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/timeflake/flake.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/contrib/timeflake/utils.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/doe.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/experiments/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/experiments/ks2.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/experiments/lapshear.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/experiments/material.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/image.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/db.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/hdf.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/isomme.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/pgp.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/pud.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/iolib/simple_signature.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/timestamp.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/tools.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata/workbook.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata.egg-info/dependency_links.txt +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata.egg-info/requires.txt +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/sdata.egg-info/top_level.txt +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/setup.cfg +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/setup.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/iolib/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/iolib/pud/__init__.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/iolib/pud/test_pud.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/iolib/test_hdf.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_attribute.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_blob.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_data.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_image.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_io.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_ks2.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_metadata.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_sqlite.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_suuid.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_table.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_testprogram.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_timeflake.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_timestamp.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_tools.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_vault.py +0 -0
- {sdata-0.24.0 → sdata-0.25.2}/tests/test_workbook.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sdata
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.2
|
|
4
4
|
Summary: a structured data format
|
|
5
5
|
Home-page: https://github.com/lepy/sdata
|
|
6
6
|
Author: Lepy
|
|
@@ -9,7 +9,6 @@ Maintainer: Lepy
|
|
|
9
9
|
Maintainer-email: lepy@tuta.io
|
|
10
10
|
License: MIT/Apache-2.0
|
|
11
11
|
Keywords: open data
|
|
12
|
-
Platform: UNKNOWN
|
|
13
12
|
Classifier: Development Status :: 3 - Alpha
|
|
14
13
|
Classifier: Intended Audience :: Developers
|
|
15
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -26,6 +25,28 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
26
25
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
License-File: LICENSE-MIT
|
|
28
|
+
Requires-Dist: numpy
|
|
29
|
+
Requires-Dist: pandas
|
|
30
|
+
Requires-Dist: tabulate
|
|
31
|
+
Requires-Dist: xlrd
|
|
32
|
+
Requires-Dist: openpyxl
|
|
33
|
+
Requires-Dist: xlsxwriter
|
|
34
|
+
Requires-Dist: pytz
|
|
35
|
+
Requires-Dist: requests
|
|
36
|
+
Requires-Dist: Pillow
|
|
37
|
+
Dynamic: author
|
|
38
|
+
Dynamic: author-email
|
|
39
|
+
Dynamic: classifier
|
|
40
|
+
Dynamic: description
|
|
41
|
+
Dynamic: description-content-type
|
|
42
|
+
Dynamic: home-page
|
|
43
|
+
Dynamic: keywords
|
|
44
|
+
Dynamic: license
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
Dynamic: maintainer
|
|
47
|
+
Dynamic: maintainer-email
|
|
48
|
+
Dynamic: requires-dist
|
|
49
|
+
Dynamic: summary
|
|
29
50
|
|
|
30
51
|
|
|
31
52
|
[](https://badge.fury.io/py/sdata)
|
|
@@ -86,7 +107,7 @@ force force 1.234 float kN x force
|
|
|
86
107
|
|
|
87
108
|
|
|
88
109
|
* [Das sdata-Format - slides](https://lepy.github.io/sdata/ipynb/Das_sdata_Format.slides.html#)
|
|
89
|
-
|
|
110
|
+
* https://deepwiki.com/lepy/sdata
|
|
90
111
|
|
|
91
112
|
## Demo App
|
|
92
113
|
|
|
@@ -128,5 +149,3 @@ Try to paste some Excel-Data in the forms ...
|
|
|
128
149
|
* [sdata](https://doi.org/10.5281/zenodo.4311396)
|
|
129
150
|
* Ingolf Lepenies. (2020, December 8). sdata - a structured data format (Version 0.8.4). Zenodo. http://doi.org/10.5281/zenodo.4311397
|
|
130
151
|
|
|
131
|
-
|
|
132
|
-
|
|
@@ -265,6 +265,13 @@ class Data(object):
|
|
|
265
265
|
s.update(tablestr)
|
|
266
266
|
return s.hexdigest()
|
|
267
267
|
|
|
268
|
+
@property
|
|
269
|
+
def sha3_256_metadata(self):
|
|
270
|
+
s = hashlib.sha3_256()
|
|
271
|
+
metadatastr = self.metadata.to_json().encode(errors="replace")
|
|
272
|
+
s.update(metadatastr)
|
|
273
|
+
return s.hexdigest()
|
|
274
|
+
|
|
268
275
|
@property
|
|
269
276
|
def sha3_256(self):
|
|
270
277
|
"""Return a SHA3 hash of the sData object with a hashbit length of 32 bytes.
|
|
@@ -281,8 +288,8 @@ class Data(object):
|
|
|
281
288
|
metadatastr = self.metadata.to_json().encode(errors="replace")
|
|
282
289
|
s.update(metadatastr)
|
|
283
290
|
if self.table is not None:
|
|
284
|
-
tablestr = self.table.to_json().encode(errors="replace")
|
|
285
|
-
s.update(
|
|
291
|
+
# tablestr = self.table.to_json().encode(errors="replace")
|
|
292
|
+
s.update(self.sha3_256_table.encode(errors="replace"))
|
|
286
293
|
s.update(self.description.encode(errors="replace"))
|
|
287
294
|
return s.hexdigest()
|
|
288
295
|
|
|
@@ -831,6 +838,62 @@ class Data(object):
|
|
|
831
838
|
"""
|
|
832
839
|
return [(x.name, x.dir()) for x in self.group.values()]
|
|
833
840
|
|
|
841
|
+
def to_dataframe(self):
|
|
842
|
+
"""Export the DataFrame with additional metadata and description.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
pandas.DataFrame: A copy of the DataFrame with added attributes.
|
|
846
|
+
"""
|
|
847
|
+
df = self.df.copy()
|
|
848
|
+
df.attrs["!sdata"] = {
|
|
849
|
+
"metadata": self.metadata.to_dict(),
|
|
850
|
+
"description": self.description
|
|
851
|
+
}
|
|
852
|
+
return df
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def to_parquet(self, filepath=None, **kwargs):
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
"""
|
|
859
|
+
engine = kwargs.get("engine", "pyarrow")
|
|
860
|
+
df = self.df.copy()
|
|
861
|
+
#df["!sdata_index"] = df.index
|
|
862
|
+
#df.reset_index(inplace=True)
|
|
863
|
+
df.attrs["!sdata"] = {"metadata": self.metadata.to_dict(),
|
|
864
|
+
"description": self.description}
|
|
865
|
+
df.to_parquet(filepath, engine=engine)
|
|
866
|
+
|
|
867
|
+
@classmethod
|
|
868
|
+
def from_parquet(cls, filepath):
|
|
869
|
+
"""load data from parquet file
|
|
870
|
+
|
|
871
|
+
:param filepath:
|
|
872
|
+
:return: Data object
|
|
873
|
+
"""
|
|
874
|
+
try:
|
|
875
|
+
if os.path.exists(filepath):
|
|
876
|
+
df = pd.read_parquet(filepath)
|
|
877
|
+
tt = cls(name=filepath)
|
|
878
|
+
tt.table = df
|
|
879
|
+
|
|
880
|
+
attrs = df.attrs.get("!sdata")
|
|
881
|
+
try:
|
|
882
|
+
tt.metadata = tt.metadata.from_dict(attrs["metadata"])
|
|
883
|
+
except:
|
|
884
|
+
logger.warning(f"ignore metadata {filepath}")
|
|
885
|
+
try:
|
|
886
|
+
tt.description = attrs.get("description")
|
|
887
|
+
except:
|
|
888
|
+
logger.warning(f"ignore description {filepath}")
|
|
889
|
+
if attrs is not None:
|
|
890
|
+
tt.df.attrs.pop("!sdata")
|
|
891
|
+
else:
|
|
892
|
+
raise Exception("parquet file '{}' not available".format(filepath))
|
|
893
|
+
return tt
|
|
894
|
+
except Exception as exp:
|
|
895
|
+
raise
|
|
896
|
+
|
|
834
897
|
def to_xlsx_byteio(self):
|
|
835
898
|
"""get xlsx as byteio
|
|
836
899
|
|
|
@@ -941,36 +1004,31 @@ class Data(object):
|
|
|
941
1004
|
"""
|
|
942
1005
|
try:
|
|
943
1006
|
if os.path.exists(filepath):
|
|
944
|
-
wb = openpyxl.load_workbook(filename=filepath)
|
|
945
|
-
sheetnames = wb.sheetnames
|
|
946
|
-
|
|
947
1007
|
tt = cls(name=filepath)
|
|
948
1008
|
|
|
949
1009
|
# read df
|
|
950
|
-
|
|
1010
|
+
try:
|
|
951
1011
|
tt.table = pd.read_excel(filepath, sheet_name="table", index_col=0)
|
|
952
|
-
|
|
1012
|
+
except:
|
|
953
1013
|
logger.info("no table data in '{}'".format(filepath))
|
|
954
1014
|
dfm = pd.read_excel(filepath, sheet_name="metadata")
|
|
955
|
-
dfm
|
|
1015
|
+
dfm.set_index(dfm.name.values, inplace=True)
|
|
956
1016
|
# dfm["value"] = dfm["value"].replace(np.nan, None)
|
|
957
1017
|
dfm["description"] = dfm["description"].replace(np.nan, '')
|
|
958
1018
|
dfm["label"] = dfm["label"].replace(np.nan, '')
|
|
959
|
-
|
|
1019
|
+
|
|
1020
|
+
# read metadata
|
|
960
1021
|
tt.metadata = tt.metadata.from_dataframe(dfm)
|
|
1022
|
+
for attr in tt.metadata.sdata_attributes.values():
|
|
1023
|
+
if attr.value == "nan":
|
|
1024
|
+
attr.value = ""
|
|
961
1025
|
|
|
962
1026
|
# read description
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
cells.append(cell.value)
|
|
968
|
-
else:
|
|
969
|
-
cells.append("")
|
|
970
|
-
tt.description = "\n".join(cells)
|
|
971
|
-
else:
|
|
1027
|
+
try:
|
|
1028
|
+
dfd = pd.read_excel(filepath, sheet_name="description", header=None)
|
|
1029
|
+
tt.description = "\n".join(dfd[0])
|
|
1030
|
+
except:
|
|
972
1031
|
logger.info("no description in '{}'".format(filepath))
|
|
973
|
-
|
|
974
1032
|
return tt
|
|
975
1033
|
else:
|
|
976
1034
|
raise Exception("excel file '{}' not available".format(filepath))
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
NAMESPACE_SDATA = uuid.uuid5(uuid.NAMESPACE_DNS, "sdata")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def stable_hash_from_list(input_list):
|
|
9
|
+
"""
|
|
10
|
+
data = [3.14159265, 42, "hello", 2.7182818]
|
|
11
|
+
hashstr = sdata.iolib.hashtree.stable_hash_from_list(data)
|
|
12
|
+
hashstr == "f54068f9b31cb94d56b454d36c1458877e5e45589684038b5c60097ad45b9052"
|
|
13
|
+
"""
|
|
14
|
+
# Konvertiere die Liste in eine normalisierte Zeichenkette
|
|
15
|
+
normalized_str = str([
|
|
16
|
+
round(item, 8) if isinstance(item, float) else item
|
|
17
|
+
for item in input_list
|
|
18
|
+
])
|
|
19
|
+
# Erstelle einen Hash mit hashlib (z. B. SHA256)
|
|
20
|
+
hash_object = hashlib.sha256(normalized_str.encode('utf-8'))
|
|
21
|
+
return hash_object.hexdigest()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_uuid_from_hash(hash_value):
|
|
25
|
+
"""
|
|
26
|
+
data = [3.14159265, 42, "hello", 2.7182818]
|
|
27
|
+
uid = sdata.iolib.hashtree.stable_uuid_from_list(data)
|
|
28
|
+
uid.hex == "f09e3370d8f953398366753ca7e16eb9"
|
|
29
|
+
"""
|
|
30
|
+
# Konvertiere den Hashwert in einen String (falls nötig)
|
|
31
|
+
hash_string = str(hash_value)
|
|
32
|
+
namespace = uuid.uuid5(uuid.NAMESPACE_DNS, "sdata")
|
|
33
|
+
return uuid.uuid5(namespace, hash_string).hex
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def stable_uuid_from_list(input_list):
|
|
37
|
+
hashstr = stable_hash_from_list(input_list)
|
|
38
|
+
return generate_uuid_from_hash(hashstr)
|
|
39
|
+
|
|
40
|
+
def uuids_from_hashes(hashes):
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
return [generate_uuid_from_hash(hashstr) for hashstr in hashes]
|
|
45
|
+
|
|
46
|
+
def stable_hash_from_nested_list(nested_list):
|
|
47
|
+
"""
|
|
48
|
+
nested_list = [[3.14159265, 42, "hello", 2.7182818],
|
|
49
|
+
[1, 1.2,"ssd"],
|
|
50
|
+
["a"],
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
['f54068f9b31cb94d56b454d36c1458877e5e45589684038b5c60097ad45b9052',
|
|
54
|
+
'ce6bc57403f6d39b1dd30fe7aca428d5de956758551facfbca290500ad9100b3',
|
|
55
|
+
'a326618a43c0b61aff497f4d8a82f4857fe952c5fae156ab94facf4adbf7dcb4']
|
|
56
|
+
"""
|
|
57
|
+
return [stable_hash_from_list(x) for x in nested_list]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def stable_uuids_from_nested_list(nested_list):
|
|
61
|
+
"""
|
|
62
|
+
nested_list = [[3.14159265, 42, "hello", 2.7182818],
|
|
63
|
+
[1, 1.2,"ssd"],
|
|
64
|
+
["a"],
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
['f09e3370d8f953398366753ca7e16eb9',
|
|
68
|
+
'b0465a6b35ce5fd28338e04233c422d7',
|
|
69
|
+
'68bb365dbf5c556faf848a6c8c72b205']
|
|
70
|
+
"""
|
|
71
|
+
return [stable_uuid_from_list(x) for x in nested_list]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def generate_combined_hash(parent_hash, own_hash):
|
|
75
|
+
"""
|
|
76
|
+
Berechnet einen neuen Hash basierend auf einem Parent-Hash und einem eigenen Hash.
|
|
77
|
+
Falls der Parent-Hash None ist, wird ein leerer String verwendet.
|
|
78
|
+
|
|
79
|
+
:param parent_hash: Der Hash des Elternknotens (String, None für Root).
|
|
80
|
+
:param own_hash: Der eigene Hash des aktuellen Knotens (String).
|
|
81
|
+
:return: Der kombinierte Hash (String).
|
|
82
|
+
"""
|
|
83
|
+
parent_hash = parent_hash or "" # Falls None, verwende leeren String
|
|
84
|
+
combined = parent_hash + own_hash
|
|
85
|
+
return hashlib.sha256(combined.encode('utf-8')).hexdigest()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cumulative_hash_list(hashes):
|
|
89
|
+
"""
|
|
90
|
+
Berechnet kumulative Hashes basierend auf einer Liste, wobei der parent_hash
|
|
91
|
+
jeweils der vorherige Hash in der Liste ist.
|
|
92
|
+
|
|
93
|
+
:param hashes: Liste von Hashes (Strings).
|
|
94
|
+
:return: Liste der kumulativ berechneten Hashes.
|
|
95
|
+
"""
|
|
96
|
+
if not hashes:
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
cumulative_hashes = [hashes[0]] # Der erste Hash ist unverändert
|
|
100
|
+
for i in range(1, len(hashes)):
|
|
101
|
+
parent_hash = cumulative_hashes[i - 1]
|
|
102
|
+
current_hash = hashes[i]
|
|
103
|
+
cumulative_hashes.append(generate_combined_hash(parent_hash, current_hash))
|
|
104
|
+
return cumulative_hashes
|
|
105
|
+
|
|
106
|
+
def stable_cum_uuids_from_nested_list(nested_list):
|
|
107
|
+
"""calculate stable uuids from cumulative hashes of a nested list
|
|
108
|
+
nested_list = [[3.14159265, 42, "hello", 2.7182818],
|
|
109
|
+
[1, 1.2,"ssd"],
|
|
110
|
+
["a"],
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
['f09e3370d8f953398366753ca7e16eb9',
|
|
114
|
+
'b8f6eb73756c5d3d8f7456003bf0dc6d',
|
|
115
|
+
'f6dd12b913135291b58bcd2f5f63f157']
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
cum_hashes = stable_cum_hashes_from_nested_list(nested_list)
|
|
120
|
+
uuids = uuids_from_hashes(cum_hashes)
|
|
121
|
+
return uuids
|
|
122
|
+
|
|
123
|
+
def stable_cum_uuid_from_nested_list(nested_list):
|
|
124
|
+
"""calculate stable uuid from cumulative hashes of a nested list
|
|
125
|
+
nested_list = [[3.14159265, 42, "hello", 2.7182818],
|
|
126
|
+
[1, 1.2,"ssd"],
|
|
127
|
+
["a"],
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
'f6dd12b913135291b58bcd2f5f63f157'
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
hashes = stable_cum_hashes_from_nested_list(nested_list)
|
|
136
|
+
cum_uuid = generate_uuid_from_hash(hashes[-1])
|
|
137
|
+
return cum_uuid
|
|
138
|
+
|
|
139
|
+
def stable_cum_hashes_from_nested_list(nested_list):
|
|
140
|
+
"""
|
|
141
|
+
nested_list = [[3.14159265, 42, "hello", 2.7182818],
|
|
142
|
+
[1, 1.2,"ssd"],
|
|
143
|
+
["a"],
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
['3a316d6d3226f84c1e46e4447fa8d5fd800bff4a1bc6498152523cd4a602b69b',
|
|
147
|
+
'9f8ba3b772342d1f57ae6fe37e7e0da8f93f5ccacaf943d605d254d57489c7e3',
|
|
148
|
+
'03da67b7f8157f7413861ffa25069581ae5d95d59eeb0c2f599f48539dcdf38c']
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
hashes = stable_hash_from_nested_list(nested_list)
|
|
153
|
+
cum_hashes = cumulative_hash_list(hashes)
|
|
154
|
+
return cum_hashes
|
|
155
|
+
|
|
156
|
+
class TreeNode:
|
|
157
|
+
def __init__(self, name, parent=None):
|
|
158
|
+
self.name = name # Der Name des Knotens
|
|
159
|
+
self.children = {} # Kinderknoten
|
|
160
|
+
self.parent = parent # Elternknoten
|
|
161
|
+
|
|
162
|
+
def __lt__(self, other):
|
|
163
|
+
"""
|
|
164
|
+
Ermöglicht die Sortierung von TreeNode-Objekten basierend auf ihrem Namen.
|
|
165
|
+
"""
|
|
166
|
+
return self.name < other.name
|
|
167
|
+
|
|
168
|
+
def get_child(self, path):
|
|
169
|
+
"""
|
|
170
|
+
Navigiert den Pfad und gibt den Zielknoten zurück, falls vorhanden.
|
|
171
|
+
:param path: Ein Pfad in der Form "child1/child2".
|
|
172
|
+
:return: Der Zielknoten oder None, falls der Pfad nicht existiert.
|
|
173
|
+
"""
|
|
174
|
+
path_list = path.strip("/").split("/") # Pfad in eine Liste umwandeln
|
|
175
|
+
current_node = self
|
|
176
|
+
for part in path_list:
|
|
177
|
+
if part in current_node.children:
|
|
178
|
+
current_node = current_node.children[part]
|
|
179
|
+
else:
|
|
180
|
+
return None # Pfad existiert nicht
|
|
181
|
+
return current_node
|
|
182
|
+
|
|
183
|
+
def add_path(self, path):
|
|
184
|
+
path_list = path.strip("/").split("/")
|
|
185
|
+
self.add_path_list(path_list)
|
|
186
|
+
|
|
187
|
+
def add_path_list(self, path_list):
|
|
188
|
+
if not path_list:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
head, *tail = path_list
|
|
192
|
+
if head not in self.children:
|
|
193
|
+
self.children[head] = TreeNode(head, parent=self)
|
|
194
|
+
self.children[head].add_path_list(tail)
|
|
195
|
+
|
|
196
|
+
def repr(self, level=0):
|
|
197
|
+
ret = " " * level + repr(self.name) + "\n"
|
|
198
|
+
for child in self.children.values():
|
|
199
|
+
ret += child.__repr__(level + 1)
|
|
200
|
+
return ret
|
|
201
|
+
|
|
202
|
+
def __repr__(self):
|
|
203
|
+
return self.full_name()
|
|
204
|
+
|
|
205
|
+
def __str__(self):
|
|
206
|
+
return self.full_name()
|
|
207
|
+
|
|
208
|
+
def to_dict(self):
|
|
209
|
+
"""
|
|
210
|
+
Konvertiert den Baum in ein verschachteltes Dictionary.
|
|
211
|
+
"""
|
|
212
|
+
return {
|
|
213
|
+
"name": self.name,
|
|
214
|
+
"children": {name: child.to_dict() for name, child in self.children.items()}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
def to_json(self):
|
|
218
|
+
"""
|
|
219
|
+
Exportiert den Baum in JSON-Format.
|
|
220
|
+
"""
|
|
221
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
222
|
+
|
|
223
|
+
def print_tree(self, padding="", is_last=True, is_root=True):
|
|
224
|
+
"""
|
|
225
|
+
Druckt den Baum in einer visuellen Struktur mit Verzweigungen.
|
|
226
|
+
:param padding: Aktuelle Einrückung.
|
|
227
|
+
:param is_last: Ob der aktuelle Knoten das letzte Kind ist.
|
|
228
|
+
:param is_root: Ob der aktuelle Knoten der Root-Knoten ist.
|
|
229
|
+
"""
|
|
230
|
+
if is_root:
|
|
231
|
+
print(self.name) # Root-Knoten ohne Verzweigungssymbol
|
|
232
|
+
else:
|
|
233
|
+
branch = "└─" if is_last else "├─"
|
|
234
|
+
print(padding + branch + self.name)
|
|
235
|
+
padding += " " if is_last else "│ "
|
|
236
|
+
|
|
237
|
+
children = sorted(self.children.values()) # Alphabetische Sortierung
|
|
238
|
+
for count, child in enumerate(children):
|
|
239
|
+
child.print_tree(padding, count == (len(children) - 1), is_root=False)
|
|
240
|
+
|
|
241
|
+
def full_name(self):
|
|
242
|
+
"""
|
|
243
|
+
Gibt den vollständigen Pfad des Knotens zurück, von der Wurzel bis zu diesem Knoten.
|
|
244
|
+
:return: Ein String, der den vollständigen Pfad darstellt.
|
|
245
|
+
"""
|
|
246
|
+
if self.parent is None:
|
|
247
|
+
return self.name # Root-Knoten
|
|
248
|
+
return f"{self.parent.full_name()}/{self.name}"
|
|
249
|
+
|
|
250
|
+
def get_subtree_by_name(self, name):
|
|
251
|
+
"""
|
|
252
|
+
Sucht im Baum nach einem Knoten mit dem angegebenen Namen.
|
|
253
|
+
:param name: Der Name des gesuchten Knotens.
|
|
254
|
+
:return: Der Knoten (TreeNode), wenn gefunden, sonst None.
|
|
255
|
+
"""
|
|
256
|
+
if self.name == name:
|
|
257
|
+
return self # Aktueller Knoten passt
|
|
258
|
+
|
|
259
|
+
for child in self.children.values():
|
|
260
|
+
result = child.get_subtree_by_name(name)
|
|
261
|
+
if result:
|
|
262
|
+
return result # Knoten gefunden
|
|
263
|
+
|
|
264
|
+
return None # Kein Knoten mit dem Namen gefunden
|