buildzr 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
buildzr/dsl/dsl.py ADDED
@@ -0,0 +1,990 @@
1
+ from dataclasses import dataclass
2
+ import buildzr
3
+ from .factory import GenerateId
4
+ from typing_extensions import (
5
+ Self,
6
+ TypeGuard,
7
+ TypeIs,
8
+ )
9
+ from typing import (
10
+ Any,
11
+ Union,
12
+ Tuple,
13
+ List,
14
+ Set,
15
+ Dict,
16
+ Optional,
17
+ Generic,
18
+ TypeVar,
19
+ Protocol,
20
+ Callable,
21
+ Iterable,
22
+ Literal,
23
+ cast,
24
+ overload,
25
+ Sequence,
26
+ Type,
27
+ )
28
+
29
+ from buildzr.dsl.interfaces import (
30
+ DslWorkspaceElement,
31
+ DslElement,
32
+ DslViewsElement,
33
+ TSrc, TDst,
34
+ TParent, TChild,
35
+ )
36
+ from buildzr.dsl.relations import (
37
+ _is_software_fluent_relationship,
38
+ _is_container_fluent_relationship,
39
+ _Relationship,
40
+ _RelationshipDescription,
41
+ _FluentRelationship,
42
+ DslElementRelationOverrides,
43
+ )
44
+
45
+ def _child_name_transform(name: str) -> str:
46
+ return name.lower().replace(' ', '_')
47
+
48
+ # TODO: Remove, not used.
49
+ # def _create_linked_relationship_from(
50
+ # relationship: '_Relationship[TSrc, TDst]') -> buildzr.models.Relationship:
51
+
52
+ # src = relationship.source
53
+ # dst = relationship.destination
54
+
55
+ # return buildzr.models.Relationship(
56
+ # id=GenerateId.for_relationship(),
57
+ # sourceId=str(src.model.id),
58
+ # destinationId=str(dst.parent.model.id),
59
+ # linkedRelationshipId=relationship.model.id,
60
+ # )
61
+
62
+ TypedModel = TypeVar('TypedModel')
63
+ class TypedDynamicAttribute(Generic[TypedModel]):
64
+
65
+ def __init__(self, dynamic_attributes: Dict[str, Any]) -> None:
66
+ self._dynamic_attributes = dynamic_attributes
67
+
68
+ def __getattr__(self, name: str) -> TypedModel:
69
+ return cast(TypedModel, self._dynamic_attributes.get(name))
70
+
71
+ class Workspace(DslWorkspaceElement):
72
+ """
73
+ Represents a Structurizr workspace, which is a wrapper for a software architecture model, views, and documentation.
74
+ """
75
+
76
+ @property
77
+ def model(self) -> buildzr.models.Workspace:
78
+ return self._m
79
+
80
+ @property
81
+ def parent(self) -> None:
82
+ return None
83
+
84
+ @property
85
+ def children(self) -> Optional[List[Union['Person', 'SoftwareSystem']]]:
86
+ return self._children
87
+
88
+ def __init__(self, name: str, description: str="", scope: Literal['landscape', 'software_system', None]='software_system') -> None:
89
+ self._m = buildzr.models.Workspace()
90
+ self._parent = None
91
+ self._children: Optional[List[Union['Person', 'SoftwareSystem']]] = []
92
+ self._dynamic_attrs: Dict[str, Union['Person', 'SoftwareSystem']] = {}
93
+ self.model.id = GenerateId.for_workspace()
94
+ self.model.name = name
95
+ self.model.description = description
96
+ self.model.model = buildzr.models.Model(
97
+ people=[],
98
+ softwareSystems=[],
99
+ deploymentNodes=[],
100
+ )
101
+
102
+ scope_mapper: Dict[
103
+ str,
104
+ Literal[buildzr.models.Scope.Landscape, buildzr.models.Scope.SoftwareSystem, None]
105
+ ] = {
106
+ 'landscape': buildzr.models.Scope.Landscape,
107
+ 'software_system': buildzr.models.Scope.SoftwareSystem,
108
+ None: None
109
+ }
110
+
111
+ self.model.configuration = buildzr.models.WorkspaceConfiguration(
112
+ scope=scope_mapper[scope],
113
+ )
114
+
115
+ def _contains_group(
116
+ self,
117
+ name: str,
118
+ *models: Union[
119
+ 'Person',
120
+ 'SoftwareSystem',
121
+ _FluentRelationship['SoftwareSystem'],
122
+ _FluentRelationship['Container'],
123
+ ]
124
+ ) -> None:
125
+
126
+ def recursive_group_name_assign(software_system: 'SoftwareSystem') -> None:
127
+ software_system.model.group = name
128
+ for container in software_system.children:
129
+ container.model.group = name
130
+ for component in container.children:
131
+ component.model.group = name
132
+
133
+ for model in models:
134
+ if isinstance(model, Person):
135
+ model.model.group = name
136
+ elif isinstance(model, SoftwareSystem):
137
+ recursive_group_name_assign(model)
138
+ elif _is_software_fluent_relationship(model):
139
+ recursive_group_name_assign(model._parent)
140
+ elif _is_container_fluent_relationship(model):
141
+ recursive_group_name_assign(model._parent._parent)
142
+
143
+ self.contains(*models)
144
+
145
+ def contains(
146
+ self,
147
+ *models: Union[
148
+ 'Group',
149
+ 'Person',
150
+ 'SoftwareSystem',
151
+ _FluentRelationship['SoftwareSystem'],
152
+ _FluentRelationship['Container'],
153
+ ]) -> _FluentRelationship['Workspace']:
154
+
155
+ for model in models:
156
+ if isinstance(model, Group):
157
+ self._contains_group(model._name, *model._elements)
158
+ elif isinstance(model, Person):
159
+ self.add_element(model)
160
+ elif isinstance(model, SoftwareSystem):
161
+ self.add_element(model)
162
+ elif _is_software_fluent_relationship(model):
163
+ self.add_element(model._parent)
164
+ elif _is_container_fluent_relationship(model):
165
+ self.add_element(model._parent._parent)
166
+ return _FluentRelationship['Workspace'](self)
167
+
168
+ def person(self) -> TypedDynamicAttribute['Person']:
169
+ return TypedDynamicAttribute['Person'](self._dynamic_attrs)
170
+
171
+ def software_system(self) -> TypedDynamicAttribute['SoftwareSystem']:
172
+ return TypedDynamicAttribute['SoftwareSystem'](self._dynamic_attrs)
173
+
174
+ def add_element(self, element: Union['Person', 'SoftwareSystem']) -> None:
175
+ if isinstance(element, Person):
176
+ self._m.model.people.append(element._m)
177
+ element._parent = self
178
+ self._dynamic_attrs[_child_name_transform(element.model.name)] = element
179
+ if element._label:
180
+ self._dynamic_attrs[_child_name_transform(element._label)] = element
181
+ self._children.append(element)
182
+ elif isinstance(element, SoftwareSystem):
183
+ self._m.model.softwareSystems.append(element._m)
184
+ element._parent = self
185
+ self._dynamic_attrs[_child_name_transform(element.model.name)] = element
186
+ if element._label:
187
+ self._dynamic_attrs[_child_name_transform(element._label)] = element
188
+ self._children.append(element)
189
+ else:
190
+ raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(element)))
191
+
192
+ def with_views(
193
+ self,
194
+ *views: Union[
195
+ 'SystemLandscapeView',
196
+ 'SystemContextView',
197
+ 'ContainerView',
198
+ 'ComponentView',
199
+ ]
200
+ ) -> 'Views':
201
+ return Views(self).contains(*views)
202
+
203
+ def __getattr__(self, name: str) -> Union['Person', 'SoftwareSystem']:
204
+ return self._dynamic_attrs[name]
205
+
206
+ def __getitem__(self, name: str) -> Union['Person', 'SoftwareSystem']:
207
+ return self._dynamic_attrs[_child_name_transform(name)]
208
+
209
+ def __dir__(self) -> Iterable[str]:
210
+ return list(super().__dir__()) + list(self._dynamic_attrs.keys())
211
+
212
+ class SoftwareSystem(DslElementRelationOverrides):
213
+ """
214
+ A software system.
215
+ """
216
+
217
+ @property
218
+ def model(self) -> buildzr.models.SoftwareSystem:
219
+ return self._m
220
+
221
+ @property
222
+ def parent(self) -> Optional[Workspace]:
223
+ return self._parent
224
+
225
+ @property
226
+ def children(self) -> Optional[List['Container']]:
227
+ return self._children
228
+
229
+ @property
230
+ def sources(self) -> List[DslElement]:
231
+ return self._sources
232
+
233
+ @property
234
+ def destinations(self) -> List[DslElement]:
235
+ return self._destinations
236
+
237
+ @property
238
+ def tags(self) -> Set[str]:
239
+ return self._tags
240
+
241
+ def __init__(self, name: str, description: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
242
+ self._m = buildzr.models.SoftwareSystem()
243
+ self._parent: Optional[Workspace] = None
244
+ self._children: Optional[List['Container']] = []
245
+ self._sources: List[DslElement] = []
246
+ self._destinations: List[DslElement] = []
247
+ self._tags = {'Element', 'Software System'}.union(tags)
248
+ self._dynamic_attrs: Dict[str, 'Container'] = {}
249
+ self._label: Optional[str] = None
250
+ self.model.id = GenerateId.for_element()
251
+ self.model.name = name
252
+ self.model.description = description
253
+ self.model.tags = ','.join(self._tags)
254
+ self.model.properties = properties
255
+
256
+ def contains(
257
+ self,
258
+ *containers: Union['Container', _FluentRelationship['Container']]
259
+ ) -> _FluentRelationship['SoftwareSystem']:
260
+ if not self.model.containers:
261
+ self.model.containers = []
262
+
263
+ for child in containers:
264
+ if isinstance(child, Container):
265
+ self.add_element(child)
266
+ elif _is_container_fluent_relationship(child):
267
+ self.add_element(child._parent)
268
+ return _FluentRelationship['SoftwareSystem'](self)
269
+
270
+ def labeled(self, label: str) -> 'SoftwareSystem':
271
+ self._label = label
272
+ return self
273
+
274
+ def container(self) -> TypedDynamicAttribute['Container']:
275
+ return TypedDynamicAttribute['Container'](self._dynamic_attrs)
276
+
277
+ def add_element(self, element: 'Container') -> None:
278
+ if isinstance(element, Container):
279
+ self.model.containers.append(element.model)
280
+ element._parent = self
281
+ self._dynamic_attrs[_child_name_transform(element.model.name)] = element
282
+ if element._label:
283
+ self._dynamic_attrs[_child_name_transform(element._label)] = element
284
+ self._children.append(element)
285
+ else:
286
+ raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(element)))
287
+
288
+ def __getattr__(self, name: str) -> 'Container':
289
+ return self._dynamic_attrs[name]
290
+
291
+ def __getitem__(self, name: str) -> 'Container':
292
+ return self._dynamic_attrs[_child_name_transform(name)]
293
+
294
+ def __dir__(self) -> Iterable[str]:
295
+ return list(super().__dir__()) + list(self._dynamic_attrs.keys())
296
+
297
+ class Person(DslElementRelationOverrides):
298
+ """
299
+ A person who uses a software system.
300
+ """
301
+
302
+ @property
303
+ def model(self) -> buildzr.models.Person:
304
+ return self._m
305
+
306
+ @property
307
+ def parent(self) -> Optional[Workspace]:
308
+ return self._parent
309
+
310
+ @property
311
+ def children(self) -> None:
312
+ """
313
+ The `Person` element does not have any children, and will always return
314
+ `None`.
315
+ """
316
+ return None
317
+
318
+ @property
319
+ def sources(self) -> List[DslElement]:
320
+ return self._sources
321
+
322
+ @property
323
+ def destinations(self) -> List[DslElement]:
324
+ return self._destinations
325
+
326
+ @property
327
+ def tags(self) -> Set[str]:
328
+ return self._tags
329
+
330
+ def __init__(self, name: str, description: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
331
+ self._m = buildzr.models.Person()
332
+ self._parent: Optional[Workspace] = None
333
+ self._sources: List[DslElement] = []
334
+ self._destinations: List[DslElement] = []
335
+ self._tags = {'Element', 'Person'}.union(tags)
336
+ self._label: Optional[str] = None
337
+ self.model.id = GenerateId.for_element()
338
+ self.model.name = name
339
+ self.model.description = description
340
+ self.model.relationships = []
341
+ self.model.tags = ','.join(self._tags)
342
+ self.model.properties = properties
343
+
344
+ def labeled(self, label: str) -> 'Person':
345
+ self._label = label
346
+ return self
347
+
348
+ class Container(DslElementRelationOverrides):
349
+ """
350
+ A container (something that can execute code or host data).
351
+ """
352
+
353
+ @property
354
+ def model(self) -> buildzr.models.Container:
355
+ return self._m
356
+
357
+ @property
358
+ def parent(self) -> Optional[SoftwareSystem]:
359
+ return self._parent
360
+
361
+ @property
362
+ def children(self) -> Optional[List['Component']]:
363
+ return self._children
364
+
365
+ @property
366
+ def sources(self) -> List[DslElement]:
367
+ return self._sources
368
+
369
+ @property
370
+ def destinations(self) -> List[DslElement]:
371
+ return self._destinations
372
+
373
+ @property
374
+ def tags(self) -> Set[str]:
375
+ return self._tags
376
+
377
+ def contains(self, *components: 'Component') -> _FluentRelationship['Container']:
378
+ if not self.model.components:
379
+ self.model.components = []
380
+ for component in components:
381
+ self.add_element(component)
382
+ return _FluentRelationship['Container'](self)
383
+
384
+ def __init__(self, name: str, description: str="", technology: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
385
+ self._m = buildzr.models.Container()
386
+ self._parent: Optional[SoftwareSystem] = None
387
+ self._children: Optional[List['Component']] = []
388
+ self._sources: List[DslElement] = []
389
+ self._destinations: List[DslElement] = []
390
+ self._tags = {'Element', 'Container'}.union(tags)
391
+ self._dynamic_attrs: Dict[str, 'Component'] = {}
392
+ self._label: Optional[str] = None
393
+ self.model.id = GenerateId.for_element()
394
+ self.model.name = name
395
+ self.model.description = description
396
+ self.model.relationships = []
397
+ self.model.technology = technology
398
+ self.model.tags = ','.join(self._tags)
399
+ self.model.properties = properties
400
+
401
+ def labeled(self, label: str) -> 'Container':
402
+ self._label = label
403
+ return self
404
+
405
+ def component(self) -> TypedDynamicAttribute['Component']:
406
+ return TypedDynamicAttribute['Component'](self._dynamic_attrs)
407
+
408
+ def add_element(self, element: 'Component') -> None:
409
+ if isinstance(element, Component):
410
+ self.model.components.append(element.model)
411
+ element._parent = self
412
+ self._dynamic_attrs[_child_name_transform(element.model.name)] = element
413
+ if element._label:
414
+ self._dynamic_attrs[_child_name_transform(element._label)] = element
415
+ self._children.append(element)
416
+ else:
417
+ raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(element)))
418
+
419
+ def __getattr__(self, name: str) -> 'Component':
420
+ return self._dynamic_attrs[name]
421
+
422
+ def __getitem__(self, name: str) -> 'Component':
423
+ return self._dynamic_attrs[_child_name_transform(name)]
424
+
425
+ def __dir__(self) -> Iterable[str]:
426
+ return list(super().__dir__()) + list(self._dynamic_attrs.keys())
427
+
428
+ class Component(DslElementRelationOverrides):
429
+ """
430
+ A component (a grouping of related functionality behind an interface that runs inside a container).
431
+ """
432
+
433
+ @property
434
+ def model(self) -> buildzr.models.Component:
435
+ return self._m
436
+
437
+ @property
438
+ def parent(self) -> Optional[Container]:
439
+ return self._parent
440
+
441
+ @property
442
+ def children(self) -> None:
443
+ return None
444
+
445
+ @property
446
+ def sources(self) -> List[DslElement]:
447
+ return self._sources
448
+
449
+ @property
450
+ def destinations(self) -> List[DslElement]:
451
+ return self._destinations
452
+
453
+ @property
454
+ def tags(self) -> Set[str]:
455
+ return self._tags
456
+
457
+ def __init__(self, name: str, description: str="", technology: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
458
+ self._m = buildzr.models.Component()
459
+ self._parent: Optional[Container] = None
460
+ self._sources: List[DslElement] = []
461
+ self._destinations: List[DslElement] = []
462
+ self._tags = {'Element', 'Component'}.union(tags)
463
+ self._label: Optional[str] = None
464
+ self.model.id = GenerateId.for_element()
465
+ self.model.name = name
466
+ self.model.description = description
467
+ self.model.technology = technology
468
+ self.model.relationships = []
469
+ self.model.tags = ','.join(self._tags)
470
+ self.model.properties = properties
471
+
472
+ def labeled(self, label: str) -> 'Component':
473
+ self._label = label
474
+ return self
475
+
476
+ class Group:
477
+
478
+ def __init__(
479
+ self,
480
+ name: str,
481
+ *elements: Union[
482
+ Person,
483
+ SoftwareSystem,
484
+ _FluentRelationship[SoftwareSystem],
485
+ _FluentRelationship[Container],
486
+ ]) -> None:
487
+ self._name = name
488
+ self._elements = elements
489
+
490
+ _RankDirection = Literal['tb', 'bt', 'lr', 'rl']
491
+
492
+ _AutoLayout = Optional[
493
+ Union[
494
+ _RankDirection,
495
+ Tuple[_RankDirection, float],
496
+ Tuple[_RankDirection, float, float]
497
+ ]
498
+ ]
499
+
500
+ def _auto_layout_to_model(auto_layout: _AutoLayout) -> buildzr.models.AutomaticLayout:
501
+ """
502
+ See: https://docs.structurizr.com/dsl/language#autolayout
503
+ """
504
+
505
+ model = buildzr.models.AutomaticLayout()
506
+
507
+ def is_auto_layout_with_rank_separation(\
508
+ auto_layout: _AutoLayout,
509
+ ) -> TypeIs[Tuple[_RankDirection, float]]:
510
+ if isinstance(auto_layout, tuple):
511
+ return len(auto_layout) == 2 and \
512
+ type(auto_layout[0]) is _RankDirection and \
513
+ type(auto_layout[1]) is float
514
+ return False
515
+
516
+ def is_auto_layout_with_node_separation(\
517
+ auto_layout: _AutoLayout,
518
+ ) -> TypeIs[Tuple[_RankDirection, float, float]]:
519
+ if isinstance(auto_layout, tuple) and len(auto_layout) == 3:
520
+ return type(auto_layout[0]) is _RankDirection and \
521
+ all([type(x) is float for x in auto_layout[1:]])
522
+ return False
523
+
524
+ map_rank_direction: Dict[_RankDirection, buildzr.models.RankDirection] = {
525
+ 'lr': buildzr.models.RankDirection.LeftRight,
526
+ 'tb': buildzr.models.RankDirection.TopBottom,
527
+ 'rl': buildzr.models.RankDirection.RightLeft,
528
+ 'bt': buildzr.models.RankDirection.BottomTop,
529
+ }
530
+
531
+ if auto_layout is not None:
532
+ if is_auto_layout_with_rank_separation(auto_layout):
533
+ d, rs = cast(Tuple[_RankDirection, float], auto_layout)
534
+ model.rankDirection = map_rank_direction[cast(_RankDirection, d)]
535
+ model.rankSeparation = rs
536
+ elif is_auto_layout_with_node_separation(auto_layout):
537
+ d, rs, ns = cast(Tuple[_RankDirection, float, float], auto_layout)
538
+ model.rankDirection = map_rank_direction[cast(_RankDirection, d)]
539
+ model.rankSeparation = rs
540
+ model.nodeSeparation = ns
541
+ else:
542
+ model.rankDirection = map_rank_direction[cast(_RankDirection, auto_layout)]
543
+
544
+ if model.rankSeparation is None:
545
+ model.rankSeparation = 300
546
+ if model.nodeSeparation is None:
547
+ model.nodeSeparation = 300
548
+ if model.edgeSeparation is None:
549
+ model.edgeSeparation = 0
550
+ if model.implementation is None:
551
+ model.implementation = buildzr.models.Implementation.Graphviz
552
+ if model.vertices is None:
553
+ model.vertices = False
554
+
555
+ return model
556
+
557
+ class SystemLandscapeView:
558
+
559
+ from buildzr.dsl.expression import Expression, Element, Relationship
560
+
561
+ @property
562
+ def model(self) -> buildzr.models.SystemLandscapeView:
563
+ return self._m
564
+
565
+ @property
566
+ def parent(self) -> Optional['Views']:
567
+ return self._parent
568
+
569
+ def __init__(
570
+ self,
571
+ key: str,
572
+ description: str,
573
+ auto_layout: _AutoLayout='tb',
574
+ title: Optional[str]=None,
575
+ include_elements: List[Callable[[Workspace, Element], bool]]=[],
576
+ exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
577
+ include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
578
+ exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
579
+ properties: Optional[Dict[str, str]]=None,
580
+ ) -> None:
581
+ self._m = buildzr.models.SystemLandscapeView()
582
+ self._parent: Optional['Views'] = None
583
+
584
+ self._m.key = key
585
+ self._m.description = description
586
+
587
+ self._m.automaticLayout = _auto_layout_to_model(auto_layout)
588
+ self._m.title = title
589
+ self._m.properties = properties
590
+
591
+ self._include_elements = include_elements
592
+ self._exclude_elements = exclude_elements
593
+ self._include_relationships = include_relationships
594
+ self._exclude_relationships = exclude_relationships
595
+
596
+ def _on_added(self) -> None:
597
+
598
+ from buildzr.dsl.expression import Expression, Element, Relationship
599
+ from buildzr.models import ElementView, RelationshipView
600
+
601
+ expression = Expression(
602
+ include_elements=self._include_elements,
603
+ exclude_elements=self._exclude_elements,
604
+ include_relationships=self._include_relationships,
605
+ exclude_relationships=self._exclude_relationships,
606
+ )
607
+
608
+ workspace = self._parent._parent
609
+
610
+ include_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
611
+ lambda w, e: e.type == Person,
612
+ lambda w, e: e.type == SoftwareSystem
613
+ ]
614
+
615
+ exclude_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
616
+ lambda w, e: e.type == Container,
617
+ lambda w, e: e.type == Component,
618
+ ]
619
+
620
+ include_view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
621
+ lambda w, r: r.source.type == Person,
622
+ lambda w, r: r.source.type == SoftwareSystem,
623
+ lambda w, r: r.destination.type == Person,
624
+ lambda w, r: r.destination.type == SoftwareSystem,
625
+ ]
626
+
627
+ expression = Expression(
628
+ include_elements=self._include_elements + include_view_elements_filter,
629
+ exclude_elements=self._exclude_elements + exclude_view_elements_filter,
630
+ include_relationships=self._include_relationships + include_view_relationships_filter,
631
+ exclude_relationships=self._exclude_relationships,
632
+ )
633
+
634
+ element_ids = map(
635
+ lambda x: str(x.model.id),
636
+ expression.elements(workspace)
637
+ )
638
+
639
+ relationship_ids = map(
640
+ lambda x: str(x.model.id),
641
+ expression.relationships(workspace)
642
+ )
643
+
644
+ self._m.elements = []
645
+ for element_id in element_ids:
646
+ self._m.elements.append(ElementView(id=element_id))
647
+
648
+ self._m.relationships = []
649
+ for relationship_id in relationship_ids:
650
+ self._m.relationships.append(RelationshipView(id=relationship_id))
651
+
652
+ class SystemContextView:
653
+
654
+ """
655
+ If no filter is applied, this view includes all elements that have a direct
656
+ relationship with the selected `SoftwareSystem`.
657
+ """
658
+
659
+ from buildzr.dsl.expression import Expression, Element, Relationship
660
+
661
+ @property
662
+ def model(self) -> buildzr.models.SystemContextView:
663
+ return self._m
664
+
665
+ @property
666
+ def parent(self) -> Optional['Views']:
667
+ return self._parent
668
+
669
+ def __init__(
670
+ self,
671
+ software_system_selector: Callable[[Workspace], SoftwareSystem],
672
+ key: str,
673
+ description: str,
674
+ auto_layout: _AutoLayout='tb',
675
+ title: Optional[str]=None,
676
+ include_elements: List[Callable[[Workspace, Element], bool]]=[],
677
+ exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
678
+ include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
679
+ exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
680
+ properties: Optional[Dict[str, str]]=None,
681
+ ) -> None:
682
+ self._m = buildzr.models.SystemContextView()
683
+ self._parent: Optional['Views'] = None
684
+
685
+ self._m.key = key
686
+ self._m.description = description
687
+
688
+ self._m.automaticLayout = _auto_layout_to_model(auto_layout)
689
+ self._m.title = title
690
+ self._m.properties = properties
691
+
692
+ self._selector = software_system_selector
693
+ self._include_elements = include_elements
694
+ self._exclude_elements = exclude_elements
695
+ self._include_relationships = include_relationships
696
+ self._exclude_relationships = exclude_relationships
697
+
698
+ def _on_added(self) -> None:
699
+
700
+ from buildzr.dsl.expression import Expression, Element, Relationship
701
+ from buildzr.models import ElementView, RelationshipView
702
+
703
+ # TODO: Refactor below codes. Similar patterns may exists for other views.
704
+ # Maybe make the views a subclass of some abstract `BaseView` class?
705
+
706
+ software_system = self._selector(self._parent._parent)
707
+ self._m.softwareSystemId = software_system.model.id
708
+ view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
709
+ lambda w, e: e == software_system,
710
+ lambda w, e: software_system.model.id in e.sources.ids,
711
+ lambda w, e: software_system.model.id in e.destinations.ids,
712
+ ]
713
+
714
+ # TODO: (Or, TOTHINK?) The code below includes all sources and all
715
+ # destinations of the subject software system. What if we want to
716
+ # exclude a source? Maybe the predicates in `elements` and
717
+ # `relationships` should be ANDed together afterall?
718
+ view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
719
+ lambda w, r: software_system == r.source,
720
+ lambda w, r: software_system == r.destination,
721
+ ]
722
+
723
+ expression = Expression(
724
+ include_elements=self._include_elements + view_elements_filter,
725
+ exclude_elements=self._exclude_elements,
726
+ include_relationships=self._include_relationships + view_relationships_filter,
727
+ exclude_relationships=self._exclude_relationships,
728
+ )
729
+
730
+ workspace = self._parent._parent
731
+
732
+ element_ids = map(
733
+ lambda x: str(x.model.id),
734
+ expression.elements(workspace)
735
+ )
736
+
737
+ relationship_ids = map(
738
+ lambda x: str(x.model.id),
739
+ expression.relationships(workspace)
740
+ )
741
+
742
+ self._m.elements = []
743
+ for element_id in element_ids:
744
+ self._m.elements.append(ElementView(id=element_id))
745
+
746
+ self._m.relationships = []
747
+ for relationship_id in relationship_ids:
748
+ self._m.relationships.append(RelationshipView(id=relationship_id))
749
+
750
+ class ContainerView:
751
+
752
+ from buildzr.dsl.expression import Expression, Element, Relationship
753
+
754
+ @property
755
+ def model(self) -> buildzr.models.ContainerView:
756
+ return self._m
757
+
758
+ @property
759
+ def parent(self) -> Optional['Views']:
760
+ return self._parent
761
+
762
+ def __init__(
763
+ self,
764
+ software_system_selector: Callable[[Workspace], SoftwareSystem],
765
+ key: str,
766
+ description: str,
767
+ auto_layout: _AutoLayout='tb',
768
+ title: Optional[str]=None,
769
+ include_elements: List[Callable[[Workspace, Element], bool]]=[],
770
+ exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
771
+ include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
772
+ exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
773
+ properties: Optional[Dict[str, str]]=None,
774
+ ) -> None:
775
+ self._m = buildzr.models.ContainerView()
776
+ self._parent: Optional['Views'] = None
777
+
778
+ self._m.key = key
779
+ self._m.description = description
780
+
781
+ self._m.automaticLayout = _auto_layout_to_model(auto_layout)
782
+ self._m.title = title
783
+ self._m.properties = properties
784
+
785
+ self._selector = software_system_selector
786
+ self._include_elements = include_elements
787
+ self._exclude_elements = exclude_elements
788
+ self._include_relationships = include_relationships
789
+ self._exclude_relationships = exclude_relationships
790
+
791
+ def _on_added(self) -> None:
792
+
793
+ from buildzr.dsl.expression import Expression, Element, Relationship
794
+ from buildzr.models import ElementView, RelationshipView
795
+
796
+ software_system = self._selector(self._parent._parent)
797
+ self._m.softwareSystemId = software_system.model.id
798
+
799
+ container_ids = { container.model.id for container in software_system.children}
800
+
801
+ view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
802
+ lambda w, e: e.parent == software_system,
803
+ lambda w, e: any(container_ids.intersection({ id for id in e.sources.ids })),
804
+ lambda w, e: any(container_ids.intersection({ id for id in e.destinations.ids })),
805
+ ]
806
+
807
+ view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
808
+ lambda w, r: software_system == r.source.parent,
809
+ lambda w, r: software_system == r.destination.parent,
810
+ ]
811
+
812
+ expression = Expression(
813
+ include_elements=self._include_elements + view_elements_filter,
814
+ exclude_elements=self._exclude_elements,
815
+ include_relationships=self._include_relationships + view_relationships_filter,
816
+ exclude_relationships=self._exclude_relationships,
817
+ )
818
+
819
+ workspace = self._parent._parent
820
+
821
+ element_ids = map(
822
+ lambda x: str(x.model.id),
823
+ expression.elements(workspace)
824
+ )
825
+
826
+ relationship_ids = map(
827
+ lambda x: str(x.model.id),
828
+ expression.relationships(workspace)
829
+ )
830
+
831
+ self._m.elements = []
832
+ for element_id in element_ids:
833
+ self._m.elements.append(ElementView(id=element_id))
834
+
835
+ self._m.relationships = []
836
+ for relationship_id in relationship_ids:
837
+ self._m.relationships.append(RelationshipView(id=relationship_id))
838
+
839
+ class ComponentView:
840
+
841
+ from buildzr.dsl.expression import Expression, Element, Relationship
842
+
843
+ @property
844
+ def model(self) -> buildzr.models.ComponentView:
845
+ return self._m
846
+
847
+ @property
848
+ def parent(self) -> Optional['Views']:
849
+ return self._parent
850
+
851
+ def __init__(
852
+ self,
853
+ container_selector: Callable[[Workspace], Container],
854
+ key: str,
855
+ description: str,
856
+ auto_layout: _AutoLayout='tb',
857
+ title: Optional[str]=None,
858
+ include_elements: List[Callable[[Workspace, Element], bool]]=[],
859
+ exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
860
+ include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
861
+ exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
862
+ properties: Optional[Dict[str, str]]=None,
863
+ ) -> None:
864
+ self._m = buildzr.models.ComponentView()
865
+ self._parent: Optional['Views'] = None
866
+
867
+ self._m.key = key
868
+ self._m.description = description
869
+
870
+ self._m.automaticLayout = _auto_layout_to_model(auto_layout)
871
+ self._m.title = title
872
+ self._m.properties = properties
873
+
874
+ self._selector = container_selector
875
+ self._include_elements = include_elements
876
+ self._exclude_elements = exclude_elements
877
+ self._include_relationships = include_relationships
878
+ self._exclude_relationships = exclude_relationships
879
+
880
+ def _on_added(self) -> None:
881
+
882
+ from buildzr.dsl.expression import Expression, Element, Relationship
883
+ from buildzr.models import ElementView, RelationshipView
884
+
885
+ container = self._selector(self._parent._parent)
886
+ self._m.containerId = container.model.id
887
+
888
+ component_ids = { component.model.id for component in container.children }
889
+
890
+ view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
891
+ lambda w, e: e.parent == container,
892
+ lambda w, e: any(component_ids.intersection({ id for id in e.sources.ids })),
893
+ lambda w, e: any(component_ids.intersection({ id for id in e.destinations.ids })),
894
+ ]
895
+
896
+ view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
897
+ lambda w, r: container == r.source.parent,
898
+ lambda w, r: container == r.destination.parent,
899
+ ]
900
+
901
+ expression = Expression(
902
+ include_elements=self._include_elements + view_elements_filter,
903
+ exclude_elements=self._exclude_elements,
904
+ include_relationships=self._include_relationships + view_relationships_filter,
905
+ exclude_relationships=self._exclude_relationships,
906
+ )
907
+
908
+ workspace = self._parent._parent
909
+
910
+ element_ids = map(
911
+ lambda x: str(x.model.id),
912
+ expression.elements(workspace)
913
+ )
914
+
915
+ relationship_ids = map(
916
+ lambda x: str(x.model.id),
917
+ expression.relationships(workspace)
918
+ )
919
+
920
+ self._m.elements = []
921
+ for element_id in element_ids:
922
+ self._m.elements.append(ElementView(id=element_id))
923
+
924
+ self._m.relationships = []
925
+ for relationship_id in relationship_ids:
926
+ self._m.relationships.append(RelationshipView(id=relationship_id))
927
+
928
+ class Views(DslViewsElement):
929
+
930
+ @property
931
+ def model(self) -> buildzr.models.Views:
932
+ return self._m
933
+
934
+ @property
935
+ def parent(self) -> Optional[Workspace]:
936
+ return self._parent
937
+
938
+ def __init__(
939
+ self,
940
+ workspace: Workspace,
941
+ ) -> None:
942
+ self._m = buildzr.models.Views()
943
+ self._parent = workspace
944
+ self._parent._m.views = self._m
945
+
946
+ def contains(
947
+ self,
948
+ *views: Union[
949
+ SystemLandscapeView,
950
+ SystemContextView,
951
+ ContainerView,
952
+ ComponentView,
953
+ ]) -> Self:
954
+
955
+ for view in views:
956
+ view._parent = self
957
+ if isinstance(view, SystemLandscapeView):
958
+ view._on_added()
959
+ if self._m.systemLandscapeViews:
960
+ self._m.systemLandscapeViews.append(view.model)
961
+ else:
962
+ self._m.systemLandscapeViews = [view.model]
963
+ elif isinstance(view, SystemContextView):
964
+ view._on_added()
965
+ if self._m.systemContextViews:
966
+ self._m.systemContextViews.append(view.model)
967
+ else:
968
+ self._m.systemContextViews = [view.model]
969
+ elif isinstance(view, ContainerView):
970
+ view._on_added()
971
+ if self._m.containerViews:
972
+ self._m.containerViews.append(view.model)
973
+ else:
974
+ self._m.containerViews = [view.model]
975
+ elif isinstance(view, ComponentView):
976
+ view._on_added()
977
+ if self._m.componentViews:
978
+ self._m.componentViews.append(view.model)
979
+ else:
980
+ self._m.componentViews = [view.model]
981
+ else:
982
+ raise NotImplementedError("The view {0} is currently not supported", type(view))
983
+
984
+ return self
985
+
986
+ def get_workspace(self) -> Workspace:
987
+ """
988
+ Get the `Workspace` which contain this views definition.
989
+ """
990
+ return self._parent