buildzr 0.0.7__py3-none-any.whl → 0.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
buildzr/dsl/explorer.py CHANGED
@@ -33,35 +33,14 @@ class Explorer:
33
33
  yield from explorer
34
34
 
35
35
  def walk_relationships(self) -> Generator[_Relationship, None, None]:
36
- import buildzr
37
- from buildzr.dsl.factory.gen_id import GenerateId
38
36
 
39
37
  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
38
 
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(','))
39
+ for child in self._workspace_or_element.children:
63
40
 
64
- yield fake_relationship
41
+ if child.relationships:
42
+ for relationship in child.relationships:
43
+ yield relationship
65
44
 
66
45
  explorer = Explorer(child).walk_relationships()
67
46
  yield from explorer
buildzr/dsl/expression.py CHANGED
@@ -10,9 +10,10 @@ from buildzr.dsl.dsl import (
10
10
  SoftwareSystem,
11
11
  Container,
12
12
  Component,
13
- _Relationship,
14
13
  )
15
14
 
15
+ from buildzr.dsl.relations import _Relationship
16
+
16
17
  import buildzr
17
18
  from typing import Set, Union, Optional, List, Dict, Any, Callable, Tuple, Sequence, Iterable, cast, Type
18
19
  from typing_extensions import TypeIs
@@ -87,6 +88,16 @@ class Element:
87
88
  def properties(self) -> Dict[str, Any]:
88
89
  return self._element.model.properties
89
90
 
91
+ @property
92
+ def group(self) -> Optional[str]:
93
+ """
94
+ Returns the group of the element. The group is a string that is used to
95
+ group elements in the Structurizr DSL.
96
+ """
97
+ if not isinstance(self._element.model, buildzr.models.Workspace):
98
+ return self._element.model.group
99
+ return None
100
+
90
101
  def __eq__(self, element: object) -> bool:
91
102
  return isinstance(element, type(self._element)) and\
92
103
  element.model.id == self._element.model.id
@@ -135,10 +146,10 @@ class Expression:
135
146
 
136
147
  def __init__(
137
148
  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]]=[],
149
+ include_elements: Iterable[Union[DslElement, Callable[[Workspace, Element], bool]]]=[lambda w, e: True],
150
+ exclude_elements: Iterable[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
151
+ include_relationships: Iterable[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[lambda w, e: True],
152
+ exclude_relationships: Iterable[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
142
153
  ) -> 'None':
143
154
  self._include_elements = include_elements
144
155
  self._exclude_elements = exclude_elements
@@ -154,9 +165,19 @@ class Expression:
154
165
 
155
166
  workspace_elements = buildzr.dsl.Explorer(workspace).walk_elements()
156
167
  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:
168
+ includes: List[bool] = []
169
+ excludes: List[bool] = []
170
+ for f in self._include_elements:
171
+ if isinstance(f, DslElement):
172
+ includes.append(f == element)
173
+ else:
174
+ includes.append(f(workspace, Element(element)))
175
+ for f in self._exclude_elements:
176
+ if isinstance(f, DslElement):
177
+ excludes.append(f == element)
178
+ else:
179
+ excludes.append(f(workspace, Element(element)))
180
+ if any(includes) and not any(excludes):
160
181
  filtered_elements.append(element)
161
182
 
162
183
  return filtered_elements
@@ -179,30 +200,47 @@ class Expression:
179
200
  def _is_relationship_of_excluded_elements(
180
201
  workspace: Workspace,
181
202
  relationship: Relationship,
182
- exclude_element_predicates: Iterable[Callable[[Workspace, Element], bool]],
203
+ exclude_element_predicates: Iterable[Union[DslElement, Callable[[Workspace, Element], bool]]],
183
204
  ) -> 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
- ])
205
+ for f in exclude_element_predicates:
206
+ if isinstance(f, DslElement):
207
+ if f == relationship.source or f == relationship.destination:
208
+ return True
209
+ else:
210
+ if f(workspace, relationship.source) or f(workspace, relationship.destination):
211
+ return True
212
+ return False
189
213
 
190
214
  workspace_relationships = buildzr.dsl.Explorer(workspace).walk_relationships()
215
+
191
216
  for relationship in workspace_relationships:
