sdata 1.5.0__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.
Files changed (98) hide show
  1. sdata/__init__.py +140 -0
  2. sdata/base.py +684 -0
  3. sdata/contrib/__init__.py +0 -0
  4. sdata/contrib/piexif/__init__.py +12 -0
  5. sdata/contrib/piexif/_common.py +94 -0
  6. sdata/contrib/piexif/_dump.py +346 -0
  7. sdata/contrib/piexif/_exceptions.py +2 -0
  8. sdata/contrib/piexif/_exif.py +638 -0
  9. sdata/contrib/piexif/_insert.py +60 -0
  10. sdata/contrib/piexif/_load.py +270 -0
  11. sdata/contrib/piexif/_remove.py +56 -0
  12. sdata/contrib/piexif/_transplant.py +45 -0
  13. sdata/contrib/piexif/_webp.py +240 -0
  14. sdata/contrib/piexif/helper.py +66 -0
  15. sdata/contrib/ranger/__init__.py +8 -0
  16. sdata/contrib/ranger/collections/__init__.py +0 -0
  17. sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
  18. sdata/contrib/ranger/collections/rangemap.py +396 -0
  19. sdata/contrib/ranger/collections/rangeset.py +483 -0
  20. sdata/contrib/ranger/range/__init__.py +0 -0
  21. sdata/contrib/ranger/range/cut.py +285 -0
  22. sdata/contrib/ranger/range/range.py +644 -0
  23. sdata/contrib/sobol_seq.py +394 -0
  24. sdata/contrib/sortedcontainers/LICENSE +13 -0
  25. sdata/contrib/sortedcontainers/__init__.py +74 -0
  26. sdata/contrib/sortedcontainers/sorteddict.py +789 -0
  27. sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
  28. sdata/contrib/sortedcontainers/sortedset.py +733 -0
  29. sdata/contrib/sqlitedict.py +598 -0
  30. sdata/contrib/timeflake/__init__.py +40 -0
  31. sdata/contrib/timeflake/flake.py +69 -0
  32. sdata/contrib/timeflake/utils.py +47 -0
  33. sdata/deprecated/__init__.py +10 -0
  34. sdata/deprecated/data.py +1503 -0
  35. sdata/did/__init__.py +129 -0
  36. sdata/did/_http.py +67 -0
  37. sdata/did/base58btc.py +49 -0
  38. sdata/did/did_github.py +152 -0
  39. sdata/did/did_peer4.py +155 -0
  40. sdata/did/did_web.py +222 -0
  41. sdata/did/diddoc.py +75 -0
  42. sdata/did/ed25519.py +225 -0
  43. sdata/did/eddsa.py +107 -0
  44. sdata/did/errors.py +40 -0
  45. sdata/did/issue_vc.py +193 -0
  46. sdata/did/jose.py +126 -0
  47. sdata/did/keys.py +104 -0
  48. sdata/did/utils_didkey.py +161 -0
  49. sdata/did/verify_vp.py +158 -0
  50. sdata/doe.py +101 -0
  51. sdata/dtypes.py +510 -0
  52. sdata/experiments/__init__.py +331 -0
  53. sdata/experiments/blob0.py +131 -0
  54. sdata/experiments/file.py +42 -0
  55. sdata/experiments/filegroup.py +144 -0
  56. sdata/experiments/image_old.py +313 -0
  57. sdata/experiments/ks2.py +15 -0
  58. sdata/experiments/lapshear.py +8 -0
  59. sdata/experiments/material.py +7 -0
  60. sdata/experiments/parameters_deprecated.py +394 -0
  61. sdata/experiments/workbook.py +226 -0
  62. sdata/format.py +125 -0
  63. sdata/imagemeta.py +533 -0
  64. sdata/interactive.py +118 -0
  65. sdata/iolib/__init__.py +51 -0
  66. sdata/iolib/hashtree.py +264 -0
  67. sdata/iolib/json1sqlitestore.py +554 -0
  68. sdata/iolib/jsonsqlitestore.py +704 -0
  69. sdata/iolib/pud.py +242 -0
  70. sdata/iolib/reader.py +262 -0
  71. sdata/iolib/writer.py +419 -0
  72. sdata/metadata.py +882 -0
  73. sdata/parameter.py +414 -0
  74. sdata/schema.py +273 -0
  75. sdata/sclass/__init__.py +111 -0
  76. sdata/sclass/bfo_classes.py +362 -0
  77. sdata/sclass/blob.py +362 -0
  78. sdata/sclass/content.py +87 -0
  79. sdata/sclass/dataframe.py +1251 -0
  80. sdata/sclass/dataframegroup.py +187 -0
  81. sdata/sclass/dependency_graph.py +85 -0
  82. sdata/sclass/filereference.py +124 -0
  83. sdata/sclass/image.py +218 -0
  84. sdata/sclass/lazy_namespace.py +155 -0
  85. sdata/sclass/process.py +383 -0
  86. sdata/sclass/process_graph.py +221 -0
  87. sdata/sclass/zipfile.py +135 -0
  88. sdata/semantic.py +346 -0
  89. sdata/suuid.py +136 -0
  90. sdata/timestamp.py +336 -0
  91. sdata/tools.py +14 -0
  92. sdata/units.py +602 -0
  93. sdata/vocab.py +149 -0
  94. sdata-1.5.0.dist-info/METADATA +264 -0
  95. sdata-1.5.0.dist-info/RECORD +98 -0
  96. sdata-1.5.0.dist-info/WHEEL +5 -0
  97. sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
  98. sdata-1.5.0.dist-info/top_level.txt +1 -0
