buildzr 0.0.4__tar.gz → 0.0.6__tar.gz

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.
Files changed (43) hide show
  1. {buildzr-0.0.4 → buildzr-0.0.6}/PKG-INFO +5 -6
  2. {buildzr-0.0.4 → buildzr-0.0.6}/README.md +4 -4
  3. buildzr-0.0.6/buildzr/__about__.py +1 -0
  4. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/dsl.py +26 -34
  5. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/interfaces/__init__.py +2 -0
  6. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/interfaces/interfaces.py +37 -0
  7. buildzr-0.0.6/buildzr/sinks/interfaces.py +18 -0
  8. buildzr-0.0.6/buildzr/sinks/json_sink.py +24 -0
  9. buildzr-0.0.6/tests/samples/__init__.py +0 -0
  10. {buildzr-0.0.4 → buildzr-0.0.6}/tests/test_dsl.py +66 -1
  11. buildzr-0.0.4/buildzr/__about__.py +0 -1
  12. {buildzr-0.0.4 → buildzr-0.0.6}/.gitignore +0 -0
  13. {buildzr-0.0.4 → buildzr-0.0.6}/CONTRIBUTING.md +0 -0
  14. {buildzr-0.0.4 → buildzr-0.0.6}/LICENSE.md +0 -0
  15. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/__init__.py +0 -0
  16. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/__init__.py +0 -0
  17. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/explorer.py +0 -0
  18. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/expression.py +0 -0
  19. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/factory/__init__.py +0 -0
  20. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/factory/gen_id.py +0 -0
  21. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/dsl/relations.py +0 -0
  22. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/encoders/__init__.py +0 -0
  23. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/encoders/encoder.py +0 -0
  24. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/models/__init__.py +0 -0
  25. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/models/generate.sh +0 -0
  26. {buildzr-0.0.4 → buildzr-0.0.6}/buildzr/models/models.py +0 -0
  27. {buildzr-0.0.4/tests/samples → buildzr-0.0.6/buildzr/sinks}/__init__.py +0 -0
  28. {buildzr-0.0.4 → buildzr-0.0.6}/pyproject.toml +0 -0
  29. {buildzr-0.0.4 → buildzr-0.0.6}/tests/__init__.py +0 -0
  30. {buildzr-0.0.4 → buildzr-0.0.6}/tests/abstract_builder.py +0 -0
  31. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/component_view.py +0 -0
  32. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/container_view.py +0 -0
  33. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/container_view_sugar.py +0 -0
  34. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/groups.py +0 -0
  35. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/implied_relationships.py +0 -0
  36. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/simple.py +0 -0
  37. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/simple_dsl.py +0 -0
  38. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/system_context_view.py +0 -0
  39. {buildzr-0.0.4 → buildzr-0.0.6}/tests/samples/system_landscape_view.py +0 -0
  40. {buildzr-0.0.4 → buildzr-0.0.6}/tests/test_explorer.py +0 -0
  41. {buildzr-0.0.4 → buildzr-0.0.6}/tests/test_expression.py +0 -0
  42. {buildzr-0.0.4 → buildzr-0.0.6}/tests/test_views.py +0 -0
  43. {buildzr-0.0.4 → buildzr-0.0.6}/tests/test_workspaces.py +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: buildzr
3
- Version: 0.0.4
3
+ Version: 0.0.6
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
9
8
  Keywords: architecture,buildzr,c4model,design,diagram,structurizr
10
9
  Classifier: Development Status :: 3 - Alpha
11
10
  Classifier: License :: OSI Approved :: MIT License
@@ -118,15 +117,15 @@ w = Workspace('w')\
118
117
  )\
119
118
  .get_workspace()
120
119
 
121
- # Writes the Workspace model to a JSON file.
122
- with open(os.path.join(os.path.curdir, f"{__file__.split('.')[0]}.json"), 'w', encoding='utf-8') as f:
123
- json.dump(w.model, f, ensure_ascii=False, indent=4, cls=JsonEncoder)
120
+ # Save workspace to a JSON file following the Structurizr JSON schema.
121
+ w.to_json('workspace.json')
124
122
  ```
125
123
 
126
124
  Here's a short breakdown on what's happening:
127
125
  - In `Workspace(...).contains(...)` method, we define the _static_ C4 models (i.e., `Person`, `SoftwareSystem`, and the `Container`s in the software system).
128
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.
129
- - Finally, 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.
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.
128
+ - Finally, we write the workspace definitions into a JSON file, which can be consumed by rendering tools, or used for further processing.
130
129
 
131
130
  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.
132
131
 
@@ -88,15 +88,15 @@ w = Workspace('w')\
88
88
  )\
89
89
  .get_workspace()
90
90
 
91
- # Writes the Workspace model to a JSON file.
92
- with open(os.path.join(os.path.curdir, f"{__file__.split('.')[0]}.json"), 'w', encoding='utf-8') as f:
93
- json.dump(w.model, f, ensure_ascii=False, indent=4, cls=JsonEncoder)
91
+ # Save workspace to a JSON file following the Structurizr JSON schema.
92
+ w.to_json('workspace.json')
94
93
  ```