192
- any_includes = any([f(workspace, Relationship(relationship)) for f in self._include_relationships])
193
217
 
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
- ] + [
218
+ includes: List[bool] = []
219
+ excludes: List[bool] = []
220
+
221
+ for f in self._include_relationships:
222
+ if isinstance(f, DslElement):
223
+ includes.append(f == relationship)
224
+ else:
225
+ includes.append(f(workspace, Relationship(relationship)))
226
+
227
+ for f in self._exclude_relationships:
228
+ if isinstance(f, DslElement):
229
+ excludes.append(f == relationship)
230
+ else:
231
+ excludes.append(f(workspace, Relationship(relationship)))
232
+
233
+ # Also exclude relationships whose source or destination elements
234
+ # are excluded.
235
+ excludes.append(
199
236
  _is_relationship_of_excluded_elements(
200
237
  workspace,
201
238
  Relationship(relationship),
202
239
  self._exclude_elements,
203
240
  )
204
- ])
205
- if any_includes and not any_excludes:
241
+ )
242
+
243
+ if any(includes) and not any(excludes):
206
244
  filtered_relationships.append(relationship)
207
245
 
208
246
  return filtered_relationships
@@ -1,7 +1,5 @@
1
1
  from .interfaces import (
2
2
  DslElement,
3
- DslFluentRelationship,
4
- DslFluentSink,
5
3
  DslRelationship,
6
4
  DslWorkspaceElement,
7
5
  DslViewElement,
@@ -11,6 +9,4 @@ from .interfaces import (
11
9
  BindLeftLate,
12
10
  TSrc,
13
11
  TDst,
14
- TParent,
15
- TChild,
16
12
  )
@@ -11,6 +11,7 @@ from typing import (
11
11
  Callable,
12
12
  overload,
13
13
  Sequence,
14
+ MutableSet,
14
15
  cast,
15
16
  )
16
17
  from typing_extensions import (
@@ -29,9 +30,6 @@ Model = Union[
29
30
  TSrc = TypeVar('TSrc', bound='DslElement', contravariant=True)
30
31
  TDst = TypeVar('TDst', bound='DslElement', contravariant=True)
31
32
 
32
- TParent = TypeVar('TParent', bound=Union['DslWorkspaceElement', 'DslElement'], covariant=True)
33
- TChild = TypeVar('TChild', bound='DslElement', contravariant=True)
34
-
35
33
  class BindLeftLate(ABC, Generic[TDst]):
36
34
 
37
35
  @abstractmethod
@@ -128,11 +126,24 @@ class DslElement(BindRight[TSrc, TDst]):
128
126
  def destinations(self) -> List['DslElement']:
129
127
  pass
130
128
 
129
+ @property
130
+ @abstractmethod
131
+ def relationships(self) -> MutableSet['DslRelationship']:
132
+ pass
133
+
131
134
  @property
132
135
  @abstractmethod
133
136
  def tags(self) -> Set[str]:
134
137
  pass
135
138
 
139
+ def add_tags(self, *tags: str) -> None:
140
+ """
141
+ Add tags to the element.
142
+ """
143
+ self.tags.update(tags)
144
+ if not isinstance(self.model, buildzr.models.Workspace):
145
+ self.model.tags = ','.join(self.tags)
146
+
136
147
  def uses(
137
148
  self,
138
149
  other: 'DslElement',
@@ -170,41 +181,16 @@ class DslRelationship(ABC, Generic[TSrc, TDst]):
170
181
  def destination(self) -> DslElement:
171
182
  pass
172
183
 
184
+ def add_tags(self, *tags: str) -> None:
185
+ """
186
+ Adds tags to the relationship.
187
+ """
188
+ self.tags.update(tags)
189
+ self.model.tags = ','.join(self.tags)
190
+
173
191
  def __contains__(self, other: 'DslElement') -> bool:
174
192
  return self.source.model.id == other.model.id or self.destination.model.id == other.model.id
175
193
 
176
- class DslFluentRelationship(ABC, Generic[TParent]):
177
-
178
- """
179
- The abstract class that defines the interface for the fluent relationship
180
- definition, where the one or more relationships between elements accessible
181
- from the `TParent` element.
182
- """
183
-
184
- @abstractmethod
185
- def where(
186
- self,
187
- func: Callable[
188
- [TParent],
189
- Sequence[
190
- Union[
191
- DslRelationship,
192
- Sequence[DslRelationship]
193
- ]
194
- ]
195
- ]) -> TParent:
196
- pass
197
-
198
- @abstractmethod
199
- def get(self) -> TParent:
200
- pass
201
-
202
- class DslFluentSink(ABC):
203
-
204
- @abstractmethod
205
- def to_json(self, path: str) -> None:
206
- pass
207
-
208
194
  class DslViewElement(ABC):
209
195
 
210
196
  ViewModel = Union[
@@ -242,8 +228,8 @@ class DslViewsElement(ABC):
242
228
  pass
243
229
 
244
230
  @abstractmethod
245
- def contains(
231
+ def add_views(
246
232
  self,
247
233
  *views: Union[DslViewElement],
248
- ) -> DslFluentSink:
234
+ ) -> None:
249
235
  pass
buildzr/dsl/relations.py CHANGED
@@ -18,33 +18,13 @@ from buildzr.dsl.interfaces import (
18
18
  BindLeft,
19
19
  BindLeftLate,
20
20
  DslRelationship,
21
- DslFluentRelationship,
22
21
  DslElement,
23
22
  DslWorkspaceElement,
24
23
  TSrc, TDst,
25
- TParent, TChild,
26
24
  )
27
25
  from buildzr.dsl.factory import GenerateId
28
26
  import buildzr
29
27
 
30
- def _is_software_fluent_relationship(
31
- obj: '_FluentRelationship[Any]'
32
- ) -> TypeIs['_FluentRelationship[buildzr.dsl.SoftwareSystem]']:
33
- return isinstance(obj._parent, buildzr.dsl.SoftwareSystem)
34
-
35
- def _is_container_fluent_relationship(
36
- obj: '_FluentRelationship[Any]'
37
- ) -> TypeIs['_FluentRelationship[buildzr.dsl.Container]']:
38
- return isinstance(obj._parent, buildzr.dsl.Container)
39
-
40
- def _is_list_of_dslelements_or_usesfromlates(
41
- obj: list
42
- ) -> TypeIs[List[Union[DslElement, '_UsesFromLate[DslElement]']]]:
43
- return all(
44
- isinstance(item, DslElement) or isinstance(item, _UsesFromLate)
45
- for item in obj
46
- )
47
-
48
28
  @dataclass
49
29
  class With:
50
30
  tags: Optional[Set[str]] = None
@@ -115,8 +95,17 @@ class _Relationship(DslRelationship[TSrc, TDst]):
115
95
  uses_data.relationship.destinationId = str(destination.model.id)
116
96
 
117
97
  if not isinstance(uses_data.source.model, buildzr.models.Workspace):
118
- uses_data.source.destinations.append(self._dst)
119
- self._dst.sources.append(self._src)
98
+
99
+ # Prevent any duplicate sources/destinations, especially when creating implied relationships.
100
+ if not any([self._dst.model.id == dest.model.id for dest in uses_data.source.destinations]):
101
+ uses_data.source.destinations.append(self._dst)
102
+ if not any([self._src.model.id == src.model.id for src in self._dst.sources]):
103
+ self._dst.sources.append(self._src)
104
+
105
+ # Make this relationship accessible from the source element.
106
+ if not any([r.model.id == self.model.id for r in self._src.relationships]):
107
+ self._src.relationships.add(self)
108
+
120
109
  if _include_in_model:
121
110
  if uses_data.source.model.relationships:
122
111
  uses_data.source.model.relationships.append(uses_data.relationship)
@@ -155,60 +144,6 @@ class _Relationship(DslRelationship[TSrc, TDst]):
155
144
  self._ref[0].relationship.url = url
156
145
  return self
157
146
 
158
- class _FluentRelationship(DslFluentRelationship[TParent]):
159
-
160
- """
161
- A hidden class used in the fluent DSL syntax after specifying a model (i.e.,
162
- Person, Software System, Container) to define relationship(s) within the
163
- specified model.
164
- """
165
-
166
- def __init__(self, parent: TParent) -> None:
167
- self._parent: TParent = parent
168
-
169
- def where(
170
- self,
171
- func: Callable[
172
- [TParent],
173
- Sequence[
174
- Union[
175
- DslRelationship,
176
- Sequence[DslRelationship]
177
- ]
178
- ]
179
- ], implied: bool=False) -> TParent:
180
-
181
- relationships: Sequence[DslRelationship] = []
182
-
183
- func = cast(Callable[[TParent], Sequence[Union[DslRelationship, Sequence[DslRelationship]]]], func)
184
-
185
- # Flatten the resulting relationship list.
186
- relationships = [
187
- rel for sublist in func(self._parent)
188
- for rel in (
189
- sublist if isinstance(sublist, list) else [sublist]
190
- )
191
- ]
192
-
193
- # If we have relationship s >> do >> a.b, then create s >> do >> a.
194
- # If we have relationship s.ss >> do >> a.b.c, then create s.ss >> do >> a.b and s.ss >> do >> a.
195
- # And so on...
196
- if implied:
197
- for relationship in relationships:
198
- source = relationship.source
199
- parent = relationship.destination.parent
200
- while parent is not None and not isinstance(parent, DslWorkspaceElement):
201
- r = source.uses(parent, description=relationship.model.description, technology=relationship.model.technology)
202
- r.model.linkedRelationshipId = relationship.model.id
203
- parent = parent.parent
204
-
205
- return self._parent
206
-
207
- # TODO: Remove this and replace with something better.
208
- # Doesn't feel "fluent."
209
- def get(self) -> TParent:
210
- return self._parent
211
-
212
147
  class _RelationshipDescription(Generic[TDst]):
213
148
 
214
149
  def __init__(self, description: str, technology: Optional[str]=None) -> None:
@@ -3,7 +3,7 @@ import dataclasses, json
3
3
  import enum
4
4
  import humps
5
5
  from buildzr.dsl.interfaces import DslElement, DslWorkspaceElement
6
- from typing import Union, List, TYPE_CHECKING, Type, Any
6
+ from typing import Union, List, TYPE_CHECKING, Type, Any, Dict, cast
7
7
  from typing_extensions import TypeGuard
8
8
 
9
9
  if TYPE_CHECKING:
@@ -47,15 +47,35 @@ class JsonEncoder(json.JSONEncoder):
47
47
  def default(self, obj: JsonEncodable) -> Union[str, list, dict]:
48
48
  # Handle the default encoder the nicely wrapped DSL elements.
49
49
  if isinstance(obj, DslElement) or isinstance(obj, DslWorkspaceElement):
50
- return humps.camelize(_remove_nones(dataclasses.asdict(obj.model)))
50
+ return cast(Union[str, list, dict], humps.camelize(_remove_nones(dataclasses.asdict(obj.model))))
51
51
 
52
52
  # Handle the default encoder for those `dataclass`es models generated in
53
53
  # `buildzr.model`
54
54
  elif _is_dataclass(obj):
55
- return humps.camelize(_remove_nones(dataclasses.asdict(obj)))
55
+ d = dataclasses.asdict(obj)
56
+ # Special handling for 'properties' fields of type Dict[str, Any]
57
+ if 'properties' in d and isinstance(d['properties'], dict):
58
+ d['properties'] = self._encode_properties(d['properties'])
59
+ return cast(Union[str, list, dict], humps.camelize(_remove_nones(d)))
56
60
 
57
61
  # Handle the enums
58
62
  elif isinstance(obj, enum.Enum):
59
63
  return str(obj.value)
60
64
 
61
- return super().default(obj) #type: ignore[no-any-return]
65
+ return super().default(obj) #type: ignore[no-any-return]
66
+
67
+ def _encode_properties(self, props: dict) -> dict:
68
+ # Recursively encode values in the properties dict
69
+ result: Dict[str, Any] = {}
70
+ for k, v in props.items():
71
+ if _is_dataclass(v):
72
+ result[k] = humps.camelize(_remove_nones(dataclasses.asdict(v)))
73
+ elif isinstance(v, enum.Enum):
74
+ result[k] = str(v.value)
75
+ elif isinstance(v, dict):
76
+ result[k] = self._encode_properties(v)
77
+ elif isinstance(v, list):
78
+ result[k] = [self._encode_properties(i) if isinstance(i, dict) else i for i in v]
79
+ else:
80
+ result[k] = v
81
+ return result
@@ -1,10 +1,11 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: buildzr
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: Structurizr for the `buildzr`s 🧱⚒️
5
5
  Project-URL: homepage, https://github.com/amirulmenjeni/buildzr
6
6
  Project-URL: issues, https://github.com/amirulmenjeni/buildzr/issues
7
7
  Author-email: Amirul Menjeni <amirulmenjeni@pm.me>
8
+ License-File: LICENSE.md
8
9
  Keywords: architecture,buildzr,c4model,design,diagram,structurizr
9
10
  Classifier: Development Status :: 3 - Alpha
10
11
  Classifier: License :: OSI Approved :: MIT License
@@ -29,13 +30,11 @@ Description-Content-Type: text/markdown
29
30
 
30
31
  # Structurizr for the `buildzr`s 🧱⚒️
31
32
 
32
- `buildzr` is a [Structurizr](https://structurizr.com/) authoring tool for Python programmers.
33
+ `buildzr` is a [Structurizr](https://structurizr.com/) authoring tool for Python programmers. It allows you to declaratively or procedurally author Structurizr models and diagrams.
33
34
 
34
- If you're not familiar with Structurizr, it is both an open standard (see [Structurizr JSON schema](https://github.com/structurizr/json)) and a [set of tools](https://docs.structurizr.com/usage) for building software architecture diagrams as code. Structurizr derive its architecture modeling paradigm based on the [C4 model](https://c4model.com/), the modeling language for visualizing software architecture.
35
+ If you're not familiar with Structurizr, it is both an open standard (see [Structurizr JSON schema](https://github.com/structurizr/json)) and a [set of tools](https://docs.structurizr.com/usage) for building software architecture diagrams as code. Structurizr derive its architecture modeling paradigm based on the [C4 model](https://c4model.com/), the modeling language for describing software architectures and the relationships.
35
36
 
36
- `buildzr` offers flexible and fluent APIs to write software architecture models,
37
- leveraging the standard Structurizr JSON schema for interoperability with
38
- various rendering and authoring tools.
37
+ In Structurizr, you define architecture models and their relationships first. And then, you can re-use the models to present multiple perspectives, views, and stories about your architecture.
39
38
 
40
39
  # Quick Start 🚀
41
40
 
@@ -52,15 +51,10 @@ pip install buildzr
52
51
  The module `buildzr.dsl` contains all the classes you need to create a workspace containing all the architecture models.
53
52
 
54
53
  Below is an example, where we:
55
- 1. Create the models (`Person`, `SoftwareSystem`s, the `Container`s inside the `SoftwareSystem`, and the relationships between them)
54
+ 1. Create the models (`Person`, `SoftwareSystem`s, the `Container`s inside the `SoftwareSystem`, and the relationships -- `>>` -- between them)
56
55
  2. Define multiple views using the models we've created before.
57
56
 
58
57
  ```python
59
- import os
60
- import json
61
-
62
- from buildzr.encoders import JsonEncoder
63
-
64
58
  from buildzr.dsl import (
65
59
  Workspace,
66
60
  SoftwareSystem,
@@ -72,74 +66,65 @@ from buildzr.dsl import (
72
66
  Group,
73
67
  )
74
68
 
75
- w = Workspace('w')\
76
- .contains(
77
- Group(
78
- "My Company",
79
- Person('Web Application User').labeled('u'),
80
- SoftwareSystem('Corporate Web App').labeled('webapp')
81
- .contains(
82
- Container('database'),
83
- Container('api'),
84
- )\
85
- .where(lambda s: [
86
- s.api >> "Reads and writes data from/to" >> s.database,
87
- ])
88
- ),
89
- Group(
90
- "Microsoft",
91
- SoftwareSystem('Microsoft 365').labeled('email_system'),
92
- )
93
- )\
94
- .where(lambda w: [
95
- w.person().u >> [
96
- desc("Reads and writes email using") >> w.software_system().email_system,
97
- desc("Create work order using") >> w.software_system().webapp,
98
- ],
99
- w.software_system().webapp >> "sends notification using" >> w.software_system().email_system,
100
- ])\
101
- .with_views(
102
- SystemContextView(
103
- lambda w: w.software_system().webapp,
104
- key='web_app_system_context_00',
105
- description="Web App System Context",
106
- auto_layout='lr',
107
- exclude_elements=[
108
- lambda w, e: w.person().user == e,
109
- ]
110
- ),
111
- ContainerView(
112
- lambda w: w.software_system().webapp,
113
- key='web_app_container_view_00',
114
- auto_layout='lr',
115
- description="Web App Container View",
116
- )
117
- )\
118
- .get_workspace()
119
-
120
- # Save workspace to a JSON file following the Structurizr JSON schema.
121
- w.to_json('workspace.json')
69
+ with Workspace('w') as w:
70
+ with Group("My Company"):
71
+ u = Person('Web Application User')
72
+ webapp = SoftwareSystem('Corporate Web App')
73
+ with webapp:
74
+ database = Container('database')
75
+ api = Container('api')
76
+ api >> ("Reads and writes data from/to", "http/api") >> database
77
+ with Group("Microsoft"):
78
+ email_system = SoftwareSystem('Microsoft 365')
79
+
80
+ u >> [
81
+ desc("Reads and writes email using") >> email_system,
82
+ desc("Create work order using") >> webapp,
83
+ ]
84
+ webapp >> "sends notification using" >> email_system
85
+
86
+ SystemContextView(
87
+ software_system_selector=webapp,
88
+ key='web_app_system_context_00',
89
+ description="Web App System Context",
90
+ auto_layout='lr',
91
+ exclude_elements=[
92
+ u,
93
+ ]
94
+ )
95
+
96
+ ContainerView(
97
+ software_system_selector=webapp,
98
+ key='web_app_container_view_00',
99
+ auto_layout='lr',
100
+ description="Web App Container View",
101
+ )
102
+
103
+ w.to_json('workspace.json')
122
104
  ```
123
105
 
124
106
  Here's a short breakdown on what's happening:
125
- - In `Workspace(...).contains(...)` method, we define the _static_ C4 models (i.e., `Person`, `SoftwareSystem`, and the `Container`s in the software system).
126
- - In the `Workspace(...).contains(...).where(...)`, we define the relationships between the C4 models in the workspace. We access the models via the `w` parameter in the `lambda` function, and create the relationships using the `>>` operators.
127
- - Once we have all the models and their relationships defined, we use (and re-use!) the static models to create multiple views to tell different stories and show various narrative to help document your software architecture.
107
+ - In the `with Workspace('w') as w:` block, we've created a `Workspace` named `w`.
108
+ - Inside this block, we're in the context of the `w` workspace, so any models, relationships, and views we declared in this block will belong to that workspace. We've declared a few models: `Person`, the `SoftwareSystems`, their `Container`s, and their relationships with each other. Oh, we've separated them into different `Group`s, too!
109
+ - Showing the who's and the what's in an architecture model is good, but an architecture model is incomplete without the arrows that describes the relationships between the systems. In `buildzr`, we can define relationships with the `>>` operator.
110
+ - Once we have all the models and their relationships defined, we use (and re-use!) the static models to create multiple views to tell different stories and show various narrative to help document your software architecture. In this case, we've created one `SystemContextView` and one `ContainerView` for the `webapp`.
128
111
  - Finally, we write the workspace definitions into a JSON file, which can be consumed by rendering tools, or used for further processing.
129
112
 
130
113
  The JSON output can be found [here](examples/system_context_and_container_view.json). You can also try out https://structurizr.com/json to see how this workspace will be rendered.
131
114
 
132
115
  # Why use `buildzr`?
133
116
 
134
- ✅ Uses fluent APIs to help you create C4 model architecture diagrams in Python concisely.
117
+ ✅ Uses `buildzr`'s declarative DSL syntax to help you create C4 model architecture diagrams in Python concisely.
118
+
119
+ ✅ Uses `buildzr`'s DSL APIs to programmatically create C4 model architecture diagrams. Good if you need to automate things!
135
120
 
136
121
  ✅ Write Structurizr diagrams more securely with extensive type hints and [mypy](https://mypy-lang.org) support.
137
122
 
138
123
  ✅ Stays true to the [Structurizr JSON schema](https://mypy-lang.org/) standards. `buildzr` uses the [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator) to automatically generate the "low-level" [representation](buildzr/models/models.py) of the Workspace model. This reduces deprecancy between `buildzr` and the Structurizr JSON schema.
139
124
 
140
- ✅ Writing architecture diagrams in Python allows you to integrate programmability and automation into your software architecture diagramming and documentation workflow.
125
+ ✅ Writing architecture models and diagrams in Python allows you to integrate programmability and automation into your software architecture diagramming and documentation workflow. For example, you might want to programmatically automate the creation of architecture models from metadata pulled from your IT asset management system, but still want to declaratively define how to present them.
141
126
 
142
- ✅ Uses the familiar Python programming language to write software architecture diagrams!
127
+ ✅ Uses the familiar Python programming language and its rich toolchains to write software architecture models and diagrams!
143
128
 
144
129
  # Contributing
145
130
 
@@ -0,0 +1,24 @@
1
+ buildzr/__about__.py,sha256=DgAnctESS7a-r7bKUfHvaK2XtP5BpQpkO_wLESIfHjc,17
2
+ buildzr/__init__.py,sha256=hY-cOdjBQcz0v2m8cBF1oEJFIbcR3sWI-xww--0RKSo,99
3
+ buildzr/dsl/__init__.py,sha256=k0G9blhA3NSzCBs1dSfBNzCh0iDS_ylkzS_5a3pqrSY,375
4
+ buildzr/dsl/color.py,sha256=at5lo3WgLEDCjrnbu37ra1p1TjzdB51sxeW7pBMC_7U,4019
5
+ buildzr/dsl/dsl.py,sha256=47_O2OqN6XR7MNZnmJK2EaX8JPZrGu-h5YEVEaLUwdM,54154
6
+ buildzr/dsl/explorer.py,sha256=vLAgQEYd0h22QuVfWfBdk4zyDpGaE1T67Pn9V7P1C-I,1238
7
+ buildzr/dsl/expression.py,sha256=EMAtaZQA0O_zwENCZs3l4u_w9wUuO09XA0LgnvsomfA,8256
8
+ buildzr/dsl/relations.py,sha256=GBs5epr9uuExU_H6VcP4XY76iJPQ__rz_d8tZlhhWQ4,11891
9
+ buildzr/dsl/factory/__init__.py,sha256=niaYqvNPUWJejoPyRyABUtzVsoxaV8eSjzS9dta4bMI,30
10
+ buildzr/dsl/factory/gen_id.py,sha256=LnaeOCMngSvYkcGnuARjQYoUVWdcOoNHO2EHe6PMGco,538
11
+ buildzr/dsl/interfaces/__init__.py,sha256=ncYARIPB4ARcCCRObgV1b4jluEubpm2s46mp1ZC8Urk,197
12
+ buildzr/dsl/interfaces/interfaces.py,sha256=j8UCPCRegmFZ2Jl4qCz0uW0OxBS2mRe0VjPcNExH8mc,5553
13
+ buildzr/encoders/__init__.py,sha256=suID63Ay-023T0uKD25EAoGYmAMTa9AKxIjioccpiPM,32
14
+ buildzr/encoders/encoder.py,sha256=n8WLVMrisykBTLTr1z6PAxgqhqW2dFRZhSupOuMdx_A,3235
15
+ buildzr/models/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
16
+ buildzr/models/generate.sh,sha256=924UoEXr5WBZ926TZfRgEQGHwBqtLGU5k0G2UfExLEg,1061
17
+ buildzr/models/models.py,sha256=0LhLG1wmbt4dvROV5MEBZLLoxPbMpkUsOqNz525cynE,42489
18
+ buildzr/sinks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ buildzr/sinks/interfaces.py,sha256=LOZekP4WNjomD5J5f3FnZTwGj0aXMr6RbrvyFV5zn0E,383
20
+ buildzr/sinks/json_sink.py,sha256=onKOZTpwOQfeMEj1ONkuIEHBAQhx4yQSqqI_lgZBaP8,777
21
+ buildzr-0.0.9.dist-info/METADATA,sha256=qjmMwahyMKpFXfFQZj4p_j_X0VVC-j3Lknwm_7NRJLQ,6578
22
+ buildzr-0.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ buildzr-0.0.9.dist-info/licenses/LICENSE.md,sha256=e8e6W6tL4MbBY-c-gXMgDbaMf_BnaQDQv4Yoy42b-CI,1070
24
+ buildzr-0.0.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any