buildzr 0.0.5__tar.gz → 0.0.7__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 (44) hide show
  1. {buildzr-0.0.5 → buildzr-0.0.7}/PKG-INFO +5 -6
  2. {buildzr-0.0.5 → buildzr-0.0.7}/README.md +4 -4
  3. buildzr-0.0.7/buildzr/__about__.py +1 -0
  4. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/dsl.py +65 -38
  5. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/interfaces/__init__.py +2 -0
  6. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/interfaces/interfaces.py +37 -0
  7. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/relations.py +25 -16
  8. buildzr-0.0.7/buildzr/sinks/interfaces.py +18 -0
  9. buildzr-0.0.7/buildzr/sinks/json_sink.py +24 -0
  10. buildzr-0.0.7/tests/samples/__init__.py +0 -0
  11. {buildzr-0.0.5 → buildzr-0.0.7}/tests/test_dsl.py +66 -1
  12. buildzr-0.0.7/tests/test_typehints.py +247 -0
  13. buildzr-0.0.5/buildzr/__about__.py +0 -1
  14. {buildzr-0.0.5 → buildzr-0.0.7}/.gitignore +0 -0
  15. {buildzr-0.0.5 → buildzr-0.0.7}/CONTRIBUTING.md +0 -0
  16. {buildzr-0.0.5 → buildzr-0.0.7}/LICENSE.md +0 -0
  17. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/__init__.py +0 -0
  18. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/__init__.py +0 -0
  19. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/explorer.py +0 -0
  20. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/expression.py +0 -0
  21. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/factory/__init__.py +0 -0
  22. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/dsl/factory/gen_id.py +0 -0
  23. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/encoders/__init__.py +0 -0
  24. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/encoders/encoder.py +0 -0
  25. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/models/__init__.py +0 -0
  26. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/models/generate.sh +0 -0
  27. {buildzr-0.0.5 → buildzr-0.0.7}/buildzr/models/models.py +0 -0
  28. {buildzr-0.0.5/tests/samples → buildzr-0.0.7/buildzr/sinks}/__init__.py +0 -0
  29. {buildzr-0.0.5 → buildzr-0.0.7}/pyproject.toml +0 -0
  30. {buildzr-0.0.5 → buildzr-0.0.7}/tests/__init__.py +0 -0
  31. {buildzr-0.0.5 → buildzr-0.0.7}/tests/abstract_builder.py +0 -0
  32. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/component_view.py +0 -0
  33. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/container_view.py +0 -0
  34. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/container_view_sugar.py +0 -0
  35. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/groups.py +0 -0
  36. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/implied_relationships.py +0 -0
  37. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/simple.py +0 -0
  38. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/simple_dsl.py +0 -0
  39. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/system_context_view.py +0 -0
  40. {buildzr-0.0.5 → buildzr-0.0.7}/tests/samples/system_landscape_view.py +0 -0
  41. {buildzr-0.0.5 → buildzr-0.0.7}/tests/test_explorer.py +0 -0
  42. {buildzr-0.0.5 → buildzr-0.0.7}/tests/test_expression.py +0 -0
  43. {buildzr-0.0.5 → buildzr-0.0.7}/tests/test_views.py +0 -0
  44. {buildzr-0.0.5 → buildzr-0.0.7}/tests/test_workspaces.py +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: buildzr
3
- Version: 0.0.5
3
+ Version: 0.0.7
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.7"
@@ -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']:
@@ -209,7 +199,15 @@ class Workspace(DslWorkspaceElement):
209
199
  def __dir__(self) -> Iterable[str]:
210
200
  return list(super().__dir__()) + list(self._dynamic_attrs.keys())
211
201
 
212
- class SoftwareSystem(DslElementRelationOverrides):
202
+ class SoftwareSystem(DslElementRelationOverrides[
203
+ 'SoftwareSystem',
204
+ Union[
205
+ 'Person',
206
+ 'SoftwareSystem',
207
+ 'Container',
208
+ 'Component'
209
+ ]
210
+ ]):
213
211
  """
214
212
  A software system.
215
213
  """
@@ -294,7 +292,15 @@ class SoftwareSystem(DslElementRelationOverrides):
294
292
  def __dir__(self) -> Iterable[str]:
