Skip to content
This repository was archived by the owner on Jan 21, 2023. It is now read-only.

Commit eda3e05

Browse files
committed
refactor: combine all views in ViewSet into a single dictionary
1 parent 634ca23 commit eda3e05

File tree

2 files changed

+91
-95
lines changed

2 files changed

+91
-95
lines changed

src/structurizr/view/view_set.py

Lines changed: 81 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
from itertools import chain
20-
from typing import TYPE_CHECKING, Iterable, List, Optional, Set
20+
from typing import TYPE_CHECKING, Iterable, List, Optional
2121

2222
from pydantic import Field
2323

@@ -91,19 +91,71 @@ def __init__(
9191
) -> None:
9292
"""Initialize a view set."""
9393
super().__init__(**kwargs)
94-
# self.enterprise_context_views = set(enterprise_context_views)
95-
self.system_landscape_views: Set[SystemLandscapeView] = set(
96-
system_landscape_views
94+
all_views = chain(
95+
system_landscape_views,
96+
system_context_views,
97+
container_views,
98+
component_views,
99+
deployment_views,
100+
dynamic_views,
101+
filtered_views,
97102
)
98-
self.system_context_views: Set[SystemContextView] = set(system_context_views)
99-
self.container_views: Set[ContainerView] = set(container_views)
100-
self.component_views: Set[ComponentView] = set(component_views)
101-
self.deployment_views: Set[DeploymentView] = set(deployment_views)
102-
self.dynamic_views: Set[DynamicView] = set(dynamic_views)
103-
self.filtered_views: Set[FilteredView] = set(filtered_views)
103+
self._views = {view.key: view for view in all_views}
104104
self.configuration = Configuration() if configuration is None else configuration
105105
self.set_model(model)
106106

107+
@property
108+
def system_landscape_views(self) -> Iterable[SystemLandscapeView]:
109+
"""Return the SystemLandscapeViews in this ViewSet."""
110+
return (
111+
view
112+
for view in self._views.values()
113+
if isinstance(view, SystemLandscapeView)
114+
)
115+
116+
@property
117+
def system_context_views(self) -> Iterable[SystemContextView]:
118+
"""Return the SystemContextViews in this ViewSet."""
119+
return (
120+
view for view in self._views.values() if isinstance(view, SystemContextView)
121+
)
122+
123+
@property
124+
def container_views(self) -> Iterable[ContainerView]:
125+
"""Return the ContainerViews in this ViewSet."""
126+
return (
127+
view for view in self._views.values() if isinstance(view, ContainerView)
128+
)
129+
130+
@property
131+
def component_views(self) -> Iterable[ComponentView]:
132+
"""Return the CompoentViews in this ViewSet."""
133+
return (
134+
view for view in self._views.values() if isinstance(view, ComponentView)
135+
)
136+
137+
@property
138+
def deployment_views(self) -> Iterable[DeploymentView]:
139+
"""Return the DeploymentViews in this ViewSet."""
140+
return (
141+
view for view in self._views.values() if isinstance(view, DeploymentView)
142+
)
143+
144+
@property
145+
def dynamic_views(self) -> Iterable[DynamicView]:
146+
"""Return the DynamicViews in this ViewSet."""
147+
return (view for view in self._views.values() if isinstance(view, DynamicView))
148+
149+
@property
150+
def filtered_views(self) -> Iterable[FilteredView]:
151+
"""Return the FilteredViews in this ViewSet."""
152+
return (view for view in self._views.values() if isinstance(view, FilteredView))
153+
154+
@property
155+
def all_views(self) -> Iterable[AbstractView]:
156+
"""Return all the views in this ViewSet."""
157+
return self._views.values()
158+
107159
@classmethod
108160
def hydrate(cls, views: ViewSetIO, model: "Model") -> "ViewSet":
109161
"""Hydrate a new ViewSet instance from its IO."""
@@ -187,6 +239,9 @@ def _hydrate_view(cls, view: View, model: "Model") -> None:
187239
relationship_view.id
188240
)
189241

242+
def _add_view(self, view: View) -> None:
243+
self._views[view.key] = view
244+
190245
def create_system_landscape_view(
191246
self, system_landscape_view: Optional[SystemLandscapeView] = None, **kwargs
192247
) -> SystemLandscapeView:
@@ -205,7 +260,7 @@ def create_system_landscape_view(
205260
)
206261
self._ensure_key_is_specific_and_unique(system_landscape_view.key)
207262
system_landscape_view.set_viewset(self)
208-
self.system_landscape_views.add(system_landscape_view)
263+
self._add_view(system_landscape_view)
209264
return system_landscape_view
210265

