1717
1818
1919import logging
20- from typing import Any , Iterable , Iterator , List , Optional , Set
20+ from typing import Iterable , List , Optional , ValuesView
2121
2222from pydantic import Field
2323
24+ from structurizr .model .deployment_node import DeploymentNode , DeploymentNodeIO
25+
2426from ..abstract_base import AbstractBase
2527from ..base_model import BaseModel
28+ from .container import Container
29+ from .container_instance import ContainerInstance
2630from .element import Element
2731from .enterprise import Enterprise , EnterpriseIO
2832from .person import Person , PersonIO
@@ -52,18 +56,18 @@ class ModelIO(BaseModel):
5256 """
5357
5458 enterprise : Optional [EnterpriseIO ] = Field (
55- None , description = "The enterprise associated with this model."
59+ default = None , description = "The enterprise associated with this model."
5660 )
57- people : Optional [ List [PersonIO ] ] = Field (
58- [], description = "The set of people belonging to this model."
61+ people : List [PersonIO ] = Field (
62+ default = [], description = "The set of people belonging to this model."
5963 )
60- software_systems : Optional [ List [SoftwareSystemIO ] ] = Field (
61- [],
64+ software_systems : List [SoftwareSystemIO ] = Field (
65+ default = [],
6266 alias = "softwareSystems" ,
6367 description = "The set of software systems belonging to this model." ,
6468 )
65- deployment_nodes : Optional [ List [Any ] ] = Field (
66- [],
69+ deployment_nodes : List [DeploymentNodeIO ] = Field (
70+ default = [],
6771 alias = "deploymentNodes" ,
6872 description = "The set of deployment nodes belonging to this model." ,
6973 )
@@ -87,9 +91,8 @@ def __init__(
8791 self ,
8892 enterprise : Optional [Enterprise ] = None ,
8993 people : Optional [Iterable [Person ]] = (),
90- software_systems : Optional [Iterable [SoftwareSystem ]] = (),
91- # TODO
92- deployment_nodes : Optional [Iterable [Any ]] = (),
94+ software_systems : Iterable [SoftwareSystem ] = (),
95+ deployment_nodes : Iterable [DeploymentNode ] = (),
9396 ** kwargs ,
9497 ) -> None :
9598 """
@@ -110,24 +113,40 @@ def __init__(
110113 self ._id_generator = SequentialIntegerIDGenerator ()
111114
112115 def __contains__ (self , element : Element ):
113- return (
114- element in self .people
115- or element in self .software_systems
116- or element in self .deployment_nodes
117- )
116+ return element in self .get_elements ()
118117
119118 @classmethod
120119 def hydrate (cls , model_io : ModelIO ) -> "Model" :
121120 """"""
122- return Model (
121+ model = cls (
123122 enterprise = Enterprise .hydrate (model_io .enterprise )
124123 if model_io .enterprise is not None
125124 else None ,
126- people = map (Person .hydrate , model_io .people ),
127- software_systems = map (SoftwareSystem .hydrate , model_io .software_systems ),
128- # TODO: deployment nodes, relationships
125+ # TODO: relationships
129126 )
130127
128+ for person_io in model_io .people :
129+ person = Person .hydrate (person_io )
130+ model .add_person (person = person )
131+
132+ for software_system_io in model_io .software_systems :
133+ software_system = SoftwareSystem .hydrate (software_system_io )
134+ model .add_software_system (software_system = software_system )
135+
136+ for deployment_node_io in model_io .deployment_nodes :
137+ deployment_node = DeploymentNode .hydrate (deployment_node_io )
138+ model .add_deployment_node (deployment_node = deployment_node )
139+
140+ for element in model .get_elements ():
141+ for relationship in element .relationships : # type: Relationship
142+ relationship .source = model .get_element (relationship .source_id )
143+ relationship .destination = model .get_element (
144+ relationship .destination_id
145+ )
146+ model .add_relationship (relationship )
147+
148+ return model
149+
131150 def add_person (self , person = None , ** kwargs ) -> Person :
132151 """
133152 Add a new person to the model.
@@ -171,7 +190,7 @@ def add_software_system(self, software_system=None, **kwargs) -> SoftwareSystem:
171190 SoftwareSystem: Either the same or a new instance, depending on arguments.
172191
173192 Raises:
174- ValueError: When a person with the same name already exists.
193+ ValueError: When a software system with the same name already exists.
175194
176195 See Also:
177196 SoftwareSystem
@@ -188,17 +207,100 @@ def add_software_system(self, software_system=None, **kwargs) -> SoftwareSystem:
188207 self .software_systems .add (software_system )
189208 return software_system
190209
191- # def add_deployment_node(self, deployment_node=None,
192- # **kwargs) -> DeploymentNode:
193- # """Add a new software system to the model."""
194- # if deployment_node is None:
195- # deployment_node = DeploymentNode(**kwargs)
196- # if deployment_node.id in {d.id for d in self.deployment_nodes}:
197- # ValueError(
198- # f"A deployment node with the ID {deployment_node.id} already "
199- # f"exists in the model.")
200- # self.deployment_nodes.add(deployment_node)
201- # return deployment_node
210+ def add_container (
211+ self , container : Optional [Container ] = None , ** kwargs
212+ ) -> Container :
213+ """
214+ Add a new container to the model.
215+
216+ Args:
217+ container (Container, optional): Either provide a
218+ `Container` instance or
219+ **kwargs: Provide keyword arguments for instantiating a `Container`
220+ (recommended).
221+
222+ Returns:
223+ SoftwareSystem: Either the same or a new instance, depending on arguments.
224+
225+ Raises:
226+ ValueError: When a container with the same name already exists.
227+
228+ See Also:
229+ Container
230+
231+ """
232+ if container is None :
233+ container = Container (** kwargs )
234+ if any (container .name == c .name for c in container .parent .containers ):
235+ ValueError (
236+ f"A container with the name { container .name } already "
237+ f"exists in the model."
238+ )
239+ # TODO (midnighter): Modifying the parent seems like creating an undesired
240+ # tight link here.
241+ container .parent .add (container )
242+ self ._add_element (container )
243+ return container
244+
245+ def add_container_instance (
246+ self ,
247+ deployment_node : DeploymentNode ,
248+ container : Container ,
249+ replicate_container_relationships : bool ,
250+ ) -> ContainerInstance :
251+ """
252+ Add a new container instance to the model.
253+
254+ Args:
255+ deployment_node (DeploymentNode, optional): `DeploymentNode` instance
256+ container (Container, optional): `Container` instance
257+
258+ Returns:
259+ ContainerInstance: A container instance.
260+
261+ Raises:
262+ ValueError: When a container with the same name already exists.
263+
264+ See Also:
265+ ContainerInstance
266+
267+ """
268+ if container is None :
269+ raise ValueError ("A container must be specified." )
270+ # TODO: implement
271+ # instance_number =
272+
273+ def add_deployment_node (
274+ self , deployment_node : Optional [DeploymentNode ] = None , ** kwargs
275+ ) -> DeploymentNode :
276+ """
277+ Add a new deployment node to the model.
278+ Args:
279+ deployment_node (DeploymentNode, optional): Either provide a
280+ `DeploymentNode` instance or
281+ **kwargs: Provide keyword arguments for instantiating a `DeploymentNode`
282+ (recommended).
283+
284+ Returns:
285+ DeploymentNode: Either the same or a new instance, depending on arguments.
286+
287+ Raises:
288+ ValueError: When a deployment node with the same name already exists.
289+
290+ See Also:
291+ DeploymentNode
292+
293+ """
294+ if deployment_node is None :
295+ deployment_node = DeploymentNode (** kwargs )
296+ if deployment_node .id in {d .id for d in self .deployment_nodes }:
297+ ValueError (
298+ f"A deployment node with the ID { deployment_node .id } already "
299+ f"exists in the model."
300+ )
301+ self .deployment_nodes .add (deployment_node )
302+ self ._add_element (deployment_node )
303+ return deployment_node
202304
203305 def add_relationship (
204306 self , relationship : Relationship = None , ** kwargs
@@ -257,10 +359,19 @@ def get_relationship(self, id: str) -> Optional[Relationship]:
257359 """
258360 return self ._relationships_by_id .get (id )
259361
260- def get_relationships (self ) -> Iterator [Relationship ]:
362+ def get_relationships (self ) -> ValuesView [Relationship ]:
261363 """Return an iterator over all relationships contained in this model."""
262364 return self ._relationships_by_id .values ()
263365
366+ def get_elements (self ) -> ValuesView [Element ]:
367+ return self ._elements_by_id .values ()
368+
369+ def get_software_system_with_id (self , id : str ) -> Optional [SoftwareSystem ]:
370+ result = self .get_element (id )
371+ if not isinstance (result , SoftwareSystem ):
372+ return None
373+ return result
374+
264375 def _add_element (self , element : Element ) -> None :
265376 """"""
266377 if not element .id :
@@ -279,10 +390,16 @@ def _add_relationship(self, relationship: Relationship) -> bool:
279390 if not relationship .id :
280391 relationship .id = self ._id_generator .generate_id ()
281392 elif (
393+ # TODO(ilaif): @midnighter: not sure this is the best check,
394+ # we should have a global id check?
282395 relationship .id in self ._elements_by_id
283396 or relationship .id in self ._relationships_by_id
284397 ):
285398 raise ValueError (f"The relationship { relationship } has an existing ID." )
399+ relationship .source .add_relationship (relationship )
400+ self ._add_relationship_to_internal_structures (relationship )
401+ return True
402+
403+ def _add_relationship_to_internal_structures (self , relationship : Relationship ):
286404 self ._relationships_by_id [relationship .id ] = relationship
287405 self ._id_generator .found (relationship .id )
288- return True
0 commit comments