295
293
  return list(super().__dir__()) + list(self._dynamic_attrs.keys())
296
294
 
297
- class Person(DslElementRelationOverrides):
295
+ class Person(DslElementRelationOverrides[
296
+ 'Person',
297
+ Union[
298
+ 'Person',
299
+ 'SoftwareSystem',
300
+ 'Container',
301
+ 'Component'
302
+ ]
303
+ ]):
298
304
  """
299
305
  A person who uses a software system.
300
306
  """
@@ -345,7 +351,15 @@ class Person(DslElementRelationOverrides):
345
351
  self._label = label
346
352
  return self
347
353
 
348
- class Container(DslElementRelationOverrides):
354
+ class Container(DslElementRelationOverrides[
355
+ 'Container',
356
+ Union[
357
+ 'Person',
358
+ 'SoftwareSystem',
359
+ 'Container',
360
+ 'Component'
361
+ ]
362
+ ]):
349
363
  """
350
364
  A container (something that can execute code or host data).
351
365
  """
@@ -425,7 +439,15 @@ class Container(DslElementRelationOverrides):
425
439
  def __dir__(self) -> Iterable[str]:
426
440
  return list(super().__dir__()) + list(self._dynamic_attrs.keys())
427
441
 
428
- class Component(DslElementRelationOverrides):
442
+ class Component(DslElementRelationOverrides[
443
+ 'Component',
444
+ Union[
445
+ 'Person',
446
+ 'SoftwareSystem',
447
+ 'Container',
448
+ 'Component'
449
+ ]
450
+ ]):
429
451
  """
430
452
  A component (a grouping of related functionality behind an interface that runs inside a container).
431
453
  """
@@ -487,6 +509,19 @@ class Group:
487
509
  self._name = name
488
510
  self._elements = elements
489
511
 
512
+ class _FluentSink(DslFluentSink):
513
+
514
+ def __init__(self, workspace: Workspace) -> None:
515
+ self._workspace = workspace
516
+
517
+ def to_json(self, path: str) -> None:
518
+ from buildzr.sinks.json_sink import JsonSink, JsonSinkConfig
519
+ sink = JsonSink()
520
+ sink.write(workspace=self._workspace.model, config=JsonSinkConfig(path=path))
521
+
522
+ def get_workspace(self) -> Workspace:
523
+ return self._workspace
524
+
490
525
  _RankDirection = Literal['tb', 'bt', 'lr', 'rl']
491
526
 
