From 6d18c70bcb39cd832e6442f0c4a5df445c4123a1 Mon Sep 17 00:00:00 2001 From: "Hock, Martin" <martin.hock@fst.tu-darmstadt.de> Date: Thu, 2 Mar 2023 18:52:51 +0100 Subject: [PATCH] Add docstrings, and reformat docstring to black and flake8 with max-line-length of 88. --- functions/classes.py | 269 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 261 insertions(+), 8 deletions(-) diff --git a/functions/classes.py b/functions/classes.py index 758fbb1..7563deb 100644 --- a/functions/classes.py +++ b/functions/classes.py @@ -1,5 +1,6 @@ """ -File consists of several classes for the different elements of a device. +File consists of several classes to model elements and assembly +layers of a composed device. """ from __future__ import annotations import uuid @@ -10,6 +11,10 @@ from typing import List, Dict, Optional class AggregationLayer(Enum): + """Describes the levl of aggregation for the objects LegoComponent + and LegoAssembly and provides the 4 applicable layers. + """ + SYSTEM = auto() ASSEMBLY = auto() SUBASSEMBLY = auto() @@ -17,6 +22,34 @@ class AggregationLayer(Enum): class LegoComponent: + """ + A class for storing information about a single Lego component. + + ... + + Attributes + ---------- + uuid : UUID + A randomly generated unique identifier for the component. + parent : None | LegoAssembly + The parent of the component. Can be either None or a LegoAssembly object. + layer : AggregationLayer + An enumeration indicating the hierarchy level. For compoennts, this is + COMPONENT by default. + properties : dict + Dictionary that holds all properties of the component that can be saved + in a dictionary format. + + Methods + ------- + clone(new_label=None) + Returns a new instance of LegoComponent identical to the current instance. + get_root_assembly() + Returns the top-level assembly in which the component belongs. + to_dict() + Returns the current instance represented as a dictionary. + """ + def __init__( self, label: Optional[str] = None, @@ -24,6 +57,25 @@ class LegoComponent: *more_properties: dict, **kwargs, ) -> None: + """ + Constructs all the necessary attributes for the LegoComponent object. + + Parameters + ---------- + label : str, optional + The name of the component to add. + datasheet : dict, optional + Metadata describing the component, read from datasheet. + more_properties : tuple of dict, dict + Additional dictionaries representing custom properties. + kwargs + Arbitrary keyword arguments representing custom properties. + + Raises + ------ + ValueError + If the type of more_properties argument is not a dictionary. + """ self.uuid: uuid.UUID = uuid.uuid4() self.parent: None | LegoAssembly = None self.layer: AggregationLayer = AggregationLayer.COMPONENT @@ -41,6 +93,24 @@ class LegoComponent: self.properties[key] = value def clone(self, new_label: Optional[str] = None) -> LegoComponent: + """ + Returns a new instance of LegoComponent identical to the current instance. + + This method creates a new instance of LegoComponent that is identical to the + current instance with an optional different label. + The assigned uuid changes and elements are copied. + + Parameters + ---------- + new_label : str, optional + A new label string to use. Defaults to None. + + Returns + ------- + LegoComponent + A new instance of LegoComponent that is identical to the current instance. + + """ if new_label is None: new_label = self.properties["label"] clone = LegoComponent(None, None, self.properties) @@ -48,6 +118,18 @@ class LegoComponent: return clone def get_root_assembly(self): + """ + Returns the top-level assembly in which the component belongs. + + This method traverses the parent hierarchy of a LegoComponent object until it + finds the root-level LegoAssembly, returning it to the caller. A parent is + assigned when a LegoComponent or LegoItem is added to a LegoAssembly object. + + Returns + ------- + None | LegoAssembly + The root-level LegoAssembly or None if the component has no parent. + """ if self.parent is None: return None current_assembly = self.parent @@ -56,6 +138,17 @@ class LegoComponent: return current_assembly def to_dict(self) -> Dict: + """ + Returns the current instance represented as a dictionary. + + This method returns a dictionary representation of the LegoComponent object + suitable for serialization as JSON. + + Returns + ------- + dict + A dictionary representation of the object. + """ dict_ = { "uuid": self.uuid, "properties": self.properties, @@ -64,13 +157,31 @@ class LegoComponent: return {"component": dict_} def __str__(self): + """ + Simple string representation of the object. + """ return self.__repr__() def __repr__(self): + """ + String representation of the object including the component label and UUID. + """ return f"LegoComponent {self.properties['label']} [{self.uuid}]" class LegoAssembly: + """ + Represents a Lego assembly that can contain Lego Components and subassemblies. + + Attributes: + uuid (uuid.UUID): The unique ID of the assembly. + parent (LegoAssembly or None): The parent assembly containing this one, if any. + properties (dict): Optional properties for the assembly, such as a label. + layer (AggregationLayer): The aggregation layer of the assembly. + components (List[LegoComponent]): The list of contained components. + assemblies (List[LegoAssembly]): The list of contained subassemblies. + """ + def __init__( self, layer: AggregationLayer, @@ -78,6 +189,16 @@ class LegoAssembly: *properties: dict, **kwargs, ) -> None: + """ + Initializes a new LegoAssembly instance. + + Args: + layer (AggregationLayer): The aggregation layer of the assembly. + label (Optional[str], optional): Optional label for the assembly. + Defaults to None. + properties (dict): Optional properties for the assembly, such as a label. + **kwargs: Optional keyword arguments for additional properties. + """ self.uuid: uuid.UUID = uuid.uuid4() self.parent: None | LegoAssembly = None self.properties: dict = {} @@ -95,6 +216,20 @@ class LegoAssembly: self.assemblies: List[LegoAssembly] = [] def add_component(self, component: LegoComponent | List[LegoComponent]) -> None: + """ + Adds a Lego component to the current assembly. + Use add() to handle both components and assemblies. + + Args: + component (LegoComponent or List[LegoComponent]): + The component or list of components to add. + + Raises: + TypeError: If the argument is not a LegoComponent instance + or a list of LegoComponent instances. + AssertionError: If the component or a component with the same UUID + is already contained in the assembly or any of its child assemblies. + """ if isinstance(component, list): for c in component: self.add_component(c) @@ -116,6 +251,20 @@ class LegoAssembly: self.components.append(component) def add_assembly(self, assembly: LegoAssembly | List[LegoAssembly]) -> None: + """ + Adds a subassembly to the current assembly. + Use add() to handle both components and assemblies. + + Args: + assembly (LegoAssembly or List[LegoAssembly]): + The subassembly or list of subassemblies to add. + + Raises: + TypeError: If the argument is not a LegoAssembly instance + or a list of LegoAssembly instances. + AssertionError: If the subassembly or a subassembly with the same UUID + is already contained in the assembly or any of its child assemblies. + """ if isinstance(assembly, list): for a in assembly: self.add_assembly(a) @@ -138,6 +287,18 @@ class LegoAssembly: def add( self, part: LegoAssembly | LegoComponent | List[LegoAssembly | LegoComponent] ) -> None: + """ + Adds either a Lego component, a subassembly or a (mixed) list of them to the + current assembly. Uses internal functions add_component() and add_assembly(). + + Args: + part (LegoAssembly or LegoComponent or List[LegoAssembly or LegoComponent]): + The part or parts to add. + + Raises: + TypeError: If the argument is not a LegoAssembly instance or a LegoComponent + instance, or a (mixed) list of them. + """ if isinstance(part, LegoComponent): self.add_component(part) elif isinstance(part, LegoAssembly): @@ -153,9 +314,29 @@ class LegoAssembly: ) def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]: + """ + Returns a dictionary of the assembly's children (components and + sub-assemblies), sorted by category. + + Returns: + A dictionary with two keys - "components" and "assemblies" - and + corresponding lists of LegoComponents and LegoAssemblies as values. + """ return {"components": self.components, "assemblies": self.assemblies} def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]: + """ + Gets a full list of all components contained in the assembly or any of + its sub-assemblies. + + Args: + max_depth: An integer indicating the maximum depth of recursion. + -1 means unlimited depth. 0 returns only direc children. + + Returns: + A list of all LegoComponents contained in the current assembly or + its sub-assemblies. + """ component_list = [] component_list.extend(self.components) if max_depth > 0 or max_depth < 0: @@ -164,41 +345,78 @@ class LegoAssembly: return component_list def get_root_assembly(self) -> LegoAssembly: + """ + Returns the root LegoAssembly of the current assembly by recursively + searching up the tree until the top-most parent (the root) is found. + + Returns: + The root LegoAssembly of the current LegoAssembly instance. + """ 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 + """ + Recursively searches through the assembly and component + tree to determine whether the specified UUID exists anywhere + within it. + + Args: + uuid_: A UUID object representing the ID to search for. + + Returns: + True if the UUID exists in the assembly or any of its sub-assemblies, + False otherwise. + """ 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: + """ + Serializes the current LegoAssembly instance and its descendants into a dict. + + Returns: + A dictionary representation of the current assembly, including all + of its component and sub-assembly children. + """ dict_ = { "uuid": self.uuid, "properties": self.properties, "layer": self.layer, } - # 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_} def __repr__(self): + """ + String representation of the object including the component label and UUID. + """ return f"LegoAssembly {self.properties['label']} [{self.uuid}]" def clone(self, label: Optional[str] = None) -> LegoAssembly: + """ + Creates a deep clone of the current LegoAssembly instance, including + all of its component and sub-assembly children. The assigned uuid changes. + Optionally a new label can be passed. + + Args: + label: The label (name) for the cloned assembly. If none is passed, + uses the same label as the original assembly. + + Returns: + The cloned LegoAssembly instance. + """ if label is None: label = self.properties["label"] clone = LegoAssembly(self.layer, None, self.properties) @@ -211,12 +429,22 @@ class LegoAssembly: def print_assembly_tree(root, level=0, is_last=False): + """ + Prints the assembly tree starting from root with a visualization + implemented with text characters. + + Args: + root (LegoAssembly): The root of the assembly tree to print. + level (int): The indentation level. Defaults to 0. + is_last (bool): Determines whether the current node is the last in level. + Defaults to False. + """ if not isinstance(root, LegoAssembly): raise TypeError( f"Argument should be of type {LegoAssembly.__name__}, " f"got {type(root).__name__} instead." ) - # print component + """ Print the items. """ assembly_padding = "" if level > 0: assembly_padding += "│ " * (level - 1) @@ -225,11 +453,11 @@ def print_assembly_tree(root, level=0, is_last=False): else: assembly_padding += "├── " print(f"{assembly_padding}{root}") - # recursively print child components + """ 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 + """ Print the components. """ for i, item in enumerate(root.components): component_padding = "│ " * level if not is_last else " " component_padding += "├── " if i < len(root.components) - 1 else "└── " @@ -237,6 +465,18 @@ def print_assembly_tree(root, level=0, is_last=False): def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False): + """ + Recursively checks whether the aggregation hierarchy from `root` is correct. + + Args: + root (LegoAssembly): The root of the assembly tree. + strict (bool): If True, the function will return False if any assembly + or component with a layer level equal to root's is found. + Defaults to False. + + Returns: + True if the aggregation hierarchy is correct. False otherwise. + """ if not isinstance(root, LegoAssembly): raise TypeError( f"Argument should be of type {LegoAssembly.__name__}, " @@ -257,7 +497,20 @@ def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False): class KPIEncoder(json.JSONEncoder): + """ + JSON encoder that handles special class types for KPI serialization. + """ + def default(self, o): + """ + Overrides default method to handle special conversion cases. + + Args: + o : Object to be converted. + + Returns: + Converted object or super method if no applicable case is found. + """ if isinstance(o, uuid.UUID): return "kpi-" + str(o) if isinstance(o, (AggregationLayer)): -- GitLab