-
Hock, Martin authoredHock, Martin authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
classes.py 7.70 KiB
"""
File consists of several classes for the different elements of a device.
"""
from __future__ import annotations
from enum import Enum, auto
import uuid
from typing import List, Dict, Optional
import json
# TODO
# - Docstrings
# - Beschreibung von Teilen (-> properties) -> Raus aus dem Konstruktor rein in ein dict. (Deep-Copy)
# - Erstmal als Shallow Copy umgesetzt, wir verwenden momentan keine nested dicts
# - Minimalbeispiel für KPIs -> halb umgesetzt -> mit get_components veranschaulichen
# - Erlaube Clone bei Assembly (jedes child muss durch durch Klon ersetzt werden)
# - Änderungen an Beispiel umsetzen
# -> Integriere AggregationLayer und die Funktionen in die Klassen (evtl. per Vererbung?) -> Nä Semester
# - Erlaube Listen bei add_component und add_assembly (-> Nä Semester)
# - Gute String Darstellung -> Ist so schon ok bisher? -> Nä Semester
# - Export als GraphViz -> Nä Semeseter
class AggregationLayer(Enum):
SYSTEM = auto()
ASSEMBLY = auto()
SUBASSEMBLY = auto()
COMPONENT = auto()
class LegoComponent:
def __init__(self, label: Optional[str] = None, datasheet: Optional[dict] = None, *more_properties: dict, **kwargs) -> None:
self.uuid: uuid.UUID = uuid.uuid4()
self.parent: None | LegoAssembly = None
self.layer: AggregationLayer = AggregationLayer.COMPONENT
self.properties: dict = {}
if label is not None:
self.properties['label'] = label
if datasheet is not None:
self.properties.update(datasheet)
self.properties[]
for prop in more_properties:
if isinstance(prop, dict):
self.properties.update(prop)
else:
raise ValueError(f"Unexpected argument type: {type(more_properties)}")
for key, value in kwargs.items():
self.properties[key] = value
def clone(self) -> LegoComponent:
clone = LegoComponent(
None,
self.properties,
)
return clone
def get_root_assembly(self):
if self.parent is None:
return None
current_assembly = self.parent
while current_assembly.parent is not None:
current_assembly = current_assembly.parent
return current_assembly
def to_dict(self) -> Dict:
ATTRIBUTES = [
"uuid",
"label",
"category",
"lego_id",
"cost",
"mass",
"delivery_time",
"layer",
"properties",
]
dict_ = {}
# store attributes
for attr in ATTRIBUTES:
dict_[attr] = getattr(self, attr)
dict_ = {"component": dict_}
return dict_
# TODO good string representation
def __str__(self):
return self.__repr__()
return (
f"Item(id={self.uuid}, item_number={self.lego_id}, "
f"mass={self.mass}, delivery_time={self.delivery_time}, "
f"parent_id={self.parent})"
)
# TODO good repr representation
def __repr__(self):
return f"LegoComponent {self.properties['label']} [{self.uuid}]"
class LegoAssembly:
def __init__(self, layer: AggregationLayer, label: Optional[str] = None, *properties: dict , **kwargs) -> None:
self.uuid: uuid.UUID = uuid.uuid4()
self.parent: None | LegoAssembly = None
self.properties: dict = {}
if label is not None:
self.properties['label'] = label
self.layer: AggregationLayer = layer
self.properties.update(properties)
self.components: List[LegoComponent] = []
self.assemblies: List[LegoAssembly] = []
def add_component(self, component: LegoComponent) -> None:
if not isinstance(component, LegoComponent):
raise TypeError(
f"Argument should be of type {LegoComponent.__name__}, "
f"got {type(component).__name__} instead."
)
if self.get_root_assembly().contains_uuid(component.uuid):
raise AssertionError(
f"This assembly or a subassembly already contains the component with ID "
f"{component.uuid}."
)
component.parent = self
self.components.append(component)
def add_assembly(self, assembly: LegoAssembly) -> None:
if not isinstance(assembly, LegoAssembly):
raise TypeError(
f"Argument should be of type {LegoAssembly.__name__}, "
f"got {type(assembly).__name__} instead."
)
if self.get_root_assembly().contains_uuid(assembly.uuid):
raise AssertionError(
f"This assembly or a subassembly already contains the assembly with ID "
f"{assembly.uuid}."
)
assembly.parent = self
self.assemblies.append(assembly)
def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]:
return {"components": self.components, "assemblies": self.assemblies}
def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]:
component_list = []
component_list.extend(self.components)
if max_depth > 0:
for assembly in self.assemblies:
component_list.extend(assembly.get_component_list(max_depth - 1))
return component_list
def get_root_assembly(self) -> LegoAssembly:
current_assembly = self
while current_assembly.parent is not None:
current_assembly = current_assembly.parent
return current_assembly
def contains_uuid(self, uuid_: uuid.UUID):
# check component ids
component_ids = list(map(lambda c: c.uuid, self.components))
if uuid_ in component_ids:
return True
# check assembly ids
assembly_ids = list(map(lambda a: a.uuid, self.assemblies))
if uuid_ in assembly_ids:
return True
# recursively check assemblies
for assembly in self.assemblies:
if assembly.contains_uuid(uuid_):
return True
return False
def to_dict(self) -> Dict:
ATTRIBUTES = ["uuid", "label", "layer", "properties"]
dict_ = {}
# store attributes
for attr in ATTRIBUTES:
dict_[attr] = getattr(self, attr)
# store components
dict_["components"] = [component.to_dict() for component in self.components]
dict_["assemblies"] = [assembly.to_dict() for assembly in self.assemblies]
return {"assembly": dict_}
# TODO find good string representation
def __repr__(self):
return f"LegoAssembly {self.properties['label']} [{self.uuid}]"
def print_assembly_tree(root, level=0, is_last=False):
# print component
assembly_padding = ""
if level > 0:
assembly_padding += "│ " * (level - 1)
if is_last:
assembly_padding += "└── "
else:
assembly_padding += "├── "
print(f"{assembly_padding}{root}")
# recursively print child components
for i, assembly in enumerate(root.assemblies):
is_last_ = i == len(root.assemblies) - 1 and len(root.components) == 0
print_assembly_tree(assembly, level + 1, is_last_)
# print items
for i, item in enumerate(root.components):
component_padding = "│ " * level if not is_last else " "
component_padding += "├── " if i < len(root.components) - 1 else "└── "
print(f"{component_padding}{item}")
## TODO maybe add Components entry from dict?
class KPIEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, uuid.UUID):
return "kpi-" + str(o)
if isinstance(o, (AggregationLayer)):
return "kpi-" + o.label
return super().default(o)