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/explorer.py
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
from buildzr.dsl.dsl import (
|
2
|
+
Person,
|
3
|
+
SoftwareSystem,
|
4
|
+
Container,
|
5
|
+
Component,
|
6
|
+
)
|
7
|
+
|
8
|
+
from buildzr.dsl.relations import (
|
9
|
+
_Relationship,
|
10
|
+
_UsesData,
|
11
|
+
)
|
12
|
+
|
13
|
+
from typing import (
|
14
|
+
Union,
|
15
|
+
Generator,
|
16
|
+
Iterable,
|
17
|
+
)
|
18
|
+
|
19
|
+
from buildzr.dsl.dsl import (
|
20
|
+
Workspace,
|
21
|
+
)
|
22
|
+
|
23
|
+
class Explorer:
|
24
|
+
|
25
|
+
def __init__(self, workspace_or_element: Union[Workspace, Person, SoftwareSystem, Container, Component]):
|
26
|
+
self._workspace_or_element = workspace_or_element
|
27
|
+
|
28
|
+
def walk_elements(self) -> Generator[Union[Person, SoftwareSystem, Container, Component], None, None]:
|
29
|
+
if self._workspace_or_element.children:
|
30
|
+
for child in self._workspace_or_element.children:
|
31
|
+
explorer = Explorer(child).walk_elements()
|
32
|
+
yield child
|
33
|
+
yield from explorer
|
34
|
+
|
35
|
+
def walk_relationships(self) -> Generator[_Relationship, None, None]:
|
36
|
+
import buildzr
|
37
|
+
from buildzr.dsl.factory.gen_id import GenerateId
|
38
|
+
|
39
|
+
if self._workspace_or_element.children:
|
40
|
+
for child in self._workspace_or_element.children:
|
41
|
+
# Relationships aren't materialized in the `Workspace` or in any
|
42
|
+
# of the `DslElement`s. As such, we need to recreate the `_Relationship` objects
|
43
|
+
# from the Structurizr model.
|
44
|
+
|
45
|
+
if child.model.relationships and child.destinations:
|
46
|
+
for relationship, destination in zip(child.model.relationships, child.destinations):
|
47
|
+
fake_relationship = _Relationship(
|
48
|
+
_UsesData(
|
49
|
+
relationship=buildzr.models.Relationship(
|
50
|
+
id=relationship.id,
|
51
|
+
description=relationship.description,
|
52
|
+
properties=relationship.properties,
|
53
|
+
technology=relationship.technology,
|
54
|
+
tags=relationship.tags,
|
55
|
+
sourceId=relationship.sourceId,
|
56
|
+
),
|
57
|
+
source=child,
|
58
|
+
),
|
59
|
+
destination=destination,
|
60
|
+
_include_in_model=False,
|
61
|
+
)
|
62
|
+
fake_relationship._tags = set(relationship.tags.split(','))
|
63
|
+
|
64
|
+
yield fake_relationship
|
65
|
+
|
66
|
+
explorer = Explorer(child).walk_relationships()
|
67
|
+
yield from explorer
|
@@ -0,0 +1,208 @@
|
|
1
|
+
from buildzr.dsl.interfaces import (
|
2
|
+
DslWorkspaceElement,
|
3
|
+
DslElement,
|
4
|
+
DslRelationship,
|
5
|
+
)
|
6
|
+
|
7
|
+
from buildzr.dsl.dsl import (
|
8
|
+
Workspace,
|
9
|
+
Person,
|
10
|
+
SoftwareSystem,
|
11
|
+
Container,
|
12
|
+
Component,
|
13
|
+
_Relationship,
|
14
|
+
)
|
15
|
+
|
16
|
+
import buildzr
|
17
|
+
from typing import Set, Union, Optional, List, Dict, Any, Callable, Tuple, Sequence, Iterable, cast, Type
|
18
|
+
from typing_extensions import TypeIs
|
19
|
+
|
20
|
+
def _has_technology_attribute(obj: DslElement) -> TypeIs[Union[Container, Component]]:
|
21
|
+
if isinstance(obj, (Person, SoftwareSystem, Workspace)):
|
22
|
+
return False
|
23
|
+
return True
|
24
|
+
|
25
|
+
class FlattenElement:
|
26
|
+
|
27
|
+
def __init__(self, elements: Iterable[DslElement]):
|
28
|
+
self._elements = elements
|
29
|
+
|
30
|
+
@property
|
31
|
+
def ids(self) -> Set[Union[str]]:
|
32
|
+
# Note that the `element.model` can also be a `Workspace`, whose `id` is
|
33
|
+
# of type `int`. But since we know that these are all `DslElements` (`id` of type `str`),
|
34
|
+
# we can safely cast all the `id`s as `str` for the type checker to be happy.
|
35
|
+
return set([str(element.model.id) for element in self._elements])
|
36
|
+
|
37
|
+
@property
|
38
|
+
def names(self) -> Set[Union[str]]:
|
39
|
+
return set([str(element.model.name) for element in self._elements])
|
40
|
+
|
41
|
+
@property
|
42
|
+
def tags(self) -> Set[Union[str]]:
|
43
|
+
all_tags: Set[str] = set()
|
44
|
+
for element in self._elements:
|
45
|
+
tags = element.tags
|
46
|
+
all_tags = all_tags.union(tags)
|
47
|
+
return all_tags
|
48
|
+
|
49
|
+
class Element:
|
50
|
+
|
51
|
+
def __init__(self, element: DslElement):
|
52
|
+
self._element = element
|
53
|
+
|
54
|
+
# TODO: Make a test for this in `tests/test_expression.py`
|
55
|
+
@property
|
56
|
+
def id(self) -> str:
|
57
|
+
return cast(str, self._element.model.id)
|
58
|
+
|
59
|
+
@property
|
60
|
+
def type(self) -> Type:
|
61
|
+
return type(self._element)
|
62
|
+
|
63
|
+
@property
|
64
|
+
def tags(self) -> Set[str]:
|
65
|
+
return self._element.tags
|
66
|
+
|
67
|
+
@property
|
68
|
+
def technology(self) -> Optional[str]:
|
69
|
+
if _has_technology_attribute(self._element):
|
70
|
+
return self._element.model.technology
|
71
|
+
return None
|
72
|
+
|
73
|
+
# TODO: Make a test for this in `tests/test_expression.py`
|
74
|
+
@property
|
75
|
+
def parent(self) -> Optional[Union[DslWorkspaceElement, DslElement]]:
|
76
|
+
return self._element.parent
|
77
|
+
|
78
|
+
@property
|
79
|
+
def sources(self) -> FlattenElement:
|
80
|
+
return FlattenElement(self._element.sources)
|
81
|
+
|
82
|
+
@property
|
83
|
+
def destinations(self) -> FlattenElement:
|
84
|
+
return FlattenElement(self._element.destinations)
|
85
|
+
|
86
|
+
@property
|
87
|
+
def properties(self) -> Dict[str, Any]:
|
88
|
+
return self._element.model.properties
|
89
|
+
|
90
|
+
def __eq__(self, element: object) -> bool:
|
91
|
+
return isinstance(element, type(self._element)) and\
|
92
|
+
element.model.id == self._element.model.id
|
93
|
+
|
94
|
+
class Relationship:
|
95
|
+
|
96
|
+
def __init__(self, relationship: _Relationship):
|
97
|
+
self._relationship = relationship
|
98
|
+
|
99
|
+
# TODO: Make a test for this in `tests/test_expression.py`
|
100
|
+
@property
|
101
|
+
def id(self) -> str:
|
102
|
+
return cast(str, self._relationship.model.id)
|
103
|
+
|
104
|
+
@property
|
105
|
+
def tags(self) -> Set[str]:
|
106
|
+
return self._relationship.tags
|
107
|
+
|
108
|
+
@property
|
109
|
+
def technology(self) -> Optional[str]:
|
110
|
+
return self._relationship.model.technology
|
111
|
+
|
112
|
+
@property
|
113
|
+
def source(self) -> Element:
|
114
|
+
return Element(self._relationship.source)
|
115
|
+
|
116
|
+
@property
|
117
|
+
def destination(self) -> Element:
|
118
|
+
return Element(self._relationship.destination)
|
119
|
+
|
120
|
+
@property
|
121
|
+
def properties(self) -> Dict[str, Any]:
|
122
|
+
if self._relationship.model.properties is not None:
|
123
|
+
return self._relationship.model.properties
|
124
|
+
return dict()
|
125
|
+
|
126
|
+
class Expression:
|
127
|
+
|
128
|
+
"""
|
129
|
+
A class used to filter the elements and the relationships in the workspace.
|
130
|
+
To be used when defining views.
|
131
|
+
|
132
|
+
In the Structurizr DSL, these are called "Expressions". See the Structurizr docs here:
|
133
|
+
https://docs.structurizr.com/dsl/expressions
|
134
|
+
"""
|
135
|
+
|
136
|
+
def __init__(
|
137
|
+
self,
|
138
|
+
include_elements: Iterable[Callable[[Workspace, Element], bool]]=[lambda w, e: True],
|
139
|
+
exclude_elements: Iterable[Callable[[Workspace, Element], bool]]=[],
|
140
|
+
include_relationships: Iterable[Callable[[Workspace, Relationship], bool]]=[lambda w, e: True],
|
141
|
+
exclude_relationships: Iterable[Callable[[Workspace, Relationship], bool]]=[],
|
142
|
+
) -> 'None':
|
143
|
+
self._include_elements = include_elements
|
144
|
+
self._exclude_elements = exclude_elements
|
145
|
+
self._include_relationships = include_relationships
|
146
|
+
self._exclude_relationships = exclude_relationships
|
147
|
+
|
148
|
+
def elements(
|
149
|
+
self,
|
150
|
+
workspace: Workspace,
|
151
|
+
) -> List[DslElement]:
|
152
|
+
|
153
|
+
filtered_elements: List[DslElement] = []
|
154
|
+
|
155
|
+
workspace_elements = buildzr.dsl.Explorer(workspace).walk_elements()
|
156
|
+
for element in workspace_elements:
|
157
|
+
any_includes = any([f(workspace, Element(element)) for f in self._include_elements])
|
158
|
+
any_excludes = any([f(workspace, Element(element)) for f in self._exclude_elements])
|
159
|
+
if any_includes and not any_excludes:
|
160
|
+
filtered_elements.append(element)
|
161
|
+
|
162
|
+
return filtered_elements
|
163
|
+
|
164
|
+
def relationships(
|
165
|
+
self,
|
166
|
+
workspace: Workspace
|
167
|
+
) -> List[DslRelationship]:
|
168
|
+
|
169
|
+
"""
|
170
|
+
Returns the relationships that are included as defined in
|
171
|
+
`include_relationships` and excludes those that are defined in
|
172
|
+
`exclude_relationships`. Any relationships that directly works on
|
173
|
+
elements that are excluded as defined in `exclude_elements` will also be
|
174
|
+
excluded.
|
175
|
+
"""
|
176
|
+
|
177
|
+
filtered_relationships: List[DslRelationship] = []
|
178
|
+
|
179
|
+
def _is_relationship_of_excluded_elements(
|
180
|
+
workspace: Workspace,
|
181
|
+
relationship: Relationship,
|
182
|
+
exclude_element_predicates: Iterable[Callable[[Workspace, Element], bool]],
|
183
|
+
) -> bool:
|
184
|
+
return any([
|
185
|
+
f(workspace, relationship.source) for f in exclude_element_predicates
|
186
|
+
] + [
|
187
|
+
f(workspace, relationship.destination) for f in exclude_element_predicates
|
188
|
+
])
|
189
|
+
|
190
|
+
workspace_relationships = buildzr.dsl.Explorer(workspace).walk_relationships()
|
191
|
+
for relationship in workspace_relationships:
|
192
|
+
any_includes = any([f(workspace, Relationship(relationship)) for f in self._include_relationships])
|
193
|
+
|
194
|
+
# Also exclude relationships whose source or destination elements are excluded.
|
195
|
+
any_excludes = any([
|
196
|
+
f(workspace, Relationship(relationship))
|
197
|
+
for f in self._exclude_relationships
|
198
|
+
] + [
|
199
|
+
_is_relationship_of_excluded_elements(
|
200
|
+
workspace,
|
201
|
+
Relationship(relationship),
|
202
|
+
self._exclude_elements,
|
203
|
+
)
|
204
|
+
])
|
205
|
+
if any_includes and not any_excludes:
|
206
|
+
filtered_relationships.append(relationship)
|
207
|
+
|
208
|
+
return filtered_relationships
|
@@ -0,0 +1 @@
|
|
1
|
+
from .gen_id import GenerateId
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
class GenerateId:
|
4
|
+
|
5
|
+
_data: Dict[int, int] = {
|
6
|
+
0: 0,
|
7
|
+
1: 0,
|
8
|
+
}
|
9
|
+
|
10
|
+
@staticmethod
|
11
|
+
def for_workspace() -> int:
|
12
|
+
GenerateId._data[0] = GenerateId._data[0] + 1
|
13
|
+
return GenerateId._data[0]
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def for_element() -> str:
|
17
|
+
GenerateId._data[1] = GenerateId._data[1] + 1
|
18
|
+
return str(GenerateId._data[1])
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
def for_relationship() -> str:
|
22
|
+
GenerateId._data[1] = GenerateId._data[1] + 1
|
23
|
+
return str(GenerateId._data[1])
|
@@ -0,0 +1,207 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Optional,
|
5
|
+
Union,
|
6
|
+
TypeVar,
|
7
|
+
Generic,
|
8
|
+
List,
|
9
|
+
Set,
|
10
|
+
Tuple,
|
11
|
+
Callable,
|
12
|
+
overload,
|
13
|
+
Sequence,
|
14
|
+
cast,
|
15
|
+
)
|
16
|
+
from typing_extensions import (
|
17
|
+
Self
|
18
|
+
)
|
19
|
+
import buildzr
|
20
|
+
|
21
|
+
Model = Union[
|
22
|
+
buildzr.models.Workspace,
|
23
|
+
buildzr.models.Person,
|
24
|
+
buildzr.models.SoftwareSystem,
|
25
|
+
buildzr.models.Container,
|
26
|
+
buildzr.models.Component,
|
27
|
+
]
|
28
|
+
|
29
|
+
TSrc = TypeVar('TSrc', bound='DslElement', contravariant=True)
|
30
|
+
TDst = TypeVar('TDst', bound='DslElement', contravariant=True)
|
31
|
+
|
32
|
+
TParent = TypeVar('TParent', bound=Union['DslWorkspaceElement', 'DslElement'], covariant=True)
|
33
|
+
TChild = TypeVar('TChild', bound='DslElement', contravariant=True)
|
34
|
+
|
35
|
+
class BindLeftLate(ABC, Generic[TDst]):
|
36
|
+
|
37
|
+
@abstractmethod
|
38
|
+
def set_source(self, source: Any) -> None:
|
39
|
+
pass
|
40
|
+
|
41
|
+
@abstractmethod
|
42
|
+
def get_relationship(self) -> 'Optional[DslRelationship[Any, TDst]]':
|
43
|
+
pass
|
44
|
+
|
45
|
+
class BindLeft(ABC, Generic[TSrc, TDst]):
|
46
|
+
|
47
|
+
# Note: an abstraction of _UsesFrom
|
48
|
+
|
49
|
+
@abstractmethod
|
50
|
+
def __rshift__(self, destination: TDst) -> 'DslRelationship[TSrc, TDst]':
|
51
|
+
pass
|
52
|
+
|
53
|
+
class BindRight(ABC, Generic[TSrc, TDst]):
|
54
|
+
|
55
|
+
@overload
|
56
|
+
@abstractmethod
|
57
|
+
def __rshift__(self, description_and_technology: Tuple[str, str]) -> BindLeft[TSrc, TDst]:
|
58
|
+
...
|
59
|
+
|
60
|
+
@overload
|
61
|
+
@abstractmethod
|
62
|
+
def __rshift__(self, description: str) -> BindLeft[TSrc, TDst]:
|
63
|
+
...
|
64
|
+
|
65
|
+
@overload
|
66
|
+
@abstractmethod
|
67
|
+
def __rshift__(self, multiple_destinations: List[BindLeftLate[TDst]]) -> 'List[DslRelationship[TSrc, TDst]]':
|
68
|
+
...
|
69
|
+
|
70
|
+
@abstractmethod
|
71
|
+
def __rshift__(self, other: Union[str, Tuple[str, str], List[BindLeftLate[TDst]]]) -> Union[BindLeft[TSrc, TDst], 'List[DslRelationship[TSrc, TDst]]']:
|
72
|
+
...
|
73
|
+
|
74
|
+
class DslWorkspaceElement(ABC):
|
75
|
+
|
76
|
+
@property
|
77
|
+
@abstractmethod
|
78
|
+
def model(self) -> buildzr.models.Workspace:
|
79
|
+
pass
|
80
|
+
|
81
|
+
@property
|
82
|
+
@abstractmethod
|
83
|
+
def parent(self) -> None:
|
84
|
+
pass
|
85
|
+
|
86
|
+
@property
|
87
|
+
@abstractmethod
|
88
|
+
def children(self) -> Optional[Sequence['DslElement']]:
|
89
|
+
pass
|
90
|
+
|
91
|
+
def __contains__(self, other: 'DslElement') -> bool:
|
92
|
+
return self.model.id == other.parent.model.id
|
93
|
+
|
94
|
+
class DslElement(BindRight[TSrc, TDst]):
|
95
|
+
"""An abstract class used to label classes that are part of the buildzr DSL"""
|
96
|
+
|
97
|
+
@property
|
98
|
+
@abstractmethod
|
99
|
+
def model(self) -> Model:
|
100
|
+
"""
|
101
|
+
Returns the `dataclass` of the `DslElement` that follows Structurizr's
|
102
|
+
JSON Schema (see https://github.com/structurizr/json)
|
103
|
+
"""
|
104
|
+
pass
|
105
|
+
|
106
|
+
@property
|
107
|
+
@abstractmethod
|
108
|
+
def parent(self) -> Union[None, DslWorkspaceElement, 'DslElement']:
|
109
|
+
pass
|
110
|
+
|
111
|
+
@property
|
112
|
+
@abstractmethod
|
113
|
+
def children(self) -> Union[None, Sequence['DslElement']]:
|
114
|
+
pass
|
115
|
+
|
116
|
+
@property
|
117
|
+
@abstractmethod
|
118
|
+
def sources(self) -> List['DslElement']:
|
119
|
+
pass
|
120
|
+
|
121
|
+
@property
|
122
|
+
@abstractmethod
|
123
|
+
def destinations(self) -> List['DslElement']:
|
124
|
+
pass
|
125
|
+
|
126
|
+
@property
|
127
|
+
@abstractmethod
|
128
|
+
def tags(self) -> Set[str]:
|
129
|
+
pass
|
130
|
+
|
131
|
+
def uses(
|
132
|
+
self,
|
133
|
+
other: 'DslElement',
|
134
|
+
description: Optional[str]=None,
|
135
|
+
technology: Optional[str]=None,
|
136
|
+
tags: Set[str]=set()) -> 'DslRelationship[Self, DslElement]':
|
137
|
+
pass
|
138
|
+
|
139
|
+
def __contains__(self, other: 'DslElement') -> bool:
|
140
|
+
return self.model.id == other.parent.model.id
|
141
|
+
|
142
|
+
class DslRelationship(ABC, Generic[TSrc, TDst]):
|
143
|
+
"""
|
144
|
+
An abstract class specially used to label classes that are part of the
|
145
|
+
relationship definer in the buildzr DSL
|
146
|
+
"""
|
147
|
+
|
148
|
+
@property
|
149
|
+
@abstractmethod
|
150
|
+
def model(self) -> buildzr.models.Relationship:
|
151
|
+
pass
|
152
|
+
|
153
|
+
@property
|
154
|
+
@abstractmethod
|
155
|
+
def tags(self) -> Set[str]:
|
156
|
+
pass
|
157
|
+
|
158
|
+
@property
|
159
|
+
@abstractmethod
|
160
|
+
def source(self) -> DslElement:
|
161
|
+
pass
|
162
|
+
|
163
|
+
@property
|
164
|
+
@abstractmethod
|
165
|
+
def destination(self) -> DslElement:
|
166
|
+
pass
|
167
|
+
|
168
|
+
def __contains__(self, other: 'DslElement') -> bool:
|
169
|
+
return self.source.model.id == other.model.id or self.destination.model.id == other.model.id
|
170
|
+
|
171
|
+
class DslFluentRelationship(ABC, Generic[TParent]):
|
172
|
+
|
173
|
+
"""
|
174
|
+
The abstract class that defines the interface for the fluent relationship
|
175
|
+
definition, where the one or more relationships between elements accessible
|
176
|
+
from the `TParent` element.
|
177
|
+
"""
|
178
|
+
|
179
|
+
@abstractmethod
|
180
|
+
def where(
|
181
|
+
self,
|
182
|
+
func: Callable[
|
183
|
+
[TParent],
|
184
|
+
Sequence[
|
185
|
+
Union[
|
186
|
+
DslRelationship,
|
187
|
+
Sequence[DslRelationship]
|
188
|
+
]
|
189
|
+
]
|
190
|
+
]) -> TParent:
|
191
|
+
pass
|
192
|
+
|
193
|
+
@abstractmethod
|
194
|
+
def get(self) -> TParent:
|
195
|
+
pass
|
196
|
+
|
197
|
+
class DslViewsElement(ABC):
|
198
|
+
|
199
|
+
@property
|
200
|
+
@abstractmethod
|
201
|
+
def model(self) -> buildzr.models.Views:
|
202
|
+
pass
|
203
|
+
|
204
|
+
@property
|
205
|
+
@abstractmethod
|
206
|
+
def parent(self) -> DslWorkspaceElement:
|
207
|
+
pass
|