Skip to content

Commit aa6875b

Browse files
yt-msMidnighter
authored andcommitted
refactor: make operations on deployment nodes consistent with software systems and containers in Model.
1 parent 32823b9 commit aa6875b

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed

src/structurizr/model/model.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ def __init__(
111111
"""
112112
super().__init__(**kwargs)
113113
self.enterprise = enterprise
114-
self.deployment_nodes = set(deployment_nodes)
115114
self.implied_relationship_strategy = implied_relationship_strategy
116115
# TODO: simply iterate attributes
117116
self._elements_by_id = {}
@@ -132,6 +131,15 @@ def people(self) -> Set[Person]:
132131
"""Return the people in the model."""
133132
return {e for e in self.get_elements() if isinstance(e, Person)}
134133

134+
@property
135+
def deployment_nodes(self) -> Set[DeploymentNode]:
136+
"""Return the *top level* deployment nodes in the model."""
137+
return {
138+
e
139+
for e in self.get_elements()
140+
if isinstance(e, DeploymentNode) and e.parent is None
141+
}
142+
135143
@classmethod
136144
def hydrate(cls, model_io: ModelIO) -> "Model":
137145
"""Return a new model, hydrated from its IO."""
@@ -219,6 +227,15 @@ def __iadd__(self, element: Element) -> "Model":
219227
f"A software system with the name '{element.name}' already "
220228
f"exists in the model."
221229
)
230+
elif isinstance(element, DeploymentNode):
231+
if any(
232+
element.name == d.name and isinstance(d, DeploymentNode)
233+
for d in self.get_elements()
234+
):
235+
raise ValueError(
236+
f"A deployment node with the name '{element.name}' already "
237+
f"exists in the model."
238+
)
222239
elif element.parent is None:
223240
raise ValueError(
224241
f"Element with name {element.name} has no parent. Please ensure "
@@ -255,20 +272,15 @@ def add_container_instance(
255272
# TODO: implement
256273
# instance_number =
257274

258-
def add_deployment_node(
259-
self, deployment_node: Optional[DeploymentNode] = None, **kwargs
260-
) -> DeploymentNode:
275+
def add_deployment_node(self, **kwargs) -> DeploymentNode:
261276
"""
262277
Add a new deployment node to the model.
263278
264279
Args:
265-
deployment_node (DeploymentNode, optional): Either provide a
266-
`DeploymentNode` instance or
267-
**kwargs: Provide keyword arguments for instantiating a `DeploymentNode`
268-
(recommended).
280+
**kwargs: Provide keyword arguments for instantiating a `DeploymentNode`.
269281
270282
Returns:
271-
DeploymentNode: Either the same or a new instance, depending on arguments.
283+
DeploymentNode: The newly created DeploymentNode.
272284
273285
Raises:
274286
ValueError: When a deployment node with the same name already exists.
@@ -277,15 +289,8 @@ def add_deployment_node(
277289
DeploymentNode
278290
279291
"""
280-
if deployment_node is None:
281-
deployment_node = DeploymentNode(**kwargs)
282-
if deployment_node.id in {d.id for d in self.deployment_nodes}:
283-
ValueError(
284-
f"A deployment node with the ID {deployment_node.id} already "
285-
f"exists in the model."
286-
)
287-
self.deployment_nodes.add(deployment_node)
288-
self._add_element(deployment_node)
292+
deployment_node = DeploymentNode(**kwargs)
293+
self += deployment_node
289294
return deployment_node
290295

291296
def add_relationship(

tests/unit/model/test_model.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import pytest
1818

1919
from structurizr.model import Component, Container, Model, Person, SoftwareSystem
20+
from structurizr.model.deployment_node import DeploymentNode
2021

2122

2223
@pytest.fixture(scope="function")
@@ -78,6 +79,33 @@ def test_model_add_container_must_have_parent(empty_model: Model):
7879
empty_model += container
7980

8081

82+
def test_model_add_top_level_deployment_node(empty_model: Model):
83+
"""Make sure top-level deployment nodes are reflected in Model.deployent_nodes."""
84+
node = empty_model.add_deployment_node(name="node1")
85+
assert node is not None
86+
assert node in empty_model.deployment_nodes
87+
assert node in empty_model.get_elements()
88+
89+
90+
def test_model_cant_add_two_deployment_nodes_with_same_name(empty_model: Model):
91+
"""Make sure that deployment nodes (at any level) can't share a name."""
92+
node = empty_model.add_deployment_node(name="node1")
93+
with pytest.raises(
94+
ValueError,
95+
match="A deployment node with the name 'node1' already exists in the model.",
96+
):
97+
node.add_deployment_node(name="node1")
98+
99+
100+
def test_model_add_lower_level_deployment_node(empty_model: Model):
101+
"""Make sure child deployment nodes are not reflected in Model.deployent_nodes."""
102+
node1 = empty_model.add_deployment_node(name="node1")
103+
node2 = DeploymentNode(name="node2", parent=node1)
104+
empty_model += node2
105+
assert node2 not in empty_model.deployment_nodes
106+
assert node2 in empty_model.get_elements()
107+
108+
81109
def test_model_add_person_with_plusequals(empty_model: Model):
82110
"""Check that adding a Person to a Model with += works."""
83111
bob = Person(name="Bob")

0 commit comments

Comments
 (0)