211266
def create_system_context_view(
@@ -226,7 +281,7 @@ def create_system_context_view(
226281
system_context_view = SystemContextView(**kwargs)
227282
self._ensure_key_is_specific_and_unique(system_context_view.key)
228283
system_context_view.set_viewset(self)
229-
self.system_context_views.add(system_context_view)
284+
self._add_view(system_context_view)
230285
return system_context_view
231286

232287
def create_container_view(
@@ -247,7 +302,7 @@ def create_container_view(
247302
container_view = ContainerView(**kwargs)
248303
self._ensure_key_is_specific_and_unique(container_view.key)
249304
container_view.set_viewset(self)
250-
self.container_views.add(container_view)
305+
self._add_view(container_view)
251306
return container_view
252307

253308
def create_component_view(
@@ -266,7 +321,7 @@ def create_component_view(
266321
component_view = ComponentView(**kwargs)
267322
self._ensure_key_is_specific_and_unique(component_view.key)
268323
component_view.set_viewset(self)
269-
self.component_views.add(component_view)
324+
self._add_view(component_view)
270325
return component_view
271326

272327
def create_deployment_view(self, **kwargs) -> DeploymentView:
@@ -280,7 +335,7 @@ def create_deployment_view(self, **kwargs) -> DeploymentView:
280335
self._ensure_key_is_specific_and_unique(deployment_view.key)
281336
deployment_view.set_viewset(self)
282337
deployment_view.set_model(self.model)
283-
self.deployment_views.add(deployment_view)
338+
self._add_view(deployment_view)
284339
return deployment_view
285340

286341
def create_dynamic_view(self, **kwargs) -> DynamicView:
@@ -294,7 +349,7 @@ def create_dynamic_view(self, **kwargs) -> DynamicView:
294349
self._ensure_key_is_specific_and_unique(dynamic_view.key)
295350
dynamic_view.set_viewset(self)
296351
dynamic_view.set_model(self.model)
297-
self.dynamic_views.add(dynamic_view)
352+
self._add_view(dynamic_view)
298353
return dynamic_view
299354

300355
def create_filtered_view(self, **kwargs) -> FilteredView:
@@ -307,100 +362,34 @@ def create_filtered_view(self, **kwargs) -> FilteredView:
307362
filtered_view = FilteredView(**kwargs)
308363
self._ensure_key_is_specific_and_unique(filtered_view.key)
309364
filtered_view.set_viewset(self)
310-
self.filtered_views.add(filtered_view)
365+
self._add_view(filtered_view)
311366
return filtered_view
312367

313368
def get_view(self, key: str) -> Optional[AbstractView]:
314369
"""Return the view with the given key, or None."""
315-
all_views = chain(
316-
self.system_landscape_views,
317-
self.system_context_views,
318-
self.container_views,
319-
self.component_views,
320-
self.deployment_views,
321-
self.dynamic_views,
322-
self.filtered_views,
323-
)
324-
return next((view for view in all_views if view.key == key), None)
370+
return self._views.get(key)
325371

326372
def __getitem__(self, key: str) -> AbstractView:
327373
"""Return the view with the given key or raise a KeyError."""
328-
result = self.get_view(key)
374+
result = self._views.get(key)
329375
if not result:
330376
raise KeyError(f"No view with key '{key}' in ViewSet")
331377
return result
332378

333379
def copy_layout_information_from(self, source: "ViewSet") -> None:
334380
"""Copy all the layout information from a source ViewSet."""
335381

336-
# Note that filtered views don't have any layout information to copy.
337-
for source_view in source.system_landscape_views:
338-
destination_view = self._find_system_landscape_view(source_view)
339-
if destination_view:
340-
destination_view.copy_layout_information_from(source_view)
341-
342-
for source_view in source.system_context_views:
343-
destination_view = self._find_system_context_view(source_view)
344-
if destination_view:
345-
destination_view.copy_layout_information_from(source_view)
346-
347-
for source_view in source.container_views:
348-
destination_view = self._find_container_view(source_view)
349-
if destination_view:
350-
destination_view.copy_layout_information_from(source_view)
351-
352-
for source_view in source.component_views:
353-
destination_view = self._find_component_view(source_view)
354-
if destination_view:
355-
destination_view.copy_layout_information_from(source_view)
356-
357-
for source_view in source.dynamic_views:
358-
destination_view = self._find_dynamic_view(source_view)
359-
if destination_view:
360-
destination_view.copy_layout_information_from(source_view)
361-
362-
for source_view in source.deployment_views:
363-
destination_view = self._find_deployment_view(source_view)
364-
if destination_view:
382+
for source_view in source.all_views:
383+
destination_view = self.get_view(source_view.key)
384+
if (
385+
destination_view
386+
and isinstance(destination_view, View)
387+
and type(destination_view) is type(source_view)
388+
):
365389
destination_view.copy_layout_information_from(source_view)
366390

367391
def _ensure_key_is_specific_and_unique(self, key: str) -> None:
368392
if key is None or key == "":
369393
raise ValueError("A key must be specified.")
370394
if self.get_view(key) is not None:
371395
raise ValueError(f"View already exists in workspace with key '{key}'.")
372-
373-
def _find_system_landscape_view(
374-
self, view: SystemLandscapeView
375-
) -> Optional[SystemLandscapeView]:
376-
for current_view in self.system_landscape_views:
377-
if view.key == current_view.key:
378-
return current_view
379-
380-
def _find_system_context_view(
381-
self,
382-
view: SystemContextView,
383-
) -> Optional[SystemContextView]:
384-
for current_view in self.system_context_views:
385-
if view.key == current_view.key:
386-
return current_view
387-
388-
def _find_container_view(self, view: ContainerView) -> Optional[ContainerView]:
389-
for current_view in self.container_views:
390-
if view.key == current_view.key:
391-
return current_view
392-
393-
def _find_component_view(self, view: ComponentView) -> Optional[ComponentView]:
394-
for current_view in self.component_views:
395-
if view.key == current_view.key:
396-
return current_view
397-
398-
def _find_dynamic_view(self, view: DynamicView) -> Optional[DynamicView]:
399-
for current_view in self.dynamic_views:
400-
if view.key == current_view.key:
401-
return current_view
402-
403-
def _find_deployment_view(self, view: DeploymentView) -> DeploymentView:
404-
for current_view in self.deployment_views:
405-
if view.key == current_view.key:
406-
return current_view

tests/unit/view/test_view_set.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
"""Ensure the correct behaviour of ViewSet."""
1414

15+
from typing import Iterable
16+
1517
import pytest
1618

1719
from structurizr.model.model import Model
@@ -37,7 +39,7 @@ def test_view_set_construction(empty_model):
3739
"""Test constructing a new view set."""
3840
viewset = ViewSet(model=empty_model)
3941
assert viewset.model is empty_model
40-
assert viewset.dynamic_views == set()
42+
assert count(viewset.dynamic_views) == 0
4143

4244

4345
def test_adding_dynamic_view(empty_model):
@@ -58,7 +60,7 @@ def test_dynamic_view_hydrated(empty_viewset):
5860
io = ViewSetIO.from_orm(viewset)
5961

6062
new_viewset = ViewSet.hydrate(io, viewset.model)
61-
assert len(new_viewset.dynamic_views) == 1
63+
assert count(new_viewset.dynamic_views) == 1
6264
view = list(new_viewset.dynamic_views)[0]
6365
assert view.description == "dynamic"
6466
assert view.element is system1
@@ -105,7 +107,7 @@ def test_filtered_view_hydrated(empty_viewset):
105107
io = ViewSetIO.from_orm(viewset)
106108

107109
new_viewset = ViewSet.hydrate(io, viewset.model)
108-
assert len(new_viewset.filtered_views) == 1
110+
assert count(new_viewset.filtered_views) == 1
109111
view = list(new_viewset.filtered_views)[0]
110112
assert view.description == "filtered"
111113
assert isinstance(view.view, ContainerView)
@@ -159,3 +161,8 @@ def test_duplicate_key_raises_error(empty_viewset):
159161
viewset.create_container_view(
160162
key="container1", description="container", software_system=system1
161163
)
164+
165+
166+
def count(iterable: Iterable) -> int:
167+
"""Count items in an iterable, as len doesn't work on generators."""
168+
return sum(1 for x in iterable)

0 commit comments

Comments
 (0)