Skip to content

Commit 1d6c521

Browse files
yt-msMidnighter
authored andcommitted
refactor: make components read-only on Container
1 parent ad91ffa commit 1d6c521

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

src/structurizr/model/container.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Container(StaticStructureElement):
6969
technology: The technology associated with this container
7070
(e.g. Apache Tomcat).
7171
tags: A comma separated list of tags associated with this container.
72-
components: The set of components within this container. Do not modify directly.
72+
components: The set of components within this container.
7373
properties: A set of arbitrary name-value properties.
7474
relationships: The set of relationships from this container to
7575
other elements.
@@ -98,18 +98,23 @@ def __init__(
9898
super().__init__(**kwargs)
9999
self.parent = parent
100100
self.technology = technology
101-
self.components = set(components)
101+
self._components = set(components)
102102

103103
self.tags.add(Tags.CONTAINER)
104104

105+
@property
106+
def components(self) -> Iterable[Component]:
107+
"""Return read-only list of child components."""
108+
return [c for c in self._components]
109+
105110
@classmethod
106111
def hydrate(
107112
cls,
108113
container_io: ContainerIO,
109114
software_system: "SoftwareSystem",
110115
model: "Model",
111116
) -> "Container":
112-
""""""
117+
"""Hydrate a new Container instance from its IO."""
113118
container = cls(
114119
**cls.hydrate_arguments(container_io),
115120
parent=software_system,
@@ -131,7 +136,8 @@ def add_component(self, **kwargs) -> Component:
131136

132137
def __iadd__(self, component: Component) -> "Container":
133138
"""Add a newly constructed component to this container."""
134-
# TODO: once we move past python 3.6 change to proper return type via __future__.annotations
139+
# TODO: once we move past python 3.6 change to proper return type via
140+
# __future__.annotations
135141
if component in self.components:
136142
# Nothing to do
137143
return self
@@ -145,13 +151,15 @@ def __iadd__(self, component: Component) -> "Container":
145151
component.parent = self
146152
elif component.parent is not self:
147153
raise ValueError(
148-
f"Component with name {component.name} already has parent {component.parent}. Cannot add to {self}."
154+
f"Component with name {component.name} already has parent "
155+
f"{component.parent}. Cannot add to {self}."
149156
)
150-
self.components.add(component)
157+
self._components.add(component)
151158
self.get_model().add_component(component)
152159
return self
153160

154161
def get_component_with_name(self, name: str) -> Component:
162+
"""Return a matching `Component` or None if not found."""
155163
for component in self.components:
156164
if component.name == name:
157165
return component

tests/unit/model/test_container.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import pytest
1818

19-
from structurizr.model import Component, Container
19+
from structurizr.model import Component, Container, ContainerIO
2020

2121

2222
class MockModel:
@@ -97,3 +97,14 @@ def test_adding_component_with_existing_parent_fails(empty_container: Container)
9797
component = empty_container.add_component(name="Component")
9898
with pytest.raises(ValueError, match="Component with name .* already has parent"):
9999
container2 += component
100+
101+
102+
def test_serialisation_of_child_components():
103+
"""Make sure that components are serialised even though read-only."""
104+
container = Container(name="Container", description="Description")
105+
container.set_model(_model)
106+
container.add_component(name="Component")
107+
io = ContainerIO.from_orm(container)
108+
109+
assert len(io.components) == 1
110+
assert io.components[0].name == "Component"

0 commit comments

Comments
 (0)