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
@@ -0,0 +1,155 @@
1
+ # lazy_namespace.py
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass
4
+ from importlib import import_module
5
+ from threading import RLock
6
+ from types import ModuleType
7
+ from typing import Any, Dict, Mapping, Tuple
8
+ import sys
9
+
10
+ Spec = str # z.B. "sdata.suuid:SUUID" oder "pkg.mod:Outer.Inner"
11
+
12
+ def _split_spec(spec: Spec) -> Tuple[str, str]:
13
+ """Zerlegt 'package.module:QualName' in (module, qualname)."""
14
+ if ":" not in spec:
15
+ raise ValueError(f"Ungültige Spec (':' fehlt): {spec!r}")
16
+ mod, qual = spec.split(":", 1)
17
+ if not mod or not qual:
18
+ raise ValueError(f"Ungültige Spec: {spec!r}")
19
+ return mod, qual
20
+
21
+ def _resolve_attr(mod: ModuleType, qualname: str) -> Any:
22
+ """Auflösen von 'Outer.Inner.Class' in verschachtelten Attributen."""
23
+ obj: Any = mod
24
+ for part in qualname.split("."):
25
+ obj = getattr(obj, part)
26
+ return obj
27
+
28
+ @dataclass(frozen=True)
29
+ class ExportItem:
30
+ name: str # Attributname im Package (z.B. "SUUID")
31
+ spec: Spec # Ziel "mod:QualName" (z.B. "sdata.suuid:SUUID")
32
+
33
+ class LazyRegistry:
34
+ """
35
+ Registry verwaltet Name->Spec und hängt sich an ein Package-Modul.
36
+ - __getattr__ lädt Zielklasse on-demand und cached sie ins Modul.
37
+ - __dir__ gibt alle dynamischen Namen zurück (IDE-Autocomplete).
38
+ - register() / register_many() erweitern zur Laufzeit.
39
+ """
40
+ def __init__(self, pkg_name: str):
41
+ self.pkg_name = pkg_name
42
+ self._exports: Dict[str, Spec] = {}
43
+ self._lock = RLock()
44
+ self._attached = False
45
+
46
+ # -------- öffentliche API --------
47
+ def attach(self) -> None:
48
+ """Bindet __getattr__/__dir__ ans Ziel-Package."""
49
+ with self._lock:
50
+ if self._attached:
51
+ return
52
+ pkg = self._get_pkg()
53
+ # bestehende __getattr__/__dir__ respektieren? (hier überschreiben)
54
+ def _lazy_getattr(name: str) -> Any:
55
+ # Fallback auf normales Attribut
56
+ if name not in self._exports:
57
+ raise AttributeError(f"no lazy import for '{name}'")
58
+ spec = self._exports[name]
59
+ mod_name, qual = _split_spec(spec)
60
+ mod = import_module(mod_name) # lazy import
61
+ obj = _resolve_attr(mod, qual)
62
+ setattr(pkg, name, obj) # Cache im Package
63
+ return obj
64
+
65
+ def _lazy_dir() -> list[str]:
66
+ # Kombiniere vorhandene und dynamische Namen
67
+ base = set(pkg.__dict__.keys())
68
+ return sorted(base.union(self._exports.keys()))
69
+
70
+ pkg.__getattr__ = _lazy_getattr # PEP 562
71
+ pkg.__dir__ = _lazy_dir # für Autocomplete
72
+ # __all__ optional pflegen
73
+ all_items = set(getattr(pkg, "__all__", ()))
74
+ pkg.__all__ = tuple(sorted(all_items.union(self._exports.keys())))
75
+ self._attached = True
76
+
77
+ def register(self, name: str, spec: Spec) -> None:
78
+ """Fügt/überschreibt einen Export."""
79
+ with self._lock:
80
+ self._exports[name] = spec
81
+ if self._attached:
82
+ # __all__ aktualisieren
83
+ pkg = self._get_pkg()
84
+ all_items = set(getattr(pkg, "__all__", ()))
85
+ pkg.__all__ = tuple(sorted(all_items.union({name})))
86
+
87
+ def register_many(self, mapping: Mapping[str, Spec]) -> None:
88
+ with self._lock:
89
+ for k, v in mapping.items():
90
+ self._exports[k] = v
91
+ if self._attached:
92
+ pkg = self._get_pkg()
93
+ all_items = set(getattr(pkg, "__all__", ()))
94
+ pkg.__all__ = tuple(sorted(all_items.union(mapping.keys())))
95
+
96
+ def exports(self) -> Dict[str, Spec]:
97
+ with self._lock:
98
+ return dict(self._exports)
99
+
100
+ # -------- Hilfen --------
101
+ def _get_pkg(self) -> ModuleType:
102
+ try:
103
+ return sys.modules[self.pkg_name]
104
+ except KeyError as e:
105
+ raise RuntimeError(f"Package {self.pkg_name!r} ist nicht importiert.") from e
106
+
107
+
108
+ # -------- Hilfsfunktionen für (De-)Serialisierung --------
109
+
110
+ TYPE_FIELD = "__type__" # z.B. "SUUID" oder "pkg.SUUID"
111
+ SPEC_FIELD = "__spec__" # optional absoluter "mod:QualName"-Fallback
112
+
113
+ def resolve_by_string(name_or_spec: str, registry: LazyRegistry | None = None) -> type:
114
+ """
115
+ Löst entweder einen Registry-Namen (z.B. 'SUUID') ODER eine absolute Spec
116
+ (z.B. 'sdata.suuid:SUUID') in eine Klasse auf.
117
+ """
118
+ if ":" in name_or_spec:
119
+ mod, qual = _split_spec(name_or_spec)
120
+ return _resolve_attr(import_module(mod), qual)
121
+ if registry is None:
122
+ raise ValueError("Registry erforderlich für Kurzname ohne Modul-Spec.")
123
+ pkg = sys.modules[registry.pkg_name]
124
+ return getattr(pkg, name_or_spec) # triggert __getattr__ → lazy import
125
+
126
+ def to_json(obj: Any, type_name: str | None = None, *, include_spec: bool = False) -> dict:
127
+ """
128
+ Serialisiert ein Objekt mit Typ-Metadaten.
129
+ - type_name: expliziter Schlüssel in der Registry, sonst obj.__class__.__name__
130
+ - include_spec=True ergänzt absolute 'mod:QualName' (robuster gegen Umbenennungen).
131
+ """
132
+ cls = obj.__class__
133
+ payload = getattr(obj, "to_dict", lambda: obj.__dict__)()
134
+ meta = {TYPE_FIELD: type_name or cls.__name__}
135
+ if include_spec:
136
+ meta[SPEC_FIELD] = f"{cls.__module__}:{cls.__qualname__}"
137
+ return {**meta, **payload}
138
+
139
+ def from_json(data: dict, registry: LazyRegistry | None = None) -> Any:
140
+ """
141
+ Rekonstruiert Objekt:
142
+ - bevorzugt __spec__, sonst __type__ via Registry.
143
+ Erwartet Klassenmethode `from_dict(d: dict)` oder init(**d).
144
+ """
145
+ if SPEC_FIELD in data:
146
+ cls = resolve_by_string(data[SPEC_FIELD])
147
+ elif TYPE_FIELD in data and registry is not None:
148
+ cls = resolve_by_string(data[TYPE_FIELD], registry)
149
+ else:
150
+ raise ValueError(f"Kein {TYPE_FIELD}/{SPEC_FIELD} in Daten.")
151
+ # Metafelder entfernen
152
+ clean = {k: v for k, v in data.items() if k not in (TYPE_FIELD, SPEC_FIELD)}
153
+ if hasattr(cls, "from_dict"):
154
+ return cls.from_dict(clean)
155
+ return cls(**clean)
@@ -0,0 +1,383 @@
1
+ from sdata.base import sdata_factory, Base
2
+ from sdata import generate_safe_name
3
+ from typing import List, Dict, Any, Type, Optional, Set, Tuple
4
+
5
+
6
+ class ProcessData(Base):
7
+ PROCESS_DATATYPE = 'ProcessData'
8
+
9
+ def __init__(self, **kwargs):
10
+ # self.default_attributes = []
11
+ # if "default_attributes" in kwargs:
12
+ # self.default_attributes.extend(kwargs.get("default_attributes"))
13
+ super().__init__(**kwargs)
14
+ # self.set_default_attributes()
15
+ attributes = kwargs.get('attributes', [])
16
+ self.set_attributes(attributes)
17
+ self.metadata.add(
18
+ self.SDATA_TOPOLOGY_CLASS, "sdata.sclass:GenericallyDependentContinuant", dtype="str",
19
+ description="sdata topology class name", required=True
20
+ )
21
+
22
+ def set_attributes(self, attributes: List[Any]):
23
+ for attribute in attributes:
24
+ self.metadata.add(**attribute)
25
+
26
+ def is_complete(self) -> bool:
27
+ for attr in self.metadata.values():
28
+ if attr.required is True and attr.value is None:
29
+ return False
30
+ return True
31
+
32
+
33
+ class ProcessNode(Base):
34
+ """
35
+ Basisklasse für Prozessknoten. Definiert Inputs/Outputs und prüft Vollständigkeit.
36
+ """
37
+ processes: List[Type['ProcessNode']] = [] # Class var for subprocess classes
38
+
39
+ required_inputs: List[Type[ProcessData]] = []
40
+ required_outputs: List[Type[ProcessData]] = []
41
+
42
+ PROCESS_DATATYPE = 'ProcessNode'
43
+
44
+ def __init__(self, name: str = "ProcessNode", inputs: Dict[str, ProcessData] = None, **kwargs: Any):
45
+ super().__init__(name=name, **kwargs)
46
+ self.metadata.add(
47
+ self.SDATA_TOPOLOGY_CLASS, "sdata.sclass:Process", dtype="str",
48
+ description="sdata topology class name", required=True
49
+ )
50
+ self.inputs: Dict[str, ProcessData] = inputs or {}
51
+ self.outputs: Dict[str, ProcessData] = {}
52
+ self.process_classes = self.__class__.processes # Instance access to class var
53
+
54
+ @classmethod
55
+ def get_dependencies(cls) -> Set[str]:
56
+ s = {cls.__base__.__name__ + ":" + cls.__name__: {x.__base__.__name__ + ":" + x.__name__ for x in cls.required_inputs},}
57
+ for x in cls.required_outputs:
58
+ s[x.__base__.__name__ + ":" + x.__name__] = {cls.__base__.__name__ + ":" + cls.__name__, }
59
+ return s
60
+
61
+ def validate_inputs(self) -> bool:
62
+ """
63
+ Prüft, ob alle erforderlichen Inputs vorhanden und vollständig sind.
64
+ """
65
+ for req_input in self.required_inputs:
66
+ found = False
67
+ for inp_key, inp in self.inputs.items():
68
+ if isinstance(inp, req_input):
69
+ if not inp.is_complete():
70
+ raise ValueError(f"Input '{inp_key}' ({inp.name}) ist nicht vollständig.")
71
+ found = True
72
+ break
73
+ if not found:
74
+ raise ValueError(f"Fehlender erforderlicher Input: {req_input.__name__}")
75
+ return True
76
+
77
+ class CompositeProcess(ProcessNode):
78
+ def __init__(self, name: str, processes: List[Type[ProcessNode]], inputs: Dict[str, ProcessData] = None, **kwargs: Any):
79
+ super().__init__(name=name, inputs=inputs, **kwargs)
80
+ self.process_classes = processes # Store class types
81
+ self.input_classes: Dict[str, Type[ProcessData]] = {}
82
+ self.output_classes: Dict[str, Type[ProcessData]] = {}
83
+ self._compute_aggregated_io()
84
+ self.required_inputs = list(self.input_classes.values())
85
+ self.required_outputs = list(self.output_classes.values())
86
+
87
+ def _compute_aggregated_io(self):
88
+ all_inputs = set()
89
+ all_outputs = set()
90
+ internal_connections = set()
91
+
92
+ for proc_type in self.process_classes:
93
+ for inp in proc_type.input_classes.keys():
94
+ all_inputs.add(inp)
95
+ for out in proc_type.output_classes.keys():
96
+ all_outputs.add(out)
97
+
98
+ for i, proc_type in enumerate(self.process_classes):
99
+ for out in proc_type.output_classes.keys():
100
+ for next_proc_type in self.process_classes[i+1:]:
101
+ if out in next_proc_type.input_classes:
102
+ internal_connections.add(out)
103
+
104
+ self.external_inputs = external_inputs = all_inputs - internal_connections
105
+ external_outputs = all_outputs - all_inputs
106
+
107
+ for inp in external_inputs:
108
+ for proc_type in self.process_classes:
109
+ if inp in proc_type.input_classes:
110
+ self.input_classes[inp] = proc_type.input_classes[inp]
111
+ break
112
+
113
+ for out in external_outputs:
114
+ for proc_type in self.process_classes:
115
+ if out in proc_type.output_classes:
116
+ self.output_classes[out] = proc_type.output_classes[out]
117
+ break
118
+
119
+ # self.computation = " + ".join([p.computation for p in self.process_classes])
120
+
121
+ # def execute(self) -> Dict[str, ProcessData]:
122
+ # if not self.validate_inputs():
123
+ # raise ValueError("Inputs not valid")
124
+ # intermediate = self.inputs.copy()
125
+ # for proc_type in self.process_classes:
126
+ # proc = proc_type(inputs={k: intermediate[k] for k in proc_type.input_classes if k in intermediate})
127
+ # proc_output = proc.execute()
128
+ # intermediate.update(proc_output)
129
+ # self.outputs = {k: intermediate[k] for k in self.output_classes}
130
+ # return self.outputs
131
+
132
+ def create_process_class(
133
+ process_name: str,
134
+ input_classes: Optional[Dict[str, Type[ProcessData]]] = None,
135
+ output_classes: Optional[Dict[str, Type[ProcessData]]] = None,
136
+ processes: Optional[List[Type[ProcessNode]]] = None
137
+ ) -> Type[ProcessNode]:
138
+ """
139
+ Factory function to generically create a subclass of ProcessNode.
140
+
141
+ :param process_name: Name of the new process class (e.g., 'Process_1').
142
+ :param input_classes: Dict mapping input names to ProcessData subclasses (default: {}).
143
+ :param output_classes: Dict mapping output names to ProcessData subclasses (default: {}).
144
+ :param processes: List of ProcessNode subclasses (default: []).
145
+ :return: A new subclass of ProcessNode.
146
+ """
147
+ input_classes = input_classes if input_classes is not None else {}
148
+ output_classes = output_classes if output_classes is not None else {}
149
+ processes = processes if processes is not None else []
150
+ class_dict = {
151
+ 'input_classes': input_classes,
152
+ 'output_classes': output_classes,
153
+ 'required_inputs': list(input_classes.values()),
154
+ 'required_outputs': list(output_classes.values()),
155
+ 'processes': processes
156
+ }
157
+
158
+ return type(process_name, (ProcessNode,), class_dict)
159
+
160
+ # Factory for composite processes
161
+ def create_composite_process_class(
162
+ composite_name: str,
163
+ process_classes: List[Type[ProcessNode]]
164
+ ) -> Type[ProcessNode]:
165
+ # Compute aggregated IO statically
166
+ all_inputs = set()
167
+ all_outputs = set()
168
+ internal_connections = set()
169
+
170
+ for proc_type in process_classes:
171
+ for inp in proc_type.input_classes.keys():
172
+ all_inputs.add(inp)
173
+ for out in proc_type.output_classes.keys():
174
+ all_outputs.add(out)
175
+
176
+ for i, proc_type in enumerate(process_classes):
177
+ for out in proc_type.output_classes.keys():
178
+ for next_proc_type in process_classes[i+1:]:
179
+ if out in next_proc_type.input_classes:
180
+ internal_connections.add(out)
181
+
182
+ external_inputs = all_inputs - internal_connections
183
+ external_outputs = all_outputs - all_inputs
184
+
185
+ input_classes = {}
186
+ for inp in external_inputs:
187
+ for proc_type in process_classes:
188
+ if inp in proc_type.input_classes:
189
+ input_classes[inp] = proc_type.input_classes[inp]
190
+ break
191
+
192
+ output_classes = {}
193
+ for out in external_outputs:
194
+ for proc_type in process_classes:
195
+ if out in proc_type.output_classes:
196
+ output_classes[out] = proc_type.output_classes[out]
197
+ break
198
+
199
+ class_dict = {
200
+ 'input_classes': input_classes,
201
+ 'output_classes': output_classes,
202
+ 'required_inputs': list(input_classes.values()),
203
+ 'required_outputs': list(output_classes.values()),
204
+ 'processes': process_classes,
205
+ }
206
+
207
+ return type(composite_name, (ProcessNode,), class_dict)
208
+
209
+ # def create_composite_process_class2(
210
+ # composite_name: str,
211
+ # process_classes: List[Type[ProcessNode]]
212
+ # ) -> Type[CompositeProcess]:
213
+ # """
214
+ # Factory function to generically create a subclass of CompositeProcess.
215
+ #
216
+ # :param composite_name: Name of the new composite process class (e.g., 'Process_1_plus_2').
217
+ # :param process_classes: List of Process subclasses to include in the composite.
218
+ # :return: A new subclass of CompositeProcess with fixed processes.
219
+ # """
220
+ #
221
+ # # Compute aggregated IO statically
222
+ # all_inputs = set()
223
+ # all_outputs = set()
224
+ # internal_connections = set()
225
+ #
226
+ # for proc_type in process_classes:
227
+ # for inp in proc_type.input_classes.keys():
228
+ # all_inputs.add(inp)
229
+ # for out in proc_type.output_classes.keys():
230
+ # all_outputs.add(out)
231
+ #
232
+ # for i, proc_type in enumerate(process_classes):
233
+ # for out in proc_type.output_classes.keys():
234
+ # for next_proc_type in process_classes[i+1:]:
235
+ # if out in next_proc_type.input_classes:
236
+ # internal_connections.add(out)
237
+ #
238
+ # external_inputs = all_inputs - internal_connections
239
+ # external_outputs = all_outputs - all_inputs
240
+ #
241
+ # input_classes = {}
242
+ # for inp in external_inputs:
243
+ # for proc_type in process_classes:
244
+ # if inp in proc_type.input_classes:
245
+ # input_classes[inp] = proc_type.input_classes[inp]
246
+ # break
247
+ #
248
+ # output_classes = {}
249
+ # for out in external_outputs:
250
+ # for proc_type in process_classes:
251
+ # if out in proc_type.output_classes:
252
+ # output_classes[out] = proc_type.output_classes[out]
253
+ # break
254
+ #
255
+ # computation = " + ".join([p.computation for p in process_classes]) if all(hasattr(p, 'computation') for p in process_classes) else ""
256
+ #
257
+ # class_dict = {
258
+ # 'input_classes': input_classes,
259
+ # 'output_classes': output_classes,
260
+ # 'required_inputs': list(input_classes.values()),
261
+ # 'required_outputs': list(output_classes.values()),
262
+ # 'computation': computation,
263
+ # }
264
+ #
265
+ # class GenericComposite(CompositeProcess):
266
+ # def __init__(self, inputs: Dict[str, ProcessData] = None, **kwargs):
267
+ # super().__init__(name=composite_name, processes=process_classes, inputs=inputs, **kwargs)
268
+ #
269
+ # for k, v in class_dict.items():
270
+ # setattr(GenericComposite, k, v)
271
+ #
272
+ # GenericComposite.__name__ = composite_name
273
+ # return GenericComposite
274
+ #
275
+ # def create_composite_process_class2(
276
+ # composite_name: str,
277
+ # process_classes: List[Type[ProcessNode]]
278
+ # ) -> Type[CompositeProcess]:
279
+ # """
280
+ # Factory function to generically create a subclass of CompositeProcess.
281
+ #
282
+ # :param composite_name: Name of the new composite process class (e.g., 'Process_1_plus_2').
283
+ # :param process_classes: List of Process subclasses to include in the composite.
284
+ # :return: A new subclass of CompositeProcess with fixed processes.
285
+ # """
286
+ #
287
+ # class GenericComposite(CompositeProcess):
288
+ # def __init__(self, inputs: Dict[str, ProcessData] = None, **kwargs):
289
+ # super().__init__(name=composite_name, processes=process_classes, inputs=inputs, **kwargs)
290
+ #
291
+ # GenericComposite.__name__ = composite_name
292
+ # return GenericComposite
293
+
294
+
295
+ def processdata_class_factory(
296
+ class_name: str,
297
+ sdata_class: Type = ProcessData,
298
+ sdata_attrs: Optional[Dict[str, Any]] = None,
299
+ **kwargs: Any
300
+ ) -> Any:
301
+ """
302
+ Factory function to create an instance of a dynamically generated subclass.
303
+
304
+ :param class_name: The name of the class to generate (e.g., "Material").
305
+ :param sdata_class: The base class to inherit from (default: Base).
306
+ :param sdata_attrs: Optional dict of custom attributes/methods to add to the class.
307
+ :param kwargs: Keyword arguments to pass to the instance initialization.
308
+ :return: generated ProcessData class.
309
+ """
310
+ class_name = generate_safe_name(class_name)
311
+ sdata_attrs = sdata_attrs or {}
312
+ cls = type(class_name, (sdata_class,), sdata_attrs)
313
+
314
+ def __init__(self, **init_kwargs: Any) -> None:
315
+ super(cls, self).__init__(**init_kwargs) # type: ignore
316
+
317
+ setattr(cls, '__init__', __init__)
318
+ cls.SDATA_CLS = f"{sdata_class.__module__}.{sdata_class.__name__}"
319
+ return cls
320
+
321
+ def sclass(class_name) -> 'Base':
322
+ """create class from class_name"""
323
+ return processdata_class_factory(class_name)
324
+
325
+ def pdata(class_name) -> 'Base':
326
+ """create class from class_name"""
327
+ return processdata_class_factory(class_name)
328
+
329
+
330
+ # Beispiel: Spezifische Subklassen
331
+ class SpecificInput(ProcessData):
332
+ def __init__(self, **kwargs):
333
+ kwargs['default_attributes'] = [{"name": "a", "value": None, "dtype": float, "unit": "-",
334
+ "description": "my a value", "label": "$a$", "required": True},
335
+ {"name": "b", "value": None, "dtype": int, "unit": "MPa",
336
+ "description": "my b value", "label": "$b$", "required": True},
337
+ ]
338
+
339
+ super().__init__(**kwargs)
340
+
341
+
342
+ class SpecificOutput(ProcessData):
343
+ pass
344
+
345
+
346
+ class SpecificProcessNode(ProcessNode):
347
+ required_inputs = [SpecificInput] # Definiert benötigte Inputs
348
+ required_outputs = [SpecificOutput] # Definiert erzeugte Outputs
349
+
350
+ def run(self) -> Dict[str, ProcessData]:
351
+ self.validate_inputs() # Validation vor Run
352
+
353
+ input1 = self.inputs.get("input1")
354
+
355
+ a = input1.md.get("a").value
356
+ b = input1.md.get("b").value
357
+
358
+ output = SpecificOutput(name="Output1")
359
+
360
+ # Berechnung
361
+ output.md.add("c", a + b)
362
+
363
+ self.outputs["output1"] = output
364
+
365
+ return self.outputs
366
+
367
+
368
+ if __name__ == '__main__':
369
+ input_a = SpecificInput(name="input", attributes=[{"name": "a", "value": 1.2},
370
+ {"name": "b", "value": 3}])
371
+
372
+ # Gültiger Knoten
373
+ node = SpecificProcessNode(
374
+ name="MyNode",
375
+ inputs={"input1": input_a}
376
+ )
377
+ try:
378
+ node.validate_inputs() # Erfolgreich
379
+ outputs = node.run() # Erzeugt Outputs
380
+ print("Valid node ran successfully.")
381
+ print(node.outputs['output1'].udf)
382
+ except ValueError as e:
383
+ print(f"Valid node error: {e}")