492
527
  _AutoLayout = Optional[
@@ -554,7 +589,7 @@ def _auto_layout_to_model(auto_layout: _AutoLayout) -> buildzr.models.AutomaticL
554
589
 
555
590
  return model
556
591
 
557
- class SystemLandscapeView:
592
+ class SystemLandscapeView(DslViewElement):
558
593
 
559
594
  from buildzr.dsl.expression import Expression, Element, Relationship
560
595
 
@@ -649,7 +684,7 @@ class SystemLandscapeView:
649
684
  for relationship_id in relationship_ids:
650
685
  self._m.relationships.append(RelationshipView(id=relationship_id))
651
686
 
652
- class SystemContextView:
687
+ class SystemContextView(DslViewElement):
653
688
 
654
689
  """
655
690
  If no filter is applied, this view includes all elements that have a direct
@@ -700,9 +735,6 @@ class SystemContextView:
700
735
  from buildzr.dsl.expression import Expression, Element, Relationship
701
736
  from buildzr.models import ElementView, RelationshipView
702
737
 
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
738
  software_system = self._selector(self._parent._parent)
707
739
  self._m.softwareSystemId = software_system.model.id
708
740
  view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
@@ -711,10 +743,6 @@ class SystemContextView:
711
743
  lambda w, e: software_system.model.id in e.destinations.ids,
712
744
  ]
713
745
 
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
746
  view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
719
747
  lambda w, r: software_system == r.source,
720
748
  lambda w, r: software_system == r.destination,
@@ -747,7 +775,7 @@ class SystemContextView:
747
775
  for relationship_id in relationship_ids:
748
776
  self._m.relationships.append(RelationshipView(id=relationship_id))
749
777
 
750
- class ContainerView:
778
+ class ContainerView(DslViewElement):
751
779
 
752
780
  from buildzr.dsl.expression import Expression, Element, Relationship
753
781
 
@@ -836,7 +864,7 @@ class ContainerView:
836
864
  for relationship_id in relationship_ids:
837
865
  self._m.relationships.append(RelationshipView(id=relationship_id))
838
866
 
839
- class ComponentView:
867
+ class ComponentView(DslViewElement):
840
868
 
841
869
  from buildzr.dsl.expression import Expression, Element, Relationship
842
870
 
@@ -945,34 +973,33 @@ class Views(DslViewsElement):
945
973
 
946
974
  def contains(
947
975
  self,
948
- *views: Union[
949
- SystemLandscapeView,
950
- SystemContextView,
951
- ContainerView,
952
- ComponentView,
953
- ]) -> Self:
976
+ *views: DslViewElement
977
+ ) -> _FluentSink:
954
978
 
955
979
  for view in views:
956
- view._parent = self
957
980
  if isinstance(view, SystemLandscapeView):
981
+ view._parent = self
958
982
  view._on_added()
959
983
  if self._m.systemLandscapeViews:
960
984
  self._m.systemLandscapeViews.append(view.model)
961
985
  else:
962
986
  self._m.systemLandscapeViews = [view.model]
963
987
  elif isinstance(view, SystemContextView):
988
+ view._parent = self
964
989
  view._on_added()
965
990
  if self._m.systemContextViews:
966
991
  self._m.systemContextViews.append(view.model)
967
992
  else:
968
993
  self._m.systemContextViews = [view.model]
969
994
  elif isinstance(view, ContainerView):
995
+ view._parent = self
970
996
  view._on_added()
971
997
  if self._m.containerViews:
972
998
  self._m.containerViews.append(view.model)
973
999
  else:
974
1000
  self._m.containerViews = [view.model]
975
1001
  elif isinstance(view, ComponentView):
1002
+ view._parent = self
976
1003
  view._on_added()
977
1004
  if self._m.componentViews:
978
1005
  self._m.componentViews.append(view.model)
@@ -981,7 +1008,7 @@ class Views(DslViewsElement):
981
1008
  else:
982
1009
  raise NotImplementedError("The view {0} is currently not supported", type(view))
983
1010
 
984
- return self
1011
+ return _FluentSink(self._parent)
985
1012
 
986
1013
  def get_workspace(self) -> Workspace:
987
1014
  """
@@ -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
@@ -204,6 +204,8 @@ class _FluentRelationship(DslFluentRelationship[TParent]):
204
204
 
205
205
  return self._parent
206
206
 
207
+ # TODO: Remove this and replace with something better.
208
+ # Doesn't feel "fluent."
207
209
  def get(self) -> TParent:
208
210
  return self._parent
209
211
 
@@ -224,7 +226,7 @@ class _RelationshipDescription(Generic[TDst]):
224
226
  destination=destination,
225
227
  )
226
228
 
227
- class _UsesFromLate(BindLeftLate[TDst]):
229
+ class _UsesFromLate(Generic[TDst], BindLeftLate[TDst]):
228
230
  """
229
231
  This method is used to create a relationship between one source element with
230
232
  multiple destination elements, like so:
@@ -306,7 +308,7 @@ class _UsesFromLate(BindLeftLate[TDst]):
306
308
 
307
309
  return self
308
310
 
309
- class DslElementRelationOverrides(DslElement):
311
+ class DslElementRelationOverrides(Generic[TSrc, TDst], DslElement[TSrc, TDst]):
310
312
 
311
313
  """
312
314
  Base class meant to be derived from to override the `__rshift__` method to
@@ -316,7 +318,7 @@ class DslElementRelationOverrides(DslElement):
316
318
 
317
319
  # TODO: Check why need to ignore the override error here.
318
320
  @overload # type: ignore[override]
