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/__about__.py +1 -0
- buildzr/__init__.py +4 -0
- buildzr/dsl/__init__.py +18 -0
- buildzr/dsl/dsl.py +990 -0
- buildzr/dsl/explorer.py +67 -0
- buildzr/dsl/expression.py +208 -0
- buildzr/dsl/factory/__init__.py +1 -0
- buildzr/dsl/factory/gen_id.py +23 -0
- buildzr/dsl/interfaces/__init__.py +14 -0
- buildzr/dsl/interfaces/interfaces.py +207 -0
- buildzr/dsl/relations.py +367 -0
- buildzr/encoders/__init__.py +1 -0
- buildzr/encoders/encoder.py +61 -0
- buildzr/models/__init__.py +1 -0
- buildzr/models/generate.sh +21 -0
- buildzr/models/models.py +1739 -0
- buildzr-0.0.1.dist-info/METADATA +140 -0
- buildzr-0.0.1.dist-info/RECORD +20 -0
- buildzr-0.0.1.dist-info/WHEEL +4 -0
- buildzr-0.0.1.dist-info/licenses/LICENSE.md +21 -0
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
|