95
94
 
96
95
  Here's a short breakdown on what's happening:
97
96
  - In `Workspace(...).contains(...)` method, we define the _static_ C4 models (i.e., `Person`, `SoftwareSystem`, and the `Container`s in the software system).
98
97
  - 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.
99
- - Finally, 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.
98
+ - 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.
99
+ - Finally, we write the workspace definitions into a JSON file, which can be consumed by rendering tools, or used for further processing.
100
100
 
101
101
  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.
102
102
 
@@ -0,0 +1 @@
1
+ VERSION = "0.0.6"
@@ -26,10 +26,14 @@ from typing import (
26
26
  Type,
27
27
  )
28
28
 
29
+ from buildzr.sinks.interfaces import Sink
30
+
29
31
  from buildzr.dsl.interfaces import (
30
32
  DslWorkspaceElement,
31
33
  DslElement,
34
+ DslViewElement,
32
35
  DslViewsElement,
36
+ DslFluentSink,
33
37
  TSrc, TDst,
34
38
  TParent, TChild,
35
39
  )
@@ -45,20 +49,6 @@ from buildzr.dsl.relations import (
45
49
  def _child_name_transform(name: str) -> str:
46
50
  return name.lower().replace(' ', '_')
47
51
 
48
- # TODO: Remove, not used.
49
- # def _create_linked_relationship_from(
50
- # relationship: '_Relationship[TSrc, TDst]') -> buildzr.models.Relationship:
51
-
52
- # src = relationship.source
53
- # dst = relationship.destination
54
-
55
- # return buildzr.models.Relationship(
56
- # id=GenerateId.for_relationship(),
57
- # sourceId=str(src.model.id),
58
- # destinationId=str(dst.parent.model.id),
59
- # linkedRelationshipId=relationship.model.id,
60
- # )
61
-
62
52
  TypedModel = TypeVar('TypedModel')
63
53
  class TypedDynamicAttribute(Generic[TypedModel]):
64
54
 
@@ -197,7 +187,7 @@ class Workspace(DslWorkspaceElement):
197
187
  'ContainerView',
198
188
  'ComponentView',
199
189
  ]
200
- ) -> 'Views':
190
+ ) -> '_FluentSink':
201
191
  return Views(self).contains(*views)
202
192
 
203
193
  def __getattr__(self, name: str) -> Union['Person', 'SoftwareSystem']:
@@ -487,6 +477,16 @@ class Group:
487
477
  self._name = name
488
478
  self._elements = elements
489
479
 
480
+ class _FluentSink(DslFluentSink):
481
+
482
+ def __init__(self, workspace: Workspace) -> None:
483
+ self._workspace = workspace
484
+
485
+ def to_json(self, path: str) -> None:
486
+ from buildzr.sinks.json_sink import JsonSink, JsonSinkConfig
487
+ sink = JsonSink()
488
+ sink.write(workspace=self._workspace.model, config=JsonSinkConfig(path=path))
489
+
490
490
  _RankDirection = Literal['tb', 'bt', 'lr', 'rl']
491
491
 
