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.
- sdata/__init__.py +140 -0
- sdata/base.py +684 -0
- sdata/contrib/__init__.py +0 -0
- sdata/contrib/piexif/__init__.py +12 -0
- sdata/contrib/piexif/_common.py +94 -0
- sdata/contrib/piexif/_dump.py +346 -0
- sdata/contrib/piexif/_exceptions.py +2 -0
- sdata/contrib/piexif/_exif.py +638 -0
- sdata/contrib/piexif/_insert.py +60 -0
- sdata/contrib/piexif/_load.py +270 -0
- sdata/contrib/piexif/_remove.py +56 -0
- sdata/contrib/piexif/_transplant.py +45 -0
- sdata/contrib/piexif/_webp.py +240 -0
- sdata/contrib/piexif/helper.py +66 -0
- sdata/contrib/ranger/__init__.py +8 -0
- sdata/contrib/ranger/collections/__init__.py +0 -0
- sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
- sdata/contrib/ranger/collections/rangemap.py +396 -0
- sdata/contrib/ranger/collections/rangeset.py +483 -0
- sdata/contrib/ranger/range/__init__.py +0 -0
- sdata/contrib/ranger/range/cut.py +285 -0
- sdata/contrib/ranger/range/range.py +644 -0
- sdata/contrib/sobol_seq.py +394 -0
- sdata/contrib/sortedcontainers/LICENSE +13 -0
- sdata/contrib/sortedcontainers/__init__.py +74 -0
- sdata/contrib/sortedcontainers/sorteddict.py +789 -0
- sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
- sdata/contrib/sortedcontainers/sortedset.py +733 -0
- sdata/contrib/sqlitedict.py +598 -0
- sdata/contrib/timeflake/__init__.py +40 -0
- sdata/contrib/timeflake/flake.py +69 -0
- sdata/contrib/timeflake/utils.py +47 -0
- sdata/deprecated/__init__.py +10 -0
- sdata/deprecated/data.py +1503 -0
- sdata/did/__init__.py +129 -0
- sdata/did/_http.py +67 -0
- sdata/did/base58btc.py +49 -0
- sdata/did/did_github.py +152 -0
- sdata/did/did_peer4.py +155 -0
- sdata/did/did_web.py +222 -0
- sdata/did/diddoc.py +75 -0
- sdata/did/ed25519.py +225 -0
- sdata/did/eddsa.py +107 -0
- sdata/did/errors.py +40 -0
- sdata/did/issue_vc.py +193 -0
- sdata/did/jose.py +126 -0
- sdata/did/keys.py +104 -0
- sdata/did/utils_didkey.py +161 -0
- sdata/did/verify_vp.py +158 -0
- sdata/doe.py +101 -0
- sdata/dtypes.py +510 -0
- sdata/experiments/__init__.py +331 -0
- sdata/experiments/blob0.py +131 -0
- sdata/experiments/file.py +42 -0
- sdata/experiments/filegroup.py +144 -0
- sdata/experiments/image_old.py +313 -0
- sdata/experiments/ks2.py +15 -0
- sdata/experiments/lapshear.py +8 -0
- sdata/experiments/material.py +7 -0
- sdata/experiments/parameters_deprecated.py +394 -0
- sdata/experiments/workbook.py +226 -0
- sdata/format.py +125 -0
- sdata/imagemeta.py +533 -0
- sdata/interactive.py +118 -0
- sdata/iolib/__init__.py +51 -0
- sdata/iolib/hashtree.py +264 -0
- sdata/iolib/json1sqlitestore.py +554 -0
- sdata/iolib/jsonsqlitestore.py +704 -0
- sdata/iolib/pud.py +242 -0
- sdata/iolib/reader.py +262 -0
- sdata/iolib/writer.py +419 -0
- sdata/metadata.py +882 -0
- sdata/parameter.py +414 -0
- sdata/schema.py +273 -0
- sdata/sclass/__init__.py +111 -0
- sdata/sclass/bfo_classes.py +362 -0
- sdata/sclass/blob.py +362 -0
- sdata/sclass/content.py +87 -0
- sdata/sclass/dataframe.py +1251 -0
- sdata/sclass/dataframegroup.py +187 -0
- sdata/sclass/dependency_graph.py +85 -0
- sdata/sclass/filereference.py +124 -0
- sdata/sclass/image.py +218 -0
- sdata/sclass/lazy_namespace.py +155 -0
- sdata/sclass/process.py +383 -0
- sdata/sclass/process_graph.py +221 -0
- sdata/sclass/zipfile.py +135 -0
- sdata/semantic.py +346 -0
- sdata/suuid.py +136 -0
- sdata/timestamp.py +336 -0
- sdata/tools.py +14 -0
- sdata/units.py +602 -0
- sdata/vocab.py +149 -0
- sdata-1.5.0.dist-info/METADATA +264 -0
- sdata-1.5.0.dist-info/RECORD +98 -0
- sdata-1.5.0.dist-info/WHEEL +5 -0
- sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
- 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)
|
sdata/sclass/process.py
ADDED
|
@@ -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}")
|