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.
@@ -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,14 @@
1
+ from .interfaces import (
2
+ DslElement,
3
+ DslFluentRelationship,
4
+ DslRelationship,
5
+ DslWorkspaceElement,
6
+ DslViewsElement,
7
+ BindLeft,
8
+ BindRight,
9
+ BindLeftLate,
10
+ TSrc,
11
+ TDst,
12
+ TParent,
13
+ TChild,
14
+ )
@@ -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