492
492
  _AutoLayout = Optional[
@@ -554,7 +554,7 @@ def _auto_layout_to_model(auto_layout: _AutoLayout) -> buildzr.models.AutomaticL
554
554
 
555
555
  return model
556
556
 
557
- class SystemLandscapeView:
557
+ class SystemLandscapeView(DslViewElement):
558
558
 
559
559
  from buildzr.dsl.expression import Expression, Element, Relationship
560
560
 
@@ -649,7 +649,7 @@ class SystemLandscapeView:
649
649
  for relationship_id in relationship_ids:
650
650
  self._m.relationships.append(RelationshipView(id=relationship_id))
651
651
 
652
- class SystemContextView:
652
+ class SystemContextView(DslViewElement):
653
653
 
654
654
  """
655
655
  If no filter is applied, this view includes all elements that have a direct
@@ -700,9 +700,6 @@ class SystemContextView:
700
700
  from buildzr.dsl.expression import Expression, Element, Relationship
701
701
  from buildzr.models import ElementView, RelationshipView
702
702
 
703
- # TODO: Refactor below codes. Similar patterns may exists for other views.
704
- # Maybe make the views a subclass of some abstract `BaseView` class?
705
-
706
703
  software_system = self._selector(self._parent._parent)
707
704
  self._m.softwareSystemId = software_system.model.id
708
705
  view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
@@ -711,10 +708,6 @@ class SystemContextView:
711
708
  lambda w, e: software_system.model.id in e.destinations.ids,
712
709
  ]
713
710
 
714
- # TODO: (Or, TOTHINK?) The code below includes all sources and all
715
- # destinations of the subject software system. What if we want to
716
- # exclude a source? Maybe the predicates in `elements` and
717
- # `relationships` should be ANDed together afterall?
718
711
  view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
719
712
  lambda w, r: software_system == r.source,
720
713
  lambda w, r: software_system == r.destination,
@@ -747,7 +740,7 @@ class SystemContextView:
747
740
  for relationship_id in relationship_ids:
748
741
  self._m.relationships.append(RelationshipView(id=relationship_id))
749
742
 
750
- class ContainerView:
743
+ class ContainerView(DslViewElement):
751
744
 
752
745
  from buildzr.dsl.expression import Expression, Element, Relationship
753
746
 
@@ -836,7 +829,7 @@ class ContainerView:
836
829
  for relationship_id in relationship_ids:
837
830
  self._m.relationships.append(RelationshipView(id=relationship_id))
838
831
 
839
- class ComponentView:
832
+ class ComponentView(DslViewElement):
840
833
 
841
834
  from buildzr.dsl.expression import Expression, Element, Relationship
842
835
 
@@ -945,34 +938,33 @@ class Views(DslViewsElement):
945
938
 
946
939
  def contains(
947
940
  self,
948
- *views: Union[
949
- SystemLandscapeView,
950
- SystemContextView,
951
- ContainerView,
952
- ComponentView,
953
- ]) -> Self:
941
+ *views: DslViewElement
942
+ ) -> _FluentSink:
954
943
 
955
944
  for view in views:
956
- view._parent = self
957
945
  if isinstance(view, SystemLandscapeView):
946
+ view._parent = self
958
947
  view._on_added()
959
948
  if self._m.systemLandscapeViews:
960
949
  self._m.systemLandscapeViews.append(view.model)
961
950
  else:
962
951
  self._m.systemLandscapeViews = [view.model]
963
952
  elif isinstance(view, SystemContextView):
953
+ view._parent = self
964
954
  view._on_added()
965
955
  if self._m.systemContextViews:
966
956
  self._m.systemContextViews.append(view.model)
967
957
  else:
968
958
  self._m.systemContextViews = [view.model]
969
959
  elif isinstance(view, ContainerView):
960
+ view._parent = self
970
961
  view._on_added()
971
962
  if self._m.containerViews:
972
963
  self._m.containerViews.append(view.model)
973
964
  else:
974
965
  self._m.containerViews = [view.model]
975
966
  elif isinstance(view, ComponentView):
967
+ view._parent = self
976
968
  view._on_added()
977
969
  if self._m.componentViews:
978
970
  self._m.componentViews.append(view.model)
@@ -981,7 +973,7 @@ class Views(DslViewsElement):
981
973
  else:
982
974
  raise NotImplementedError("The view {0} is currently not supported", type(view))
983
975
 
984
- return self
976
+ return _FluentSink(self._parent)
985
977
 
986
978
  def get_workspace(self) -> Workspace:
987
979
  """
@@ -1,8 +1,10 @@
1
1
  from .interfaces import (
2
2
  DslElement,
3
3
  DslFluentRelationship,
4
+ DslFluentSink,
4
5
  DslRelationship,
5
6
  DslWorkspaceElement,
7
+ DslViewElement,
6
8
  DslViewsElement,
7
9
  BindLeft,
8
10
  BindRight,
@@ -199,6 +199,36 @@ class DslFluentRelationship(ABC, Generic[TParent]):
199
199
  def get(self) -> TParent:
200
200
  pass
201
201
 
202
+ class DslFluentSink(ABC):
203
+
204
+ @abstractmethod
205
+ def to_json(self, path: str) -> None:
206
+ pass
207
+
208
+ class DslViewElement(ABC):
209
+
210
+ ViewModel = Union[
211
+ buildzr.models.SystemLandscapeView,
212
+ buildzr.models.SystemContextView,
213
+ buildzr.models.ContainerView,
214
+ buildzr.models.ComponentView,
215
+ buildzr.models.DynamicView,
216
+ buildzr.models.DeploymentView,
217
+ ]
218
+
219
+ @property
220
+ @abstractmethod
221
+ def model(self) -> ViewModel:
222
+ pass
223
+
224
+ @property
225
+ @abstractmethod
226
+ def parent(self) -> 'DslViewsElement':
227
+ pass
228
+
229
+ def _on_added(self) -> None:
230
+ pass
231
+
202
232
  class DslViewsElement(ABC):
203
233
 
204
234
  @property
@@ -209,4 +239,11 @@ class DslViewsElement(ABC):
209
239
  @property
210
240
  @abstractmethod
211
241
  def parent(self) -> DslWorkspaceElement:
242
+ pass
243
+
244
+ @abstractmethod
245
+ def contains(
246
+ self,
247
+ *views: Union[DslViewElement],
248
+ ) -> DslFluentSink:
212
249
  pass
@@ -0,0 +1,18 @@
1
+ from typing import (
2
+ Optional,
3
+ Any,
4
+ TypedDict,
5
+ TypeVar,
6
+ Generic,
7
+ )
8
+ from dataclasses import dataclass
9
+ from abc import ABC, abstractmethod
10
+ from buildzr.models.models import Workspace
11
+
12
+ TConfig = TypeVar('TConfig')
13
+
14
+ class Sink(ABC, Generic[TConfig]):
15
+
16
+ @abstractmethod
17
+ def write(self, workspace: Workspace, config: Optional[TConfig]=None) -> None:
18
+ pass
@@ -0,0 +1,24 @@
1
+ from typing import (
2
+ Optional,
3
+ )
4
+ from dataclasses import dataclass
5
+ from buildzr.models.models import Workspace
6
+ from buildzr.encoders.encoder import JsonEncoder
7
+ from buildzr.sinks.interfaces import Sink
8
+
9
+ @dataclass
10
+ class JsonSinkConfig:
11
+ path: str
12
+
13
+ class JsonSink(Sink[JsonSinkConfig]):
14
+
15
+ def write(self, workspace: Workspace, config: Optional[JsonSinkConfig]=None) -> None:
16
+ if config is not None:
17
+ with open(config.path, 'w') as file:
18
+ file.write(JsonEncoder().encode(workspace))
19
+ else:
20
+ import os
21
+ workspace_name = workspace.name.replace(' ', '_').lower()
22
+
23
+ with open(os.path.join(os.curdir, f'{workspace_name}.json'), 'w') as file:
24
+ file.write(JsonEncoder().encode(workspace))
File without changes
@@ -843,4 +843,69 @@ def test_dsl_relationship_without_desc_multiple_dest() -> Optional[None]:
843
843
  assert not w.person().user.model.relationships[2].technology
844
844
  assert w.person().user.model.relationships[0].destinationId == w.software_system().software_1.model.id
845
845
  assert w.person().user.model.relationships[1].destinationId == w.software_system().software_2.model.id
846
- assert w.person().user.model.relationships[2].destinationId == w.software_system().software_3.model.id
846
+ assert w.person().user.model.relationships[2].destinationId == w.software_system().software_3.model.id
847
+
848
+ def test_fluent_json_sink() -> Optional[None]:
849
+
850
+ Workspace("w")\
851
+ .contains(
852
+ Person("User"),
853
+ SoftwareSystem("Software 1"),
854
+ SoftwareSystem("Software 2"),
855
+ )\
856
+ .where(lambda w: [
857
+ w.person().user >> [
858
+ desc("Uses") >> w.software_system().software_1,
859
+ desc("Uses") >> w.software_system().software_2,
860
+ ]
861
+ ])\
862
+ .with_views(
863
+ SystemContextView(
864
+ key="ss_01",
865
+ title="System Context",
866
+ description="A simple system context view for software 1",
867
+ software_system_selector=lambda w: w.software_system().software_1,
868
+ ),
869
+ SystemContextView(
870
+ key="ss_02",
871
+ title="System Context",
872
+ description="A simple system context view for software 2",
873
+ software_system_selector=lambda w: w.software_system().software_2,
874
+ ),
875
+ )\
876
+ .to_json(path="test.json")
877
+
878
+ with open("test.json", "r") as f:
879
+ data = f.read()
880
+
881
+ assert data
882
+
883
+ import os
884
+ os.remove("test.json")
885
+
886
+ def test_fluent_json_sink_empty_views() -> Optional[None]:
887
+
888
+ # No views defined here.
889
+
890
+ Workspace("w")\
891
+ .contains(
892
+ Person("User"),
893
+ SoftwareSystem("Software 1"),
894
+ SoftwareSystem("Software 2"),
895
+ )\
896
+ .where(lambda w: [
897
+ w.person().user >> [
898
+ desc("Uses") >> w.software_system().software_1,
899
+ desc("Uses") >> w.software_system().software_2,
900
+ ]
901
+ ])\
902
+ .with_views()\
903
+ .to_json(path="test.json")
904
+
905
+ with open("test.json", "r") as f:
906
+ data = f.read()
907
+
908
+ assert data
909
+
910
+ import os
911
+ os.remove("test.json")
@@ -1 +0,0 @@
1
- VERSION = "0.0.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes