diff --git a/functions/classes.py b/functions/classes.py
index 7265d9370d1af4e7dd4a270d4a12f26130a191d5..96b993eec82976ad3ce743c5c8eaf4447f0f4f2c 100644
--- a/functions/classes.py
+++ b/functions/classes.py
@@ -66,8 +66,8 @@ class LegoComponent:
             ValueError: If the type of more_properties argument is not a dictionary.
         """
 
-        self.uuid: uuid.UUID = uuid.uuid4()
-        self.parent: None | LegoAssembly = 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:
@@ -82,6 +82,14 @@ class LegoComponent:
         for key, value in kwargs.items():
             self.properties[key] = deepcopy(value)
 
+    @property
+    def uuid(self) -> uuid.UUID:
+        return self._uuid
+
+    @property
+    def parent(self) -> None | LegoAssembly:
+        return self._parent
+
     def clone(self, new_label: Optional[str] = None) -> LegoComponent:
         """Return a new instance of LegoComponent identical to the current instance.
 
@@ -103,6 +111,23 @@ class LegoComponent:
         clone.properties["label"] = new_label
         return clone
 
+    def get_property(self, key: str, default: Any = None) -> Any:
+        """Get a property from the component.
+
+        Args:
+            key (str): Property name.
+            default (Any, optional): What to return if key is not available in
+                properties. Defaults to None.
+
+        Returns:
+            Any: Property value.
+        """
+        if key == "uuid":
+            return self._uuid
+        if key == "layer":
+            return self._layer
+        return self.properties.get(key, default=default)
+
     def get_root_assembly(self):
         """Return the top-level assembly in which the component belongs.
 
@@ -114,11 +139,11 @@ class LegoComponent:
             None | LegoAssembly: The root-level LegoAssembly or None if the component
                 has no parent.
         """
-        if self.parent is None:
+        if self._parent is None:
             return None
-        current_assembly = self.parent
-        while current_assembly.parent is not None:
-            current_assembly = current_assembly.parent
+        current_assembly = self._parent
+        while current_assembly._parent is not None:
+            current_assembly = current_assembly._parent
         return current_assembly
 
     def to_dict(self) -> dict:
@@ -131,7 +156,7 @@ class LegoComponent:
             dict[str, Any]: A dictionary representation of the object.
         """
         dict_ = {
-            "uuid": self.uuid,
+            "uuid": self._uuid,
             "properties": self.properties,
             "layer": self.layer,
         }
@@ -151,7 +176,7 @@ class LegoComponent:
             return False
 
         if (
-            self.uuid == obj.uuid
+            self._uuid == obj._uuid
             and self.layer == obj.layer
             and self.properties == obj.properties
         ):
@@ -175,8 +200,8 @@ class LegoComponent:
             str: A string converted from the LegoComponent instance.
         """
         if self.properties.get("label") is None:
-            return f"LegoComponent [{self.uuid}]"
-        return f"LegoComponent {self.properties['label']} [{self.uuid}]"
+            return f"LegoComponent [{self._uuid}]"
+        return f"LegoComponent {self.properties['label']} [{self._uuid}]"
 
 
 class LegoAssembly:
@@ -209,10 +234,10 @@ class LegoAssembly:
             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._uuid: uuid.UUID = uuid.uuid4()
+        self._parent: None | LegoAssembly = None
         self.properties: dict = {}
-        self.layer: AggregationLayer = layer
+        self._layer: AggregationLayer = layer
         if label is not None:
             self.properties["label"] = label
         for prop in properties:
@@ -222,8 +247,28 @@ class LegoAssembly:
                 raise ValueError(f"Unexpected argument type: {type(properties)}")
         for key, value in kwargs.items():
             self.properties[key] = deepcopy(value)
-        self.components: list[LegoComponent] = []
-        self.assemblies: list[LegoAssembly] = []
+        self._components: list[LegoComponent] = []
+        self._assemblies: list[LegoAssembly] = []
+
+    @property
+    def uuid(self) -> uuid.UUID:
+        return self._uuid
+
+    @property
+    def parent(self) -> None | LegoAssembly:
+        return self._parent
+
+    @property
+    def layer(self) -> AggregationLayer:
+        return self._layer
+
+    @property
+    def components(self) -> list[LegoComponent]:
+        return self._components
+
+    @property
+    def assemblies(self) -> list[LegoAssembly]:
+        return self._assemblies
 
     def add_component(self, component: LegoComponent | list[LegoComponent]) -> None:
         """
@@ -251,14 +296,14 @@ class LegoAssembly:
                 f"got {type(component).__name__} instead."
             )
 
-        if self.get_root_assembly().contains_uuid(component.uuid):
+        if self.get_root_assembly().contains_uuid(component._uuid):
             raise AssertionError(
                 f"This assembly or a subassembly already contains "
                 f"the component with ID "
-                f"{component.uuid}."
+                f"{component._uuid}."
             )
-        component.parent = self
-        self.components.append(component)
+        component._parent = self
+        self._components.append(component)
 
     def add_assembly(self, assembly: LegoAssembly | list[LegoAssembly]) -> None:
         """
@@ -286,13 +331,13 @@ class LegoAssembly:
                 f"got {type(assembly).__name__} instead."
             )
 
-        if self.get_root_assembly().contains_uuid(assembly.uuid):
+        if self.get_root_assembly().contains_uuid(assembly._uuid):
             raise AssertionError(
                 f"This assembly or a subassembly already contains "
-                f"the assembly with ID {assembly.uuid}."
+                f"the assembly with ID {assembly._uuid}."
             )
-        assembly.parent = self
-        self.assemblies.append(assembly)
+        assembly._parent = self
+        self._assemblies.append(assembly)
 
     def add(
         self, part: LegoAssembly | LegoComponent | list[LegoAssembly | LegoComponent]
@@ -332,7 +377,7 @@ class LegoAssembly:
             A dictionary with two keys - "components" and "assemblies" - and
             corresponding lists of LegoComponents and LegoAssemblies as values.
         """
-        return {"components": self.components, "assemblies": self.assemblies}
+        return {"components": self._components, "assemblies": self._assemblies}
 
     def get_component_list(self, max_depth: int = -1) -> list[LegoComponent]:
         """
@@ -341,19 +386,36 @@ class LegoAssembly:
 
         Args:
             max_depth: An integer indicating the maximum depth of recursion.
-                -1 means unlimited depth. 0 returns only direc children.
+                -1 means unlimited depth. 0 returns only direct 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:
-            for assembly in self.assemblies:
+        component_list.extend(self._components)
+        if not max_depth == 0:
+            for assembly in self._assemblies:
                 component_list.extend(assembly.get_component_list(max_depth - 1))
         return component_list
 
+    def get_property(self, key: str, default: Any = None) -> Any:
+        """Get a property from the assembly.
+
+        Args:
+            key (str): Property name.
+            default (Any, optional): What to return if key is not available in
+                properties. Defaults to None.
+
+        Returns:
+            Any: Property value.
+        """
+        if key == "uuid":
+            return self._uuid
+        if key == "layer":
+            return self._layer
+        return self.properties.get(key, default=default)
+
     def get_root_assembly(self) -> LegoAssembly:
         """
         Returns the root LegoAssembly of the current assembly by recursively
@@ -363,8 +425,8 @@ class LegoAssembly:
             The root LegoAssembly of the current LegoAssembly instance.
         """
         current_assembly = self
-        while current_assembly.parent is not None:
-            current_assembly = current_assembly.parent
+        while current_assembly._parent is not None:
+            current_assembly = current_assembly._parent
         return current_assembly
 
     def contains_uuid(self, uuid_: uuid.UUID):
@@ -380,13 +442,13 @@ class LegoAssembly:
             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))
