buildzr 0.0.6__py3-none-any.whl → 0.0.8__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 -1
- buildzr/dsl/dsl.py +331 -180
- buildzr/dsl/expression.py +50 -22
- buildzr/dsl/interfaces/__init__.py +0 -4
- buildzr/dsl/interfaces/interfaces.py +2 -37
- buildzr/dsl/relations.py +30 -90
- buildzr/encoders/encoder.py +24 -4
- {buildzr-0.0.6.dist-info → buildzr-0.0.8.dist-info}/METADATA +51 -66
- {buildzr-0.0.6.dist-info → buildzr-0.0.8.dist-info}/RECORD +11 -11
- {buildzr-0.0.6.dist-info → buildzr-0.0.8.dist-info}/WHEEL +1 -1
- {buildzr-0.0.6.dist-info → buildzr-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
buildzr/dsl/dsl.py
CHANGED
@@ -3,9 +3,10 @@ import buildzr
|
|
3
3
|
from .factory import GenerateId
|
4
4
|
from typing_extensions import (
|
5
5
|
Self,
|
6
|
-
TypeGuard,
|
7
6
|
TypeIs,
|
8
7
|
)
|
8
|
+
from collections import deque
|
9
|
+
from contextvars import ContextVar
|
9
10
|
from typing import (
|
10
11
|
Any,
|
11
12
|
Union,
|
@@ -16,33 +17,21 @@ from typing import (
|
|
16
17
|
Optional,
|
17
18
|
Generic,
|
18
19
|
TypeVar,
|
19
|
-
Protocol,
|
20
20
|
Callable,
|
21
21
|
Iterable,
|
22
22
|
Literal,
|
23
23
|
cast,
|
24
|
-
overload,
|
25
|
-
Sequence,
|
26
24
|
Type,
|
27
25
|
)
|
28
26
|
|
29
27
|
from buildzr.sinks.interfaces import Sink
|
30
|
-
|
31
28
|
from buildzr.dsl.interfaces import (
|
32
29
|
DslWorkspaceElement,
|
33
30
|
DslElement,
|
34
31
|
DslViewElement,
|
35
32
|
DslViewsElement,
|
36
|
-
DslFluentSink,
|
37
|
-
TSrc, TDst,
|
38
|
-
TParent, TChild,
|
39
33
|
)
|
40
34
|
from buildzr.dsl.relations import (
|
41
|
-
_is_software_fluent_relationship,
|
42
|
-
_is_container_fluent_relationship,
|
43
|
-
_Relationship,
|
44
|
-
_RelationshipDescription,
|
45
|
-
_FluentRelationship,
|
46
35
|
DslElementRelationOverrides,
|
47
36
|
)
|
48
37
|
|
@@ -58,6 +47,11 @@ class TypedDynamicAttribute(Generic[TypedModel]):
|
|
58
47
|
def __getattr__(self, name: str) -> TypedModel:
|
59
48
|
return cast(TypedModel, self._dynamic_attributes.get(name))
|
60
49
|
|
50
|
+
_current_workspace: ContextVar[Optional['Workspace']] = ContextVar('current_workspace', default=None)
|
51
|
+
_current_group_stack: ContextVar[List['Group']] = ContextVar('current_group', default=[])
|
52
|
+
_current_software_system: ContextVar[Optional['SoftwareSystem']] = ContextVar('current_software_system', default=None)
|
53
|
+
_current_container: ContextVar[Optional['Container']] = ContextVar('current_container', default=None)
|
54
|
+
|
61
55
|
class Workspace(DslWorkspaceElement):
|
62
56
|
"""
|
63
57
|
Represents a Structurizr workspace, which is a wrapper for a software architecture model, views, and documentation.
|
@@ -75,11 +69,21 @@ class Workspace(DslWorkspaceElement):
|
|
75
69
|
def children(self) -> Optional[List[Union['Person', 'SoftwareSystem']]]:
|
76
70
|
return self._children
|
77
71
|
|
78
|
-
def __init__(
|
72
|
+
def __init__(
|
73
|
+
self,
|
74
|
+
name: str,
|
75
|
+
description: str="",
|
76
|
+
scope: Literal['landscape', 'software_system', None]='software_system',
|
77
|
+
implied_relationships: bool=False,
|
78
|
+
group_separator: str='/',
|
79
|
+
) -> None:
|
80
|
+
|
79
81
|
self._m = buildzr.models.Workspace()
|
80
82
|
self._parent = None
|
81
83
|
self._children: Optional[List[Union['Person', 'SoftwareSystem']]] = []
|
82
84
|
self._dynamic_attrs: Dict[str, Union['Person', 'SoftwareSystem']] = {}
|
85
|
+
self._use_implied_relationships = implied_relationships
|
86
|
+
self._group_separator = group_separator
|
83
87
|
self.model.id = GenerateId.for_workspace()
|
84
88
|
self.model.name = name
|
85
89
|
self.model.description = description
|
@@ -102,58 +106,57 @@ class Workspace(DslWorkspaceElement):
|
|
102
106
|
scope=scope_mapper[scope],
|
103
107
|
)
|
104
108
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
*models: Union[
|
109
|
-
'Person',
|
110
|
-
'SoftwareSystem',
|
111
|
-
_FluentRelationship['SoftwareSystem'],
|
112
|
-
_FluentRelationship['Container'],
|
113
|
-
]
|
114
|
-
) -> None:
|
109
|
+
self.model.model.properties = {
|
110
|
+
'structurizr.groupSeparator': group_separator,
|
111
|
+
}
|
115
112
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
113
|
+
def __enter__(self) -> Self:
|
114
|
+
self._token = _current_workspace.set(self)
|
115
|
+
return self
|
116
|
+
|
117
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
118
|
+
|
119
|
+
from buildzr.dsl.explorer import Explorer
|
120
|
+
|
121
|
+
# Process implied relationships:
|
122
|
+
# If we have relationship s >> do >> a.b, then create s >> do >> a.
|
123
|
+
# If we have relationship s.ss >> do >> a.b.c, then create s.ss >> do >> a.b and s.ss >> do >> a.
|
124
|
+
# And so on...
|
125
|
+
if self._use_implied_relationships:
|
126
|
+
explorer = Explorer(self)
|
127
|
+
relationships = list(explorer.walk_relationships())
|
128
|
+
for relationship in relationships:
|
129
|
+
source = relationship.source
|
130
|
+
destination = relationship.destination
|
131
|
+
destination_parent = destination.parent
|
132
|
+
|
133
|
+
while destination_parent is not None and \
|
134
|
+
isinstance(source, DslElement) and \
|
135
|
+
not isinstance(source.model, buildzr.models.Workspace) and \
|
136
|
+
not isinstance(destination_parent, DslWorkspaceElement):
|
137
|
+
|
138
|
+
if destination_parent is source.parent:
|
139
|
+
break
|
140
|
+
|
141
|
+
rels = source.model.relationships
|
142
|
+
|
143
|
+
if rels:
|
144
|
+
already_exists = any(
|
145
|
+
r.destinationId == destination_parent.model.id and
|
146
|
+
r.description == relationship.model.description and
|
147
|
+
r.technology == relationship.model.technology
|
148
|
+
for r in rels
|
149
|
+
)
|
150
|
+
if not already_exists:
|
151
|
+
r = source.uses(
|
152
|
+
destination_parent,
|
153
|
+
description=relationship.model.description,
|
154
|
+
technology=relationship.model.technology,
|
155
|
+
)
|
156
|
+
r.model.linkedRelationshipId = relationship.model.id
|
157
|
+
destination_parent = destination_parent.parent
|
158
|
+
|
159
|
+
_current_workspace.reset(self._token)
|
157
160
|
|
158
161
|
def person(self) -> TypedDynamicAttribute['Person']:
|
159
162
|
return TypedDynamicAttribute['Person'](self._dynamic_attrs)
|
@@ -161,34 +164,44 @@ class Workspace(DslWorkspaceElement):
|
|
161
164
|
def software_system(self) -> TypedDynamicAttribute['SoftwareSystem']:
|
162
165
|
return TypedDynamicAttribute['SoftwareSystem'](self._dynamic_attrs)
|
163
166
|
|
164
|
-
def
|
165
|
-
if isinstance(
|
166
|
-
self._m.model.people.append(
|
167
|
-
|
168
|
-
self.
|
169
|
-
|
170
|
-
|
171
|
-
self.
|
172
|
-
|
173
|
-
self.
|
174
|
-
|
175
|
-
self._dynamic_attrs[_child_name_transform(element.model.name)] = element
|
176
|
-
if element._label:
|
177
|
-
self._dynamic_attrs[_child_name_transform(element._label)] = element
|
178
|
-
self._children.append(element)
|
167
|
+
def add_model(self, model: Union['Person', 'SoftwareSystem']) -> None:
|
168
|
+
if isinstance(model, Person):
|
169
|
+
self._m.model.people.append(model._m)
|
170
|
+
model._parent = self
|
171
|
+
self._add_dynamic_attr(model.model.name, model)
|
172
|
+
self._children.append(model)
|
173
|
+
elif isinstance(model, SoftwareSystem):
|
174
|
+
self._m.model.softwareSystems.append(model._m)
|
175
|
+
model._parent = self
|
176
|
+
self._add_dynamic_attr(model.model.name, model)
|
177
|
+
self._children.append(model)
|
179
178
|
else:
|
180
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(
|
179
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(model)))
|
181
180
|
|
182
|
-
def
|
183
|
-
self,
|
184
|
-
*views: Union[
|
185
|
-
'SystemLandscapeView',
|
181
|
+
def apply_views( self, *views: Union[ 'SystemLandscapeView',
|
186
182
|
'SystemContextView',
|
187
183
|
'ContainerView',
|
188
184
|
'ComponentView',
|
189
185
|
]
|
190
|
-
) ->
|
191
|
-
|
186
|
+
) -> None:
|
187
|
+
Views(self).add_views(*views)
|
188
|
+
|
189
|
+
def to_json(self, path: str) -> None:
|
190
|
+
from buildzr.sinks.json_sink import JsonSink, JsonSinkConfig
|
191
|
+
sink = JsonSink()
|
192
|
+
sink.write(workspace=self.model, config=JsonSinkConfig(path=path))
|
193
|
+
|
194
|
+
def _add_dynamic_attr(self, name: str, model: Union['Person', 'SoftwareSystem']) -> None:
|
195
|
+
if isinstance(model, Person):
|
196
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
197
|
+
if model._label:
|
198
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
199
|
+
elif isinstance(model, SoftwareSystem):
|
200
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
201
|
+
if model._label:
|
202
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
203
|
+
else:
|
204
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(model)))
|
192
205
|
|
193
206
|
def __getattr__(self, name: str) -> Union['Person', 'SoftwareSystem']:
|
194
207
|
return self._dynamic_attrs[name]
|
@@ -199,7 +212,15 @@ class Workspace(DslWorkspaceElement):
|
|
199
212
|
def __dir__(self) -> Iterable[str]:
|
200
213
|
return list(super().__dir__()) + list(self._dynamic_attrs.keys())
|
201
214
|
|
202
|
-
class SoftwareSystem(DslElementRelationOverrides
|
215
|
+
class SoftwareSystem(DslElementRelationOverrides[
|
216
|
+
'SoftwareSystem',
|
217
|
+
Union[
|
218
|
+
'Person',
|
219
|
+
'SoftwareSystem',
|
220
|
+
'Container',
|
221
|
+
'Component'
|
222
|
+
]
|
223
|
+
]):
|
203
224
|
"""
|
204
225
|
A software system.
|
205
226
|
"""
|
@@ -230,6 +251,7 @@ class SoftwareSystem(DslElementRelationOverrides):
|
|
230
251
|
|
231
252
|
def __init__(self, name: str, description: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
|
232
253
|
self._m = buildzr.models.SoftwareSystem()
|
254
|
+
self.model.containers = []
|
233
255
|
self._parent: Optional[Workspace] = None
|
234
256
|
self._children: Optional[List['Container']] = []
|
235
257
|
self._sources: List[DslElement] = []
|
@@ -243,37 +265,41 @@ class SoftwareSystem(DslElementRelationOverrides):
|
|
243
265
|
self.model.tags = ','.join(self._tags)
|
244
266
|
self.model.properties = properties
|
245
267
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
if not self.model.containers:
|
251
|
-
self.model.containers = []
|
252
|
-
|
253
|
-
for child in containers:
|
254
|
-
if isinstance(child, Container):
|
255
|
-
self.add_element(child)
|
256
|
-
elif _is_container_fluent_relationship(child):
|
257
|
-
self.add_element(child._parent)
|
258
|
-
return _FluentRelationship['SoftwareSystem'](self)
|
268
|
+
workspace = _current_workspace.get()
|
269
|
+
if workspace is not None:
|
270
|
+
workspace.add_model(self)
|
271
|
+
workspace._add_dynamic_attr(self.model.name, self)
|
259
272
|
|
260
|
-
|
261
|
-
|
273
|
+
stack = _current_group_stack.get()
|
274
|
+
if stack:
|
275
|
+
stack[-1].add_element(self)
|
276
|
+
|
277
|
+
def __enter__(self) -> Self:
|
278
|
+
self._token = _current_software_system.set(self)
|
262
279
|
return self
|
263
280
|
|
281
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
282
|
+
_current_software_system.reset(self._token)
|
283
|
+
|
264
284
|
def container(self) -> TypedDynamicAttribute['Container']:
|
265
285
|
return TypedDynamicAttribute['Container'](self._dynamic_attrs)
|
266
286
|
|
267
|
-
def
|
268
|
-
if isinstance(
|
269
|
-
self.model.containers.append(
|
270
|
-
|
271
|
-
self.
|
272
|
-
|
273
|
-
self._dynamic_attrs[_child_name_transform(element._label)] = element
|
274
|
-
self._children.append(element)
|
287
|
+
def add_container(self, container: 'Container') -> None:
|
288
|
+
if isinstance(container, Container):
|
289
|
+
self.model.containers.append(container.model)
|
290
|
+
container._parent = self
|
291
|
+
self._add_dynamic_attr(container.model.name, container)
|
292
|
+
self._children.append(container)
|
275
293
|
else:
|
276
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(
|
294
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(container)))
|
295
|
+
|
296
|
+
def _add_dynamic_attr(self, name: str, model: 'Container') -> None:
|
297
|
+
if isinstance(model, Container):
|
298
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
299
|
+
if model._label:
|
300
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
301
|
+
else:
|
302
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(model)))
|
277
303
|
|
278
304
|
def __getattr__(self, name: str) -> 'Container':
|
279
305
|
return self._dynamic_attrs[name]
|
@@ -284,7 +310,22 @@ class SoftwareSystem(DslElementRelationOverrides):
|
|
284
310
|
def __dir__(self) -> Iterable[str]:
|
285
311
|
return list(super().__dir__()) + list(self._dynamic_attrs.keys())
|
286
312
|
|
287
|
-
|
313
|
+
def labeled(self, label: str) -> 'SoftwareSystem':
|
314
|
+
self._label = label
|
315
|
+
workspace = _current_workspace.get()
|
316
|
+
if workspace is not None:
|
317
|
+
workspace._add_dynamic_attr(label, self)
|
318
|
+
return self
|
319
|
+
|
320
|
+
class Person(DslElementRelationOverrides[
|
321
|
+
'Person',
|
322
|
+
Union[
|
323
|
+
'Person',
|
324
|
+
'SoftwareSystem',
|
325
|
+
'Container',
|
326
|
+
'Component'
|
327
|
+
]
|
328
|
+
]):
|
288
329
|
"""
|
289
330
|
A person who uses a software system.
|
290
331
|
"""
|
@@ -331,11 +372,30 @@ class Person(DslElementRelationOverrides):
|
|
331
372
|
self.model.tags = ','.join(self._tags)
|
332
373
|
self.model.properties = properties
|
333
374
|
|
375
|
+
workspace = _current_workspace.get()
|
376
|
+
if workspace is not None:
|
377
|
+
workspace.add_model(self)
|
378
|
+
|
379
|
+
stack = _current_group_stack.get()
|
380
|
+
if stack:
|
381
|
+
stack[-1].add_element(self)
|
382
|
+
|
334
383
|
def labeled(self, label: str) -> 'Person':
|
335
384
|
self._label = label
|
385
|
+
workspace = _current_workspace.get()
|
386
|
+
if workspace is not None:
|
387
|
+
workspace._add_dynamic_attr(label, self)
|
336
388
|
return self
|
337
389
|
|
338
|
-
class Container(DslElementRelationOverrides
|
390
|
+
class Container(DslElementRelationOverrides[
|
391
|
+
'Container',
|
392
|
+
Union[
|
393
|
+
'Person',
|
394
|
+
'SoftwareSystem',
|
395
|
+
'Container',
|
396
|
+
'Component'
|
397
|
+
]
|
398
|
+
]):
|
339
399
|
"""
|
340
400
|
A container (something that can execute code or host data).
|
341
401
|
"""
|
@@ -364,15 +424,9 @@ class Container(DslElementRelationOverrides):
|
|
364
424
|
def tags(self) -> Set[str]:
|
365
425
|
return self._tags
|
366
426
|
|
367
|
-
def contains(self, *components: 'Component') -> _FluentRelationship['Container']:
|
368
|
-
if not self.model.components:
|
369
|
-
self.model.components = []
|
370
|
-
for component in components:
|
371
|
-
self.add_element(component)
|
372
|
-
return _FluentRelationship['Container'](self)
|
373
|
-
|
374
427
|
def __init__(self, name: str, description: str="", technology: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
|
375
428
|
self._m = buildzr.models.Container()
|
429
|
+
self.model.components = []
|
376
430
|
self._parent: Optional[SoftwareSystem] = None
|
377
431
|
self._children: Optional[List['Component']] = []
|
378
432
|
self._sources: List[DslElement] = []
|
@@ -388,23 +442,48 @@ class Container(DslElementRelationOverrides):
|
|
388
442
|
self.model.tags = ','.join(self._tags)
|
389
443
|
self.model.properties = properties
|
390
444
|
|
445
|
+
software_system = _current_software_system.get()
|
446
|
+
if software_system is not None:
|
447
|
+
software_system.add_container(self)
|
448
|
+
software_system._add_dynamic_attr(self.model.name, self)
|
449
|
+
|
450
|
+
stack = _current_group_stack.get()
|
451
|
+
if stack:
|
452
|
+
stack[-1].add_element(self)
|
453
|
+
|
454
|
+
def __enter__(self) -> Self:
|
455
|
+
self._token = _current_container.set(self)
|
456
|
+
return self
|
457
|
+
|
458
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
459
|
+
_current_container.reset(self._token)
|
460
|
+
|
391
461
|
def labeled(self, label: str) -> 'Container':
|
392
462
|
self._label = label
|
463
|
+
software_system = _current_software_system.get()
|
464
|
+
if software_system is not None:
|
465
|
+
software_system._add_dynamic_attr(label, self)
|
393
466
|
return self
|
394
467
|
|
395
468
|
def component(self) -> TypedDynamicAttribute['Component']:
|
396
469
|
return TypedDynamicAttribute['Component'](self._dynamic_attrs)
|
397
470
|
|
398
|
-
def
|
399
|
-
if isinstance(
|
400
|
-
self.model.components.append(
|
401
|
-
|
402
|
-
self.
|
403
|
-
|
404
|
-
|
405
|
-
|
471
|
+
def add_component(self, component: 'Component') -> None:
|
472
|
+
if isinstance(component, Component):
|
473
|
+
self.model.components.append(component.model)
|
474
|
+
component._parent = self
|
475
|
+
self._add_dynamic_attr(component.model.name, component)
|
476
|
+
self._children.append(component)
|
477
|
+
else:
|
478
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(component)))
|
479
|
+
|
480
|
+
def _add_dynamic_attr(self, name: str, model: 'Component') -> None:
|
481
|
+
if isinstance(model, Component):
|
482
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
483
|
+
if model._label:
|
484
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
406
485
|
else:
|
407
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(
|
486
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(model)))
|
408
487
|
|
409
488
|
def __getattr__(self, name: str) -> 'Component':
|
410
489
|
return self._dynamic_attrs[name]
|
@@ -415,7 +494,15 @@ class Container(DslElementRelationOverrides):
|
|
415
494
|
def __dir__(self) -> Iterable[str]:
|
416
495
|
return list(super().__dir__()) + list(self._dynamic_attrs.keys())
|
417
496
|
|
418
|
-
class Component(DslElementRelationOverrides
|
497
|
+
class Component(DslElementRelationOverrides[
|
498
|
+
'Component',
|
499
|
+
Union[
|
500
|
+
'Person',
|
501
|
+
'SoftwareSystem',
|
502
|
+
'Container',
|
503
|
+
'Component'
|
504
|
+
]
|
505
|
+
]):
|
419
506
|
"""
|
420
507
|
A component (a grouping of related functionality behind an interface that runs inside a container).
|
421
508
|
"""
|
@@ -459,8 +546,20 @@ class Component(DslElementRelationOverrides):
|
|
459
546
|
self.model.tags = ','.join(self._tags)
|
460
547
|
self.model.properties = properties
|
461
548
|
|
549
|
+
container = _current_container.get()
|
550
|
+
if container is not None:
|
551
|
+
container.add_component(self)
|
552
|
+
container._add_dynamic_attr(self.model.name, self)
|
553
|
+
|
554
|
+
stack = _current_group_stack.get()
|
555
|
+
if stack:
|
556
|
+
stack[-1].add_element(self)
|
557
|
+
|
462
558
|
def labeled(self, label: str) -> 'Component':
|
463
559
|
self._label = label
|
560
|
+
container = _current_container.get()
|
561
|
+
if container is not None:
|
562
|
+
container._add_dynamic_attr(label, self)
|
464
563
|
return self
|
465
564
|
|
466
565
|
class Group:
|
@@ -468,24 +567,53 @@ class Group:
|
|
468
567
|
def __init__(
|
469
568
|
self,
|
470
569
|
name: str,
|
471
|
-
|
472
|
-
Person,
|
473
|
-
SoftwareSystem,
|
474
|
-
_FluentRelationship[SoftwareSystem],
|
475
|
-
_FluentRelationship[Container],
|
476
|
-
]) -> None:
|
570
|
+
) -> None:
|
477
571
|
self._name = name
|
478
|
-
self._elements = elements
|
479
572
|
|
480
|
-
|
573
|
+
def add_element(
|
574
|
+
self,
|
575
|
+
model: Union[
|
576
|
+
'Person',
|
577
|
+
'SoftwareSystem',
|
578
|
+
'Container',
|
579
|
+
'Component',
|
580
|
+
],
|
581
|
+
group_separator: str="/",
|
582
|
+
) -> None:
|
481
583
|
|
482
|
-
|
483
|
-
self._workspace = workspace
|
584
|
+
separator = group_separator
|
484
585
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
586
|
+
workspace = _current_workspace.get()
|
587
|
+
if workspace is not None:
|
588
|
+
separator = workspace._group_separator
|
589
|
+
|
590
|
+
if len(separator) > 1:
|
591
|
+
raise ValueError('Group separator must be a single character.')
|
592
|
+
|
593
|
+
if separator in self._name:
|
594
|
+
raise ValueError('Group name cannot contain the group separator.')
|
595
|
+
|
596
|
+
stack = _current_group_stack.get()
|
597
|
+
|
598
|
+
index = next((i for i, group in enumerate(stack) if group._name == self._name), -1)
|
599
|
+
if index >= 0:
|
600
|
+
model.model.group = separator.join([group._name for group in stack[:index + 1]])
|
601
|
+
|
602
|
+
def __enter__(self) -> Self:
|
603
|
+
stack = _current_group_stack.get() # stack: a/b
|
604
|
+
stack.extend([self]) # stack: a/b -> a/b/self
|
605
|
+
self._token = _current_group_stack.set(stack)
|
606
|
+
return self
|
607
|
+
|
608
|
+
def __exit__(
|
609
|
+
self,
|
610
|
+
exc_type: Optional[Type[BaseException]],
|
611
|
+
exc_value: Optional[BaseException],
|
612
|
+
traceback: Optional[Any]
|
613
|
+
) -> None:
|
614
|
+
stack = _current_group_stack.get()
|
615
|
+
stack.pop() # stack: a/b/self -> a/b
|
616
|
+
_current_group_stack.reset(self._token)
|
489
617
|
|
490
618
|
_RankDirection = Literal['tb', 'bt', 'lr', 'rl']
|
491
619
|
|
@@ -572,10 +700,10 @@ class SystemLandscapeView(DslViewElement):
|
|
572
700
|
description: str,
|
573
701
|
auto_layout: _AutoLayout='tb',
|
574
702
|
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]]=[],
|
703
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
704
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
705
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
706
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
579
707
|
properties: Optional[Dict[str, str]]=None,
|
580
708
|
) -> None:
|
581
709
|
self._m = buildzr.models.SystemLandscapeView()
|
@@ -593,6 +721,10 @@ class SystemLandscapeView(DslViewElement):
|
|
593
721
|
self._include_relationships = include_relationships
|
594
722
|
self._exclude_relationships = exclude_relationships
|
595
723
|
|
724
|
+
workspace = _current_workspace.get()
|
725
|
+
if workspace is not None:
|
726
|
+
workspace.apply_views(self)
|
727
|
+
|
596
728
|
def _on_added(self) -> None:
|
597
729
|
|
598
730
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
@@ -607,17 +739,17 @@ class SystemLandscapeView(DslViewElement):
|
|
607
739
|
|
608
740
|
workspace = self._parent._parent
|
609
741
|
|
610
|
-
include_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
742
|
+
include_view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
611
743
|
lambda w, e: e.type == Person,
|
612
744
|
lambda w, e: e.type == SoftwareSystem
|
613
745
|
]
|
614
746
|
|
615
|
-
exclude_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
747
|
+
exclude_view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
616
748
|
lambda w, e: e.type == Container,
|
617
749
|
lambda w, e: e.type == Component,
|
618
750
|
]
|
619
751
|
|
620
|
-
include_view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
752
|
+
include_view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
621
753
|
lambda w, r: r.source.type == Person,
|
622
754
|
lambda w, r: r.source.type == SoftwareSystem,
|
623
755
|
lambda w, r: r.destination.type == Person,
|
@@ -668,15 +800,15 @@ class SystemContextView(DslViewElement):
|
|
668
800
|
|
669
801
|
def __init__(
|
670
802
|
self,
|
671
|
-
software_system_selector: Callable[[Workspace], SoftwareSystem],
|
803
|
+
software_system_selector: Union[SoftwareSystem, Callable[[Workspace], SoftwareSystem]],
|
672
804
|
key: str,
|
673
805
|
description: str,
|
674
806
|
auto_layout: _AutoLayout='tb',
|
675
807
|
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]]=[],
|
808
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
809
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
810
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
811
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
680
812
|
properties: Optional[Dict[str, str]]=None,
|
681
813
|
) -> None:
|
682
814
|
self._m = buildzr.models.SystemContextView()
|
@@ -695,20 +827,27 @@ class SystemContextView(DslViewElement):
|
|
695
827
|
self._include_relationships = include_relationships
|
696
828
|
self._exclude_relationships = exclude_relationships
|
697
829
|
|
830
|
+
workspace = _current_workspace.get()
|
831
|
+
if workspace is not None:
|
832
|
+
workspace.apply_views(self)
|
833
|
+
|
698
834
|
def _on_added(self) -> None:
|
699
835
|
|
700
836
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
701
837
|
from buildzr.models import ElementView, RelationshipView
|
702
838
|
|
703
|
-
|
839
|
+
if isinstance(self._selector, SoftwareSystem):
|
840
|
+
software_system = self._selector
|
841
|
+
else:
|
842
|
+
software_system = self._selector(self._parent._parent)
|
704
843
|
self._m.softwareSystemId = software_system.model.id
|
705
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
844
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
706
845
|
lambda w, e: e == software_system,
|
707
846
|
lambda w, e: software_system.model.id in e.sources.ids,
|
708
847
|
lambda w, e: software_system.model.id in e.destinations.ids,
|
709
848
|
]
|
710
849
|
|
711
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
850
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
712
851
|
lambda w, r: software_system == r.source,
|
713
852
|
lambda w, r: software_system == r.destination,
|
714
853
|
]
|
@@ -754,15 +893,15 @@ class ContainerView(DslViewElement):
|
|
754
893
|
|
755
894
|
def __init__(
|
756
895
|
self,
|
757
|
-
software_system_selector: Callable[[Workspace], SoftwareSystem],
|
896
|
+
software_system_selector: Union[SoftwareSystem, Callable[[Workspace], SoftwareSystem]],
|
758
897
|
key: str,
|
759
898
|
description: str,
|
760
899
|
auto_layout: _AutoLayout='tb',
|
761
900
|
title: Optional[str]=None,
|
762
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
763
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
764
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
765
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
901
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
902
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
903
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
904
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
766
905
|
properties: Optional[Dict[str, str]]=None,
|
767
906
|
) -> None:
|
768
907
|
self._m = buildzr.models.ContainerView()
|
@@ -781,23 +920,30 @@ class ContainerView(DslViewElement):
|
|
781
920
|
self._include_relationships = include_relationships
|
782
921
|
self._exclude_relationships = exclude_relationships
|
783
922
|
|
923
|
+
workspace = _current_workspace.get()
|
924
|
+
if workspace is not None:
|
925
|
+
workspace.apply_views(self)
|
926
|
+
|
784
927
|
def _on_added(self) -> None:
|
785
928
|
|
786
929
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
787
930
|
from buildzr.models import ElementView, RelationshipView
|
788
931
|
|
789
|
-
|
932
|
+
if isinstance(self._selector, SoftwareSystem):
|
933
|
+
software_system = self._selector
|
934
|
+
else:
|
935
|
+
software_system = self._selector(self._parent._parent)
|
790
936
|
self._m.softwareSystemId = software_system.model.id
|
791
937
|
|
792
938
|
container_ids = { container.model.id for container in software_system.children}
|
793
939
|
|
794
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
940
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
795
941
|
lambda w, e: e.parent == software_system,
|
796
942
|
lambda w, e: any(container_ids.intersection({ id for id in e.sources.ids })),
|
797
943
|
lambda w, e: any(container_ids.intersection({ id for id in e.destinations.ids })),
|
798
944
|
]
|
799
945
|
|
800
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
946
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
801
947
|
lambda w, r: software_system == r.source.parent,
|
802
948
|
lambda w, r: software_system == r.destination.parent,
|
803
949
|
]
|
@@ -843,15 +989,15 @@ class ComponentView(DslViewElement):
|
|
843
989
|
|
844
990
|
def __init__(
|
845
991
|
self,
|
846
|
-
container_selector: Callable[[Workspace], Container],
|
992
|
+
container_selector: Union[Container, Callable[[Workspace], Container]],
|
847
993
|
key: str,
|
848
994
|
description: str,
|
849
995
|
auto_layout: _AutoLayout='tb',
|
850
996
|
title: Optional[str]=None,
|
851
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
852
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
853
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
854
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
997
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
998
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
999
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
1000
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
855
1001
|
properties: Optional[Dict[str, str]]=None,
|
856
1002
|
) -> None:
|
857
1003
|
self._m = buildzr.models.ComponentView()
|
@@ -870,23 +1016,30 @@ class ComponentView(DslViewElement):
|
|
870
1016
|
self._include_relationships = include_relationships
|
871
1017
|
self._exclude_relationships = exclude_relationships
|
872
1018
|
|
1019
|
+
workspace = _current_workspace.get()
|
1020
|
+
if workspace is not None:
|
1021
|
+
workspace.apply_views(self)
|
1022
|
+
|
873
1023
|
def _on_added(self) -> None:
|
874
1024
|
|
875
1025
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
876
1026
|
from buildzr.models import ElementView, RelationshipView
|
877
1027
|
|
878
|
-
|
1028
|
+
if isinstance(self._selector, Container):
|
1029
|
+
container = self._selector
|
1030
|
+
else:
|
1031
|
+
container = self._selector(self._parent._parent)
|
879
1032
|
self._m.containerId = container.model.id
|
880
1033
|
|
881
1034
|
component_ids = { component.model.id for component in container.children }
|
882
1035
|
|
883
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
1036
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
884
1037
|
lambda w, e: e.parent == container,
|
885
1038
|
lambda w, e: any(component_ids.intersection({ id for id in e.sources.ids })),
|
886
1039
|
lambda w, e: any(component_ids.intersection({ id for id in e.destinations.ids })),
|
887
1040
|
]
|
888
1041
|
|
889
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
1042
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
890
1043
|
lambda w, r: container == r.source.parent,
|
891
1044
|
lambda w, r: container == r.destination.parent,
|
892
1045
|
]
|
@@ -936,10 +1089,10 @@ class Views(DslViewsElement):
|
|
936
1089
|
self._parent = workspace
|
937
1090
|
self._parent._m.views = self._m
|
938
1091
|
|
939
|
-
def
|
1092
|
+
def add_views(
|
940
1093
|
self,
|
941
1094
|
*views: DslViewElement
|
942
|
-
) ->
|
1095
|
+
) -> None:
|
943
1096
|
|
944
1097
|
for view in views:
|
945
1098
|
if isinstance(view, SystemLandscapeView):
|
@@ -973,8 +1126,6 @@ class Views(DslViewsElement):
|
|
973
1126
|
else:
|
974
1127
|
raise NotImplementedError("The view {0} is currently not supported", type(view))
|
975
1128
|
|
976
|
-
return _FluentSink(self._parent)
|
977
|
-
|
978
1129
|
def get_workspace(self) -> Workspace:
|
979
1130
|
"""
|
980
1131
|
Get the `Workspace` which contain this views definition.
|