319
- def __rshift__(self, other: DslElement) -> _Relationship[Self, DslElement]:
321
+ def __rshift__(self, other: TDst) -> _Relationship[Self, TDst]:
320
322
  """
321
323
  Create a relationship between the source element and the destination
322
324
  without specifying description or technology.
@@ -326,30 +328,30 @@ class DslElementRelationOverrides(DslElement):
326
328
  ...
327
329
 
328
330
  @overload
329
- def __rshift__(self, description_and_technology: Tuple[str, str]) -> _UsesFrom[Self, DslElement]:
331
+ def __rshift__(self, description_and_technology: Tuple[str, str]) -> _UsesFrom[Self, TDst]:
330
332
  ...
331
333
 
332
334
  @overload
333
- def __rshift__(self, description: str) -> _UsesFrom[Self, DslElement]:
335
+ def __rshift__(self, description: str) -> _UsesFrom[Self, TDst]:
334
336
  ...
335
337
 
336
338
  @overload
337
- def __rshift__(self, _RelationshipDescription: _RelationshipDescription[DslElement]) -> _UsesFrom[Self, DslElement]:
339
+ def __rshift__(self, _RelationshipDescription: _RelationshipDescription[TDst]) -> _UsesFrom[Self, TDst]:
338
340
  ...
339
341
 
340
342
  @overload
341
- def __rshift__(self, multiple_destinations: List[Union[DslElement, _UsesFromLate[DslElement]]]) -> List[_Relationship[Self, DslElement]]:
343
+ def __rshift__(self, multiple_destinations: List[Union[TDst, _UsesFromLate[TDst]]]) -> List[_Relationship[Self, TDst]]:
342
344
  ...
343
345
 
344
346
  def __rshift__(
345
347
  self,
346
348
  other: Union[
347
- DslElement,
349
+ TDst,
348
350
  str,
349
351
  Tuple[str, str],
350
- _RelationshipDescription[DslElement],
351
- List[Union[DslElement, _UsesFromLate[DslElement]]]
352
- ]) -> Union[_UsesFrom[Self, DslElement], _Relationship[Self, DslElement], List[_Relationship[Self, DslElement]]]:
352
+ _RelationshipDescription[TDst],
353
+ List[Union[TDst, _UsesFromLate[TDst]]]
354
+ ]) -> Union[_UsesFrom[Self, TDst], _Relationship[Self, TDst], List[_Relationship[Self, TDst]]]:
353
355
  if isinstance(other, DslElement):
354
356
  return cast(
355
357
  _Relationship[Self, DslElement],
@@ -364,8 +366,8 @@ class DslElementRelationOverrides(DslElement):
364
366
  return _UsesFrom(self, description=other[0], technology=other[1])
365
367
  elif isinstance(other, _RelationshipDescription):
366
368
  return _UsesFrom(self, description=other._description, technology=other._technology)
367
- elif _is_list_of_dslelements_or_usesfromlates(other):
368
- relationships: List[_Relationship[Self, DslElement]] = []
369
+ elif isinstance(other, list):
370
+ relationships: List[_Relationship[Self, TDst]] = []
369
371
  for dest in other:
370
372
  if isinstance(dest, _UsesFromLate):
371
373
  dest.set_source(self)
@@ -373,14 +375,21 @@ class DslElementRelationOverrides(DslElement):
373
375
  elif isinstance(dest, DslElement):
374
376
  relationships.append(
375
377
  cast(
376
- _Relationship[Self, DslElement],
378
+ _Relationship[Self, DslElement[Self, TDst]],
377
379
  cast(
378
- _UsesFrom[Self, DslElement],
380
+ _UsesFrom[Self, DslElement[Self, TDst]],
379
381
  _UsesFrom(self)
380
382
  ) >> dest
381
383
  )
382
384
  )
383
- return relationships
385
+ return cast(
386
+ Union[
387
+ List[_Relationship[Self, TDst]],
388
+ _UsesFrom[Self, TDst],
389
+ _Relationship[Self, TDst],
390
+ ],
391
+ relationships
392
+ )
384
393
  else:
385
394
  raise TypeError(f"Unsupported operand type for >>: '{type(self).__name__}' and {type(other).__name__}")
386
395
 
@@ -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")
@@ -0,0 +1,247 @@
1
+ # All tests in this file are to ensure that the typehints are correct.
2
+ # IMPORTANT: Run pytest with --mypy flag to check for typehint errors.
3
+
4
+ from typing import Optional
5
+ from buildzr.dsl import (
6
+ Workspace,
7
+ Person,
8
+ SoftwareSystem,
9
+ Container,
10
+ Component,
11
+ desc,
12
+ )
13
+
14
+ def test_relationship_typehint_person_to_person() -> Optional[None]:
15
+
16
+ w = (
17
+ Workspace("w")
18
+ .contains(
19
+ Person("p1"),
20
+ Person("p2"),
21
+ Person("p3"),
22
+ Person("p4"),
23
+ )
24
+ .where(lambda w: [
25
+ w.person().p1 >> "greet" >> w.person().p2,
26
+ w.person().p1 >> [
27
+ w.person().p3,
28
+ desc("greet") >> w.person().p4,
29
+ ]
30
+ ])
31
+ )
32
+
33
+ def test_relationship_typehint_person_to_software_system() -> Optional[None]:
34
+
35
+ w = (
36
+ Workspace("w")
37
+ .contains(
38
+ Person("p"),
39
+ SoftwareSystem("s1"),
40
+ SoftwareSystem("s2"),
41
+ SoftwareSystem("s3"),
42
+ SoftwareSystem("s4"),
43
+ )
44
+ .where(lambda w: [
45
+ w.person().p >> "use" >> w.software_system().s1,
46
+ w.person().p >> [
47
+ w.software_system().s2,
48
+ desc("use") >> w.software_system().s3,
49
+ desc("use") >> w.software_system().s4,
50
+ ]
51
+ ])
52
+ )
53
+
54
+ def test_relationship_typehint_person_to_container() -> Optional[None]:
55
+
56
+ w = (
57
+ Workspace("w")
58
+ .contains(
59
+ Person('p'),
60
+ SoftwareSystem('s')
61
+ .contains(
62
+ Container('c1'),
63
+ Container('c2'),
64
+ Container('c3'),
65
+ Container('c4'),
66
+ )
67
+ )
68
+ .where(lambda w: [
69
+ w.person().p >> "use" >> w.software_system().s.container().c1,
70
+ w.person().p >> [
71
+ w.software_system().s.container().c2,
72
+ desc("use") >> w.software_system().s.container().c3,
73
+ desc("use") >> w.software_system().s.container().c4,
74
+ ]
75
+ ])
76
+ )
77
+
78
+ def test_relationship_typehint_person_to_component() -> Optional[None]:
79
+
80
+ w = (
81
+ Workspace("w")
82
+ .contains(
83
+ Person('p'),
84
+ SoftwareSystem('s')
85
+ .contains(
86
+ Container('c')
87
+ .contains(
88
+ Component('c1'),
89
+ Component('c2'),
90
+ Component('c3'),
91
+ Component('c4'),
92
+ )
93
+ )
94
+ )
95
+ .where(lambda w: [
96
+ w.person().p >> "use" >> w.software_system().s.container().c.component().c1,
97
+ w.person().p >> [
98
+ w.software_system().s.container().c.component().c2,
99
+ desc("use") >> w.software_system().s.container().c.component().c3,
100
+ desc("use") >> w.software_system().s.container().c.component().c4,
101
+ ]
102
+ ])
103
+ )
104
+
105
+ def test_relationship_typehint_software_system_to_software_system() -> Optional[None]:
106
+
107
+ w = (
108
+ Workspace("w")
109
+ .contains(
110
+ SoftwareSystem("s1"),
111
+ SoftwareSystem("s2"),
112
+ SoftwareSystem("s3"),
113
+ SoftwareSystem("s4"),
114
+ )
115
+ .where(lambda w: [
116
+ w.software_system().s1 >> "integrate" >> w.software_system().s2,
117
+ w.software_system().s1 >> [
118
+ w.software_system().s3,
119
+ desc("integrate") >> w.software_system().s4,
120
+ ]
121
+ ])
122
+ )
123
+
124
+ def test_relationship_typehint_software_system_to_container() -> Optional[None]:
125
+
126
+ w = (
127
+ Workspace("w")
128
+ .contains(
129
+ SoftwareSystem('s1'),
130
+ SoftwareSystem('s2')
131
+ .contains(
132
+ Container('c1'),
133
+ Container('c2'),
134
+ Container('c3'),
135
+ Container('c4'),
136
+ )
137
+ )
138
+ .where(lambda w: [
139
+ w.software_system().s1 >> "use" >> w.software_system().s2.container().c1,
140
+ w.software_system().s1 >> [
141
+ w.software_system().s2.container().c2,
142
+ desc("use") >> w.software_system().s2.container().c3,
143
+ desc("use") >> w.software_system().s2.container().c4,
144
+ ]
145
+ ])
146
+ )
147
+
148
+ def test_relationship_typehint_software_system_to_component() -> Optional[None]:
149
+
150
+ w = (
151
+ Workspace("w")
152
+ .contains(
153
+ SoftwareSystem('s1'),
154
+ SoftwareSystem('s2')
155
+ .contains(
156
+ Container('c')
157
+ .contains(
158
+ Component('c1'),
159
+ Component('c2'),
160
+ Component('c3'),
161
+ Component('c4'),
162
+ )
163
+ )
164
+ )
165
+ .where(lambda w: [
166
+ w.software_system().s1 >> "use" >> w.software_system().s2.container().c.component().c1,
167
+ w.software_system().s1 >> [
168
+ w.software_system().s2.container().c.component().c2,
169
+ desc("use") >> w.software_system().s2.container().c.component().c3,
170
+ desc("use") >> w.software_system().s2.container().c.component().c4,
171
+ ]
172
+ ])
173
+ )
174
+
175
+ def test_relationship_typehint_container_to_container() -> Optional[None]:
176
+
177
+ w = (
178
+ Workspace("w")
179
+ .contains(
180
+ SoftwareSystem('s')
181
+ .contains(
182
+ Container('c1'),
183
+ Container('c2'),
184
+ Container('c3'),
185
+ Container('c4'),
186
+ )
187
+ )
188
+ .where(lambda w: [
189
+ w.software_system().s.container().c1 >> "call" >> w.software_system().s.container().c2,
190
+ w.software_system().s.container().c1 >> [
191
+ w.software_system().s.container().c3,
192
+ desc("call") >> w.software_system().s.container().c4,
193
+ ]
194
+ ])
195
+ )
196
+
197
+ def test_relationship_typehint_container_to_component() -> Optional[None]:
198
+
199
+ w = (
200
+ Workspace("w")
201
+ .contains(
202
+ SoftwareSystem('s')
203
+ .contains(
204
+ Container('c1'),
205
+ Container('c2')
206
+ .contains(
207
+ Component('c1'),
208
+ Component('c2'),
209
+ Component('c3'),
210
+ Component('c4'),
211
+ )
212
+ )
213
+ )
214
+ .where(lambda w: [
215
+ w.software_system().s.container().c1 >> "call" >> w.software_system().s.container().c2.component().c1,
216
+ w.software_system().s.container().c1 >> [
217
+ w.software_system().s.container().c2.component().c2,
218
+ desc("call") >> w.software_system().s.container().c2.component().c3,
219
+ desc("call") >> w.software_system().s.container().c2.component().c4,
220
+ ]
221
+ ])
222
+ )
223
+
224
+ def test_relationship_typehint_component_to_component() -> Optional[None]:
225
+
226
+ w = (
227
+ Workspace("w")
228
+ .contains(
229
+ SoftwareSystem('s')
230
+ .contains(
231
+ Container('c')
232
+ .contains(
233
+ Component('c1'),
234
+ Component('c2'),
235
+ Component('c3'),
236
+ Component('c4'),
237
+ )
238
+ )
239
+ )
240
+ .where(lambda w: [
241
+ w.software_system().s.container().c.component().c1 >> "call" >> w.software_system().s.container().c.component().c2,
242
+ w.software_system().s.container().c.component().c1 >> [
243
+ w.software_system().s.container().c.component().c3,
244
+ desc("call") >> w.software_system().s.container().c.component().c4,
245
+ ]
246
+ ])
247
+ )
@@ -1 +0,0 @@
1
- VERSION = "0.0.5"
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