+        component_ids = list(map(lambda c: c._uuid, self._components))
         if uuid_ in component_ids:
             return True
-        assembly_ids = list(map(lambda a: a.uuid, self.assemblies))
+        assembly_ids = list(map(lambda a: a._uuid, self._assemblies))
         if uuid_ in assembly_ids:
             return True
-        for assembly in self.assemblies:
+        for assembly in self._assemblies:
             if assembly.contains_uuid(uuid_):
                 return True
         return False
@@ -400,12 +462,12 @@ class LegoAssembly:
             of its component and sub-assembly children.
         """
         dict_ = {
-            "uuid": self.uuid,
+            "uuid": self._uuid,
             "properties": self.properties,
-            "layer": self.layer,
+            "layer": self._layer,
         }
-        dict_["components"] = [component.to_dict() for component in self.components]
-        dict_["assemblies"] = [assembly.to_dict() for assembly in self.assemblies]
+        dict_["components"] = [component.to_dict() for component in self._components]
+        dict_["assemblies"] = [assembly.to_dict() for assembly in self._assemblies]
         return {"assembly": dict_}
 
     def clone(self, label: Optional[str] = None) -> LegoAssembly:
@@ -423,11 +485,11 @@ class LegoAssembly:
         """
         if label is None:
             label = self.properties["label"]
-        clone = LegoAssembly(self.layer, None, deepcopy(self.properties))
+        clone = LegoAssembly(self._layer, None, deepcopy(self.properties))
         clone.properties["label"] = label
-        for component in self.components:
+        for component in self._components:
             clone.add_component(component.clone())
-        for assembly in self.assemblies:
+        for assembly in self._assemblies:
             clone.add_assembly(assembly.clone())
         return clone
 
@@ -446,11 +508,11 @@ class LegoAssembly:
             return False
 
         if (
-            self.uuid == obj.uuid
+            self._uuid == obj._uuid
             and self.properties == obj.properties
-            and self.layer == obj.layer
-            and self.components == obj.components
-            and self.assemblies == obj.assemblies
+            and self._layer == obj._layer
+            and self._components == obj._components
+            and self._assemblies == obj._assemblies
         ):
             return True
         else:
@@ -460,7 +522,7 @@ class LegoAssembly:
         """
         String representation of the object including the component label and UUID.
         """
-        return f"LegoAssembly {self.properties['label']} [{self.uuid}]"
+        return f"LegoAssembly {self.properties['label']} [{self._uuid}]"
 
 
 def print_assembly_tree(root: LegoAssembly, levels: list[bool] = None) -> None:
@@ -486,12 +548,12 @@ def print_assembly_tree(root: LegoAssembly, levels: list[bool] = None) -> None:
         assembly_padding = "├── " if levels[-1] else "└── "
     print(f"{connection_padding[:-4]}{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
+    for i, assembly in enumerate(root._assemblies):
+        is_last = i == len(root._assemblies) - 1 and len(root._components) == 0
         print_assembly_tree(assembly, [*levels, not is_last])
     """ Print the components. """
-    for i, component in enumerate(root.components):
-        component_padding = "├── " if i < len(root.components) - 1 else "└── "
+    for i, component in enumerate(root._components):
+        component_padding = "├── " if i < len(root._components) - 1 else "└── "
         print(f"{connection_padding}{component_padding}{component}")
 
 
@@ -516,11 +578,11 @@ def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False):
     higher_level = operator.le
     if strict:
         higher_level = operator.lt
-    for component in root.components:
-        if not higher_level(root.layer.value, component.layer.value):
+    for component in root._components:
+        if not higher_level(root._layer.value, component.layer.value):
             return False
-    for assembly in root.assemblies:
-        if not higher_level(root.layer.value, assembly.layer.value):
+    for assembly in root._assemblies:
+        if not higher_level(root._layer.value, assembly._layer.value):
             return False
         if not correct_aggregation_hierarchy(assembly, strict):
             return False