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/semantic.py ADDED
@@ -0,0 +1,346 @@
1
+ # -*- coding: utf-8 -*-
2
+ """JSON-LD-/Linked-Data-Serialisierung für sdata-Metadaten + Sidecar-Dateien.
3
+
4
+ Wandelt eine :class:`sdata.metadata.Metadata` in ein selbstbeschreibendes
5
+ JSON-LD-Dokument (und zurück) und schreibt/liest optionale Sidecar-Dateien
6
+ ``<sname>.meta.jsonld`` neben einem Datenblob.
7
+
8
+ Modellierung (siehe Projektentscheidungen):
9
+
10
+ * ``@id`` = DID des Objekts ``did:suuid:<sname>:sdata``.
11
+ * ``@type`` = ``[sdata:<Klasse>, <BFO-IRI der Topologieklasse>]``.
12
+ * Reservierte ``_sdata_*``-Felder -> schema.org/DCAT/PROV-Terme (name,
13
+ identifier, generatedAtTime, wasDerivedFrom, isPartOf, …).
14
+ * **Hybrid** für User-Attribute: mit echter Einheit -> ``qudt:Quantity``-Knoten;
15
+ ohne Einheit aber mit ``ontology`` -> getypter Knoten; sonst einfaches
16
+ typisiertes Literal. ``ontology`` ist stets ein ``@type``/Klasse des Werts;
17
+ das Prädikat ist ``sdata:<name>``.
18
+
19
+ Pure-Python; keine Pflicht-Dependency.
20
+ """
21
+ import json
22
+ import os
23
+
24
+ import sdata.dtypes as dtypes
25
+ from sdata import vocab, units
26
+
27
+ try: # optionales RDF-Backend ([rdf]=rdflib)
28
+ import rdflib as _rdflib
29
+ except ImportError: # pragma: no cover - abhängig vom Environment
30
+ _rdflib = None
31
+
32
+ __all__ = [
33
+ "to_jsonld", "from_jsonld", "to_rdf", "to_turtle", "rdf_from_doc",
34
+ "column_node", "write_sidecar", "read_sidecar",
35
+ "to_verifiable_credential", "verify_credential", "SIDECAR_SUFFIX",
36
+ ]
37
+
38
+ SIDECAR_SUFFIX = ".meta.jsonld"
39
+
40
+ # reserviertes Feld -> JSON-LD-Term (für Top-Level-Mapping & Rück-Parsing)
41
+ _RESERVED_TERMS = {
42
+ "_sdata_name": "name",
43
+ "_sdata_suuid": "identifier",
44
+ "_sdata_ctime": "generatedAtTime",
45
+ "_sdata_topology_class": "topologyClass",
46
+ "_sdata_version": "sdata:version",
47
+ "_sdata_unit_system": "sdata:unitSystem",
48
+ }
49
+ _TERMS_RESERVED = {v: k for k, v in _RESERVED_TERMS.items()}
50
+
51
+ # XSD-CURIE -> dtype (eindeutige Rückabbildung; list/json kommen über JSON-Typ)
52
+ _DTYPE_FROM_XSD = {
53
+ "xsd:string": "str", "xsd:integer": "int", "xsd:double": "float",
54
+ "xsd:boolean": "bool", "xsd:dateTime": "timestamp",
55
+ "xsd:base64Binary": "bytes", "xsd:anyURI": "uri",
56
+ "xsd:date": "date", "xsd:time": "time",
57
+ "xsd:duration": "duration", "xsd:decimal": "decimal",
58
+ "sdata:complex": "complex",
59
+ }
60
+
61
+
62
+ def _did(sname):
63
+ return "did:suuid:{}:sdata".format(sname)
64
+
65
+
66
+ def _sname_from_did(ref):
67
+ """Extrahiere den sname aus ``did:suuid:<sname>:sdata`` (oder aus {'@id':…})."""
68
+ if isinstance(ref, dict):
69
+ ref = ref.get("@id", "")
70
+ parts = str(ref).split(":")
71
+ if len(parts) >= 4 and parts[0] == "did" and parts[1] == "suuid":
72
+ return parts[2]
73
+ return str(ref)
74
+
75
+
76
+ def _class_local(class_spec):
77
+ """``"sdata.sclass.dataframe:DataFrame"`` -> ``"DataFrame"``."""
78
+ return str(class_spec).split(":")[-1].split(".")[-1]
79
+
80
+
81
+ def _value_literal(attr):
82
+ """Typisiertes JSON-LD-Literal ``{"@value": …, "@type": xsd}`` für ein Attribut.
83
+
84
+ Ausnahme ``langstring``: rdf:langString wird über ``@language`` ausgedrückt
85
+ (nicht ``@type``).
86
+ """
87
+ if attr.dtype == "langstring" and isinstance(attr.value, dtypes.LangString):
88
+ node = {"@value": attr.value.text}
89
+ if attr.value.lang:
90
+ node["@language"] = attr.value.lang
91
+ return node
92
+ spec = dtypes.get(attr.dtype) or dtypes.get("str")
93
+ return {"@value": spec.to_json(attr.value), "@type": vocab.xsd_for_dtype(attr.dtype)}
94
+
95
+
96
+ def _attr_node(attr):
97
+ """Hybrid-Repräsentation eines User-Attributs (Knoten oder Literal)."""
98
+ has_unit = attr.unit not in (None, "", "-")
99
+ types = []
100
+ if has_unit:
101
+ types.append("qudt:Quantity")
102
+ type_of = vocab.type_iri(attr.ontology)
103
+ if type_of:
104
+ types.append(type_of)
105
+ # ohne Einheit und ohne ontology -> einfaches Literal
106
+ if not types:
107
+ return _value_literal(attr)
108
+ node = {"@type": types if len(types) > 1 else types[0],
109
+ "name": attr.name,
110
+ "value": _value_literal(attr)}
111
+ if has_unit:
112
+ node.update(units.unit_node(attr.unit))
113
+ if attr.description:
114
+ node["description"] = attr.description
115
+ if attr.label:
116
+ node["label"] = attr.label
117
+ if attr.symbol:
118
+ node["quantitySymbol"] = attr.symbol
119
+ if attr.required:
120
+ node["required"] = bool(attr.required)
121
+ return node
122
+
123
+
124
+ def column_node(attr):
125
+ """JSON-LD-Knoten (CSVW) für eine Tabellenspalte aus ``column_metadata``.
126
+
127
+ ``attr.name`` = Spaltenname, ``attr.value`` = pandas-dtype-Name (z.B.
128
+ ``float64``); optional ``unit``/``label``/``description`` und ``ontology``
129
+ (Klasse der Spaltenwerte, als ``rdfType``).
130
+ """
131
+ node = {"name": attr.name, "datatype": vocab.xsd_for_dtype(attr.value)}
132
+ if attr.unit not in (None, "", "-"):
133
+ node.update(units.unit_node(attr.unit))
134
+ rdf_type = vocab.type_iri(attr.ontology)
135
+ if rdf_type:
136
+ node["rdfType"] = rdf_type
137
+ if attr.label:
138
+ node["label"] = attr.label
139
+ if attr.symbol:
140
+ node["quantitySymbol"] = attr.symbol
141
+ if attr.description:
142
+ node["description"] = attr.description
143
+ return node
144
+
145
+
146
+ def to_jsonld(metadata, context_mode="inline", columns=None):
147
+ """Serialisiere ``metadata`` als JSON-LD-Dokument (dict).
148
+
149
+ :param columns: optionale, **geordnete** Iterable von Spalten-``Attribute``s
150
+ (DataFrame, in df-Spaltenreihenfolge); wird als ``csvw:column``-Liste ergänzt.
151
+ """
152
+ doc = {"@context": vocab.build_context(mode=context_mode)}
153
+
154
+ sname = metadata.get("_sdata_sname")
155
+ if sname is not None and sname.value:
156
+ doc["@id"] = _did(sname.value)
157
+
158
+ types = []
159
+ cls = metadata.get("_sdata_class")
160
+ if cls is not None and cls.value:
161
+ types.append("sdata:" + _class_local(cls.value))
162
+ topo = metadata.get("_sdata_topology_class")
163
+ bfo = vocab.bfo_iri(topo.value) if topo is not None else None
164
+ if bfo:
165
+ types.append(bfo)
166
+ if types:
167
+ doc["@type"] = types if len(types) > 1 else types[0]
168
+
169
+ # bekannte reservierte Felder -> Terme
170
+ for key, term in _RESERVED_TERMS.items():
171
+ attr = metadata.get(key)
172
+ if attr is None or attr.value in (None, ""):
173
+ continue
174
+ doc[term] = attr.value
175
+ # Relationen als DID-Referenzen
176
+ parent = metadata.get("_sdata_parent_sname")
177
+ if parent is not None and parent.value:
178
+ doc["wasDerivedFrom"] = {"@id": _did(parent.value)}
179
+ project = metadata.get("_sdata_project_sname")
180
+ if project is not None and project.value:
181
+ doc["isPartOf"] = {"@id": _did(project.value)}
182
+
183
+ # User-Attribute -> sdata:<name>
184
+ for attr in metadata.user_attributes.values():
185
+ doc[vocab.predicate_for(attr.name)] = _attr_node(attr)
186
+
187
+ # tabellarische Spalten (DataFrame) als geordnete csvw:column-Liste
188
+ if columns:
189
+ doc["columns"] = [column_node(a) for a in columns]
190
+ return doc
191
+
192
+
193
+ def _set_from_node(metadata, name, node):
194
+ """Rekonstruiere ein User-Attribut aus Knoten/Literal."""
195
+ unit = "-"
196
+ ontology = ""
197
+ lang = None
198
+ if isinstance(node, dict) and "@value" in node:
199
+ raw, xsd = node.get("@value"), node.get("@type")
200
+ lang = node.get("@language")
201
+ elif isinstance(node, dict):
202
+ literal = node.get("value", {})
203
+ raw = literal.get("@value") if isinstance(literal, dict) else literal
204
+ xsd = literal.get("@type") if isinstance(literal, dict) else None
205
+ unit = node.get("symbol", "-") or "-"
206
+ type_list = node.get("@type", [])
207
+ if isinstance(type_list, str):
208
+ type_list = [type_list]
209
+ ontology = next((t for t in type_list if t != "qudt:Quantity"), "")
210
+ else:
211
+ raw, xsd = node, None
212
+ # dtype bestimmen: JSON-Typ/@language haben Vorrang, sonst XSD-Rückabbildung
213
+ if isinstance(raw, list):
214
+ dtype = "floatlist" if xsd == "sdata:floatlist" else "list"
215
+ elif isinstance(raw, dict):
216
+ dtype = "json"
217
+ elif lang:
218
+ dtype = "langstring"
219
+ raw = "{}@{}".format(raw, lang) # text@lang rekonstruieren
220
+ else:
221
+ dtype = _DTYPE_FROM_XSD.get(xsd, "str")
222
+ metadata.set_attr(name, raw, dtype=dtype, unit=unit, ontology=ontology)
223
+
224
+
225
+ def from_jsonld(doc):
226
+ """Rekonstruiere eine :class:`Metadata` aus einem JSON-LD-Dokument (dict oder str)."""
227
+ from sdata.metadata import Metadata
228
+ if isinstance(doc, str):
229
+ doc = json.loads(doc)
230
+ m = Metadata()
231
+
232
+ if "@id" in doc:
233
+ m.set_attr("_sdata_sname", _sname_from_did(doc["@id"]))
234
+ type_list = doc.get("@type", [])
235
+ if isinstance(type_list, str):
236
+ type_list = [type_list]
237
+ for t in type_list:
238
+ if isinstance(t, str) and t.startswith("sdata:"):
239
+ m.set_attr("_sdata_class", t.split(":", 1)[1])
240
+ for term, key in _TERMS_RESERVED.items():
241
+ if term in doc:
242
+ m.set_attr(key, doc[term])
243
+ if "wasDerivedFrom" in doc:
244
+ m.set_attr("_sdata_parent_sname", _sname_from_did(doc["wasDerivedFrom"]))
245
+ if "isPartOf" in doc:
246
+ m.set_attr("_sdata_project_sname", _sname_from_did(doc["isPartOf"]))
247
+
248
+ reserved_terms = set(_TERMS_RESERVED) | {
249
+ "@context", "@id", "@type", "wasDerivedFrom", "isPartOf"}
250
+ for key, val in doc.items():
251
+ if key in reserved_terms:
252
+ continue
253
+ if key.startswith("sdata:"):
254
+ _set_from_node(m, key.split(":", 1)[1], val)
255
+ return m
256
+
257
+
258
+ def to_rdf(metadata, fmt="turtle"):
259
+ """Serialisiere die Metadaten als RDF.
260
+
261
+ Mit installiertem ``rdflib`` (``[rdf]``) wird das JSON-LD nach ``fmt``
262
+ (turtle/nt/xml/…) serialisiert. Ohne rdflib wird das JSON-LD selbst
263
+ zurückgegeben – ``application/ld+json`` ist bereits gültiges RDF.
264
+ """
265
+ return rdf_from_doc(to_jsonld(metadata), fmt=fmt)
266
+
267
+
268
+ def rdf_from_doc(doc, fmt="turtle"):
269
+ """Serialisiere ein bereits gebautes JSON-LD-Dokument als RDF (siehe :func:`to_rdf`)."""
270
+ data = json.dumps(doc, default=dtypes.json_default)
271
+ if _rdflib is not None:
272
+ graph = _rdflib.Graph()
273
+ graph.parse(data=data, format="json-ld")
274
+ return graph.serialize(format=fmt)
275
+ return json.dumps(doc, indent=2, default=dtypes.json_default)
276
+
277
+
278
+ def to_turtle(metadata):
279
+ """Convenience: RDF im Turtle-Format (siehe :func:`to_rdf`)."""
280
+ return to_rdf(metadata, fmt="turtle")
281
+
282
+
283
+ def to_verifiable_credential(metadata, issuer_did, issuer_priv_jwk,
284
+ kid=None, extra_claims=None):
285
+ """Signiere die Metadaten als W3C Verifiable Credential (Compact-JWS, EdDSA).
286
+
287
+ Wickelt :func:`to_jsonld` als ``credentialSubject`` und signiert über den
288
+ pure-Python-EdDSA-Stack (:mod:`sdata.did.jose`) – keine externe Krypto.
289
+
290
+ :return: Compact-JWS-String (``header.payload.signature``)
291
+ """
292
+ from sdata.did import jose
293
+ subject = to_jsonld(metadata)
294
+ payload = {
295
+ "iss": issuer_did,
296
+ "vc": {
297
+ "@context": ["https://www.w3.org/2018/credentials/v1"],
298
+ "type": ["VerifiableCredential", "SdataMetadataCredential"],
299
+ "credentialSubject": subject,
300
+ },
301
+ }
302
+ if subject.get("@id"):
303
+ payload["sub"] = subject["@id"]
304
+ if extra_claims:
305
+ payload.update(extra_claims)
306
+ return jose.sign_compact(payload, issuer_priv_jwk,
307
+ kid=kid or (issuer_did + "#key-1"), typ="vc+ld+jwt")
308
+
309
+
310
+ def verify_credential(vc_jws, pub_jwk):
311
+ """Verifiziere ein VC (Compact-JWS) und gib das ``credentialSubject`` (JSON-LD) zurück.
312
+
313
+ :raises sdata.did.errors.VerificationError: bei ungültiger Signatur.
314
+ """
315
+ from sdata.did import jose
316
+ payload = jose.verify_compact(vc_jws, pub_jwk)
317
+ return payload["vc"]["credentialSubject"]
318
+
319
+
320
+ def _sidecar_path(path, sname):
321
+ """Ziel-Dateipfad: Verzeichnis -> ``<dir>/<sname>.meta.jsonld``; sonst ``path``."""
322
+ if path is None or os.path.isdir(path):
323
+ directory = path or "."
324
+ return os.path.join(directory, "{}{}".format(sname, SIDECAR_SUFFIX))
325
+ return path
326
+
327
+
328
+ def write_sidecar(metadata, path=None, indent=2):
329
+ """Schreibe ``<sname>.meta.jsonld`` (JSON-LD) neben einen Blob; gibt den Pfad zurück."""
330
+ sname_attr = metadata.get("_sdata_sname")
331
+ sname = sname_attr.value if sname_attr is not None and sname_attr.value else "metadata"
332
+ return write_sidecar_doc(to_jsonld(metadata), path, sname, indent=indent)
333
+
334
+
335
+ def write_sidecar_doc(doc, path, sname, indent=2):
336
+ """Schreibe ein bereits gebautes JSON-LD-Dokument als ``<sname>.meta.jsonld``."""
337
+ target = _sidecar_path(path, sname)
338
+ with open(target, "w") as fh:
339
+ json.dump(doc, fh, indent=indent, default=dtypes.json_default)
340
+ return target
341
+
342
+
343
+ def read_sidecar(path):
344
+ """Lade eine ``.meta.jsonld``-Sidecar-Datei und rekonstruiere die Metadata."""
345
+ with open(path, "r") as fh:
346
+ return from_jsonld(json.load(fh))
sdata/suuid.py ADDED
@@ -0,0 +1,136 @@
1
+ """sdata.suuid — Adapter auf das eigenständige ``suuid``-Package.
2
+
3
+ Ab jetzt ist **``suuid``** (``pip install "suuid>=0.2.0"``) die Single Source of
4
+ Truth für semantische, S3-sichere IDs der Form ``Class__safe_name__huuid``.
5
+ Dieses Modul ist ein dünner Rückwärtskompatibilitäts-Adapter: es bildet die
6
+ historisch in sdata genutzte SUUID-API auf das ``suuid``-Package ab.
7
+
8
+ Mapping (sdata → suuid):
9
+ ``suuid_str`` / ``suuid_bytes`` → :attr:`suuid.SUUID.compact_token`
10
+ ``uuid`` / ``get_uuid()`` → :meth:`suuid.SUUID.as_uuid`
11
+ ``from_name(ns_name=…)`` → :meth:`suuid.SUUID.from_name` (``ns=…``)
12
+ ``from_suuid_sname(strict=False)`` → :meth:`suuid.SUUID.from_sname`
13
+ ``from_suuid_str`` / ``from_suuid_bytes`` → :meth:`suuid.SUUID.from_compact_token`
14
+ ``generate_safe_filename`` → :func:`suuid.safe_name`
15
+
16
+ Damit gilt durchgängig die in ``suuid`` eingefrorene, 100% S3-sichere
17
+ Normalisierung (kein führendes ``_``, kein ``@``, ``sname`` ∈ ``[A-Za-z0-9_]``).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import uuid as _uuid
22
+ from typing import Optional
23
+
24
+ from suuid import SUUID as _SUUID, OID_NAMESPACE, safe_name # noqa: F401
25
+ from suuid.core import clean_class_name
26
+
27
+ __all__ = ["SUUID"]
28
+
29
+
30
+ class SUUID(_SUUID):
31
+ """sdata-Adapter um :class:`suuid.SUUID` mit Kompatibilitäts-Aliassen.
32
+
33
+ Erbt Immutabilität, Wertgleichheit und die S3-sichere Normalisierung von
34
+ :class:`suuid.SUUID`; ergänzt nur die in sdata genutzten Alias-Namen.
35
+ """
36
+
37
+ #: Separator (Kompat-Konstante; identisch zu ``suuid`` ``SEP``).
38
+ SEP = "__"
39
+
40
+ # --- Property-Aliasse ------------------------------------------------
41
+ @property
42
+ def suuid_str(self) -> str:
43
+ """Kompakter Base64-Token (Alias auf :attr:`compact_token`)."""
44
+ return self.compact_token
45
+
46
+ @property
47
+ def suuid_bytes(self) -> bytes:
48
+ """:attr:`suuid_str` als UTF-8-Bytes."""
49
+ return self.compact_token.encode()
50
+
51
+ @property
52
+ def uuid(self) -> _uuid.UUID:
53
+ """Das :class:`uuid.UUID`-Objekt hinter ``huuid``."""
54
+ return self.as_uuid()
55
+
56
+ def get_uuid(self) -> _uuid.UUID:
57
+ """Wie :attr:`uuid`."""
58
+ return self.as_uuid()
59
+
60
+ def to_list(self) -> list:
61
+ """``[sname, suuid_str, class_name, name, huuid]`` (sdata-Kompat)."""
62
+ return [self.sname, self.suuid_str, self.class_name, self.name, self.huuid]
63
+
64
+ def to_dict(self) -> dict:
65
+ """Wie :meth:`suuid.SUUID.to_dict`, plus sdata-Alias ``suuid``."""
66
+ d = super().to_dict()
67
+ d["suuid"] = self.suuid_str
68
+ return d
69
+
70
+ # --- Konstruktoren mit sdata-Signaturen ------------------------------
71
+ @classmethod
72
+ def from_name(cls, class_name: str, name: str = "",
73
+ ns_name: Optional[str] = None) -> "SUUID":
74
+ """Deterministische SUUID; ``ns_name`` (String) scoped die ID."""
75
+ ns = OID_NAMESPACE if not ns_name else ns_name
76
+ return super().from_name(class_name, name, ns=ns)
77
+
78
+ @classmethod
79
+ def from_suuid_sname(cls, s, strict: bool = False):
80
+ """Parse einen ``sname``; ``strict=False`` (Default) → ``None`` bei Fehler."""
81
+ return super().from_sname(s, strict=strict)
82
+
83
+ @classmethod
84
+ def from_suuid_str(cls, s: str) -> "SUUID":
85
+ """Aus :attr:`suuid_str`/compact-token rekonstruieren."""
86
+ return super().from_compact_token(s)
87
+
88
+ @classmethod
89
+ def from_suuid_bytes(cls, b) -> "SUUID":
90
+ """Aus :attr:`suuid_bytes` rekonstruieren."""
91
+ token = b.decode() if isinstance(b, (bytes, bytearray)) else b
92
+ return super().from_compact_token(token)
93
+
94
+ @classmethod
95
+ def from_file(cls, class_name: str, filepath, ns_name: Optional[str] = None) -> "SUUID":
96
+ """Content-adressierte SUUID aus Dateiinhalt (``name`` = Basename)."""
97
+ ns = OID_NAMESPACE if not ns_name else ns_name
98
+ return super().from_file(class_name, filepath, ns=ns)
99
+
100
+ @classmethod
101
+ def from_str(cls, class_name: str, s: str, ns_name: Optional[str] = None) -> "SUUID":
102
+ """Content-adressierte SUUID aus einem String."""
103
+ ns = OID_NAMESPACE if not ns_name else ns_name
104
+ return super().from_content(class_name, "", s.encode("utf-8"), ns=ns)
105
+
106
+ @classmethod
107
+ def from_uuid(cls, class_name: str, uuid_obj: _uuid.UUID) -> "SUUID":
108
+ """SUUID aus ``class_name`` und einem :class:`uuid.UUID` (ohne Name)."""
109
+ return cls(class_name=clean_class_name(class_name), name="", huuid=uuid_obj.hex)
110
+
111
+ @classmethod
112
+ def from_obj(cls, obj, class_name: Optional[str] = None):
113
+ """Koerziere ein Base-artiges Objekt, einen ``sname`` oder String zu SUUID."""
114
+ if obj is None:
115
+ return None
116
+ if hasattr(obj, "sname"):
117
+ return cls.from_suuid_sname(obj.sname, strict=False)
118
+ if isinstance(obj, str):
119
+ parsed = cls.from_suuid_sname(obj, strict=False)
120
+ return parsed if parsed is not None else cls.from_name(class_name or cls.__name__, obj)
121
+ return None
122
+
123
+ # --- Statics ---------------------------------------------------------
124
+ @staticmethod
125
+ def generate_safe_filename(name: str) -> str:
126
+ """100% S3-sichere Normalisierung (Alias auf :func:`suuid.safe_name`)."""
127
+ return safe_name(name)
128
+
129
+ @staticmethod
130
+ def is_valid_suuid_str(s: str) -> bool:
131
+ """``True``, wenn ``s`` ein gültiger :attr:`suuid_str` ist."""
132
+ try:
133
+ _SUUID.from_compact_token(s)
134
+ return True
135
+ except Exception:
136
+ return False