sdata/interactive.py ADDED
@@ -0,0 +1,118 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Interaktive Ergonomie für Metadaten: Jupyter-``_repr_html_``, Attribut-
3
+ Autocomplete (``m.a.force_x``) und Prefix-Filterung.
4
+
5
+ Reine Standardbibliothek; die Renderer bauen HTML manuell (keine pandas-Styler-/
6
+ tabulate-Abhängigkeit).
7
+ """
8
+ import html as _html
9
+
10
+ __all__ = ["attribute_html", "metadata_html", "AttrAccessor", "ColumnAccessor"]
11
+
12
+
13
+ def _esc(value):
14
+ return _html.escape("" if value is None else str(value))
15
+
16
+
17
+ def attribute_html(attr):
18
+ """Einzeilige HTML-Tabelle für ein :class:`Attribute`."""
19
+ return (
20
+ "<table><tr><th>name</th><th>symbol</th><th>value</th><th>unit</th>"
21
+ "<th>dtype</th><th>ontology</th></tr>"
22
+ "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td>"
23
+ "</tr></table>"
24
+ ).format(_esc(attr.name), _esc(attr.symbol), _esc(attr.value),
25
+ _esc(attr.unit), _esc(attr.dtype), _esc(attr.ontology))
26
+
27
+
28
+ def metadata_html(metadata):
29
+ """HTML-Darstellung einer :class:`Metadata` (Kopf + User-Attribut-Tabelle)."""
30
+ sname_attr = metadata.get("_sdata_sname")
31
+ sname = sname_attr.value if sname_attr is not None and sname_attr.value else ""
32
+ cls_attr = metadata.get("_sdata_class")
33
+ cls = cls_attr.value if cls_attr is not None and cls_attr.value else ""
34
+
35
+ header = "<b>{}</b>".format(_esc(metadata.name))
36
+ if cls:
37
+ header += " <code>{}</code>".format(_esc(cls))
38
+ if sname:
39
+ header += " <small>did:suuid:{}:sdata</small>".format(_esc(sname))
40
+
41
+ rows = ""
42
+ for attr in metadata.user_attributes.values():
43
+ rows += ("<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td>"
44
+ "<td>{}</td><td>{}</td></tr>").format(
45
+ _esc(attr.name), _esc(attr.symbol), _esc(attr.value), _esc(attr.unit),
46
+ _esc(attr.dtype), _esc(attr.ontology), _esc(attr.description))
47
+ table = ("<table><tr><th>name</th><th>symbol</th><th>value</th><th>unit</th>"
48
+ "<th>dtype</th><th>ontology</th><th>description</th></tr>{}</table>"
49
+ .format(rows))
50
+ return "<div>{}{}</div>".format(header, table)
51
+
52
+
53
+ class AttrAccessor:
54
+ """Attribut-Autocomplete-Helfer: ``m.a.force_x`` liefert/setzt ein Attribut.
55
+
56
+ Liegt bewusst auf einem eigenen Objekt (nicht auf :class:`Metadata`), damit
57
+ Tab-Completion nur Attributnamen zeigt und keine Methoden überschattet.
58
+ """
59
+
60
+ def __init__(self, metadata):
61
+ object.__setattr__(self, "_md", metadata)
62
+
63
+ def __getattr__(self, name):
64
+ attr = self._md.get(name)
65
+ if attr is None:
66
+ raise AttributeError(name)
67
+ return attr
68
+
69
+ def __setattr__(self, name, value):
70
+ self._md.set_attr(name, value)
71
+
72
+ def __dir__(self):
73
+ return [a.name for a in self._md.user_attributes.values()
74
+ if a.name.isidentifier()]
75
+
76
+ def __repr__(self):
77
+ return "AttrAccessor({})".format(self.__dir__())
78
+
79
+
80
+ class ColumnAccessor:
81
+ """Spalten-Annotation-Autocomplete: ``df.col.weight`` / ``df.col['weight']``.
82
+
83
+ Liefert das Spalten-:class:`Attribute` aus ``column_metadata``; dessen Felder
84
+ lassen sich direkt mutieren (``df.col.weight.unit = 'kg'``). Zum Anlegen/Setzen
85
+ ganzer Felder dient :meth:`~sdata.sclass.dataframe.DataFrame.set_column`.
86
+
87
+ Liegt – wie :class:`AttrAccessor` – bewusst auf einem eigenen Objekt, damit die
88
+ Tab-Completion nur Spaltennamen zeigt und keine Methoden überschattet.
89
+ """
90
+
91
+ def __init__(self, owner):
92
+ object.__setattr__(self, "_owner", owner)
93
+
94
+ def _cm(self):
95
+ return self._owner.column_metadata
96
+
97
+ def __getattr__(self, name):
98
+ attr = self._cm().get(name)
99
+ if attr is None:
100
+ raise AttributeError(name)
101
+ return attr
102
+
103
+ def __getitem__(self, name):
104
+ attr = self._cm().get(str(name))
105
+ if attr is None:
106
+ raise KeyError(name)
107
+ return attr
108
+
109
+ def __setattr__(self, name, value):
110
+ raise AttributeError(
111
+ "use df.set_column({0!r}, ...) or mutate fields "
112
+ "(df.col.{0}.unit = ...) instead of assigning df.col.{0}".format(name))
113
+
114
+ def __dir__(self):
115
+ return [str(c) for c in self._owner.df.columns if str(c).isidentifier()]
116
+
117
+ def __repr__(self):
118
+ return "ColumnAccessor({})".format(self.__dir__())
@@ -0,0 +1,51 @@
1
+ import os
2
+ import uuid
3
+ from collections import OrderedDict
4
+ #import logging
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ class PID(object):
10
+ """Process object, which has an uuid and metadata"""
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ self._metadata = kwargs.get("metadata") or {}
14
+ try:
15
+ self.uuid = uuid.UUID(kwargs.get("uuid"))
16
+ except:
17
+ self.uuid = uuid.uuid4()
18
+ self.name = kwargs.get("name") or "N.N."
19
+
20
+
21
+ def get_attr(self, key, default=None):
22
+ return self.metadata.get(key, default)
23
+
24
+ def add_attr(self, key, value):
25
+ self.metadata[str(key)] = value
26
+
27
+ def set_attr(self, key, value):
28
+ self.metadata[str(key)] = value
29
+
30
+ def get_metadata(self):
31
+ return self._metadata
32
+ def set_metadata(self, metadata):
33
+ self._metadata.update(metadata)
34
+ metadata = property(get_metadata, set_metadata)
35
+
36
+ def get_uuid(self):
37
+ return uuid.UUID(self._metadata.get("uuid"))
38
+ def set_uuid(self, uuid):
39
+ self._metadata["uuid"] = uuid.hex
40
+ uuid = property(get_uuid, set_uuid)
41
+
42
+ def get_name(self):
43
+ return self._metadata.get("name")
44
+ def set_name(self, name):
45
+ self._metadata["name"] = name
46
+ name = property(get_name, set_name)
47
+
48
+ def __str__(self):
49
+ return "({0.__class__.__name__}:'{0.name}':{0.uuid.hex})".format(self)
50
+
51
+ __repr__ = __str__
@@ -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) # pragma: no cover - __repr__ nimmt kein level
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