buildzr 0.0.16__tar.gz → 0.0.18__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.
- {buildzr-0.0.16 → buildzr-0.0.18}/CONTRIBUTING.md +69 -63
- {buildzr-0.0.16 → buildzr-0.0.18}/PKG-INFO +1 -1
- buildzr-0.0.18/buildzr/__about__.py +1 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/dsl.py +29 -8
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/sinks/json_sink.py +3 -1
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_dsl.py +122 -12
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_explorer.py +2 -2
- buildzr-0.0.16/buildzr/__about__.py +0 -1
- {buildzr-0.0.16 → buildzr-0.0.18}/.gitignore +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/LICENSE.md +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/README.md +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/color.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/explorer.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/expression.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/factory/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/factory/gen_id.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/interfaces/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/interfaces/interfaces.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/dsl/relations.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/encoders/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/encoders/encoder.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/models/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/models/generate.sh +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/models/models.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/sinks/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/buildzr/sinks/interfaces.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/pyproject.toml +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/abstract_builder.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/__init__.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/component_view.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/container_view.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/container_view_sugar.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/groups.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/implied_relationships.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/nested_groups.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/simple.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/simple_dsl.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/system_context_view.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/samples/system_landscape_view.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_expression.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_typehints.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_views.py +0 -0
- {buildzr-0.0.16 → buildzr-0.0.18}/tests/test_workspaces.py +0 -0
@@ -58,74 +58,80 @@ pytest --mypy tests
|
|
58
58
|
|
59
59
|
Sometimes it is valuable to manually check the output of the Structurizr workspace generated by `buildzr` visually. For this reason, you can create `buildzr` samples in the [`tests/samples`](https://github.com/amirulmenjeni/buildzr/tree/master/tests/samples) directory.
|
60
60
|
|
61
|
-
Here's an example of
|
61
|
+
Here's an example of a simple workspace to showcase the [group](https://docs.structurizr.com/dsl/cookbook/groups/) feature in Structurizr DSL used for grouping elements together:
|
62
62
|
|
63
63
|
```python
|
64
|
-
import
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
return w.model
|
64
|
+
from buildzr.dsl import (
|
65
|
+
Workspace,
|
66
|
+
SoftwareSystem,
|
67
|
+
Container,
|
68
|
+
Component,
|
69
|
+
Group,
|
70
|
+
SystemLandscapeView,
|
71
|
+
SystemContextView,
|
72
|
+
ContainerView,
|
73
|
+
)
|
74
|
+
|
75
|
+
with Workspace("w") as w:
|
76
|
+
with Group("Company 1"):
|
77
|
+
system_a = SoftwareSystem("A")
|
78
|
+
with system_a:
|
79
|
+
a1 = Container("a1")
|
80
|
+
a2 = Container("a2")
|
81
|
+
|
82
|
+
with Group("Company 2"):
|
83
|
+
system_b = SoftwareSystem("B")
|
84
|
+
with system_b:
|
85
|
+
b1 = Container("b1")
|
86
|
+
b2 = Container("b2")
|
87
|
+
with b2:
|
88
|
+
c1 = Component("c1")
|
89
|
+
|
90
|
+
system_c = SoftwareSystem("C")
|
91
|
+
|
92
|
+
# Define relationships
|
93
|
+
system_a >> "Uses" >> system_b
|
94
|
+
a1 >> "Uses" >> b1
|
95
|
+
system_a >> "Uses" >> system_c
|
96
|
+
|
97
|
+
# Create views
|
98
|
+
SystemLandscapeView(
|
99
|
+
key='groups-sample',
|
100
|
+
description="Groups Sample"
|
101
|
+
)
|
102
|
+
|
103
|
+
SystemContextView(
|
104
|
+
software_system_selector=system_a,
|
105
|
+
key='groups-sample-a',
|
106
|
+
description="Groups Sample - Software System A"
|
107
|
+
)
|
108
|
+
|
109
|
+
SystemContextView(
|
110
|
+
software_system_selector=system_b,
|
111
|
+
key='groups-sample-b',
|
112
|
+
description="Groups Sample - Software System B"
|
113
|
+
)
|
114
|
+
|
115
|
+
ContainerView(
|
116
|
+
software_system_selector=system_b,
|
117
|
+
key='groups-sample-b2',
|
118
|
+
description="Groups Sample - Container B2"
|
119
|
+
)
|
120
|
+
|
121
|
+
# Export to JSON
|
122
|
+
w.to_json('groups-sample.json')
|
124
123
|
```
|
125
124
|
|
126
|
-
|
125
|
+
The example above demonstrates the current `buildzr` API using context managers (`with` statements) for a cleaner, more Pythonic syntax. Key features shown:
|
127
126
|
|
128
|
-
|
127
|
+
- Use `with Workspace("name") as w:` to create a workspace context
|
128
|
+
- Use `with Group("name"):` to group elements together
|
129
|
+
- Use `with software_system:` or `with container:` to nest containers and components
|
130
|
+
- Define relationships using the `>>` operator
|
131
|
+
- Views and styles are created within the workspace context
|
132
|
+
- Export using `w.to_json('filename.json')`
|
133
|
+
|
134
|
+
You can run this code directly or create sample files in the `tests/samples` directory for testing purposes.
|
129
135
|
|
130
136
|
### Working with the `.json` files
|
131
137
|
|
@@ -0,0 +1 @@
|
|
1
|
+
VERSION = "0.0.18"
|
@@ -133,12 +133,24 @@ class Workspace(DslWorkspaceElement):
|
|
133
133
|
self,
|
134
134
|
) -> None:
|
135
135
|
|
136
|
+
"""
|
137
|
+
Process implied relationships:
|
138
|
+
If we have relationship s >> do >> a.b, then create s >> do >> a.
|
139
|
+
If we have relationship s.ss >> do >> a.b.c, then create s.ss >> do >> a.b and s.ss >> do >> a.
|
140
|
+
And so on...
|
141
|
+
|
142
|
+
Relationships of `SoftwareSystemInstance`s and `ContainerInstance`s are
|
143
|
+
skipped.
|
144
|
+
|
145
|
+
This process is idempotent, which means this can be called multiple times
|
146
|
+
without duplicating similar relationships.
|
147
|
+
"""
|
148
|
+
|
149
|
+
if not self._use_implied_relationships:
|
150
|
+
return
|
151
|
+
|
136
152
|
from buildzr.dsl.explorer import Explorer
|
137
153
|
|
138
|
-
# Process implied relationships:
|
139
|
-
# If we have relationship s >> do >> a.b, then create s >> do >> a.
|
140
|
-
# If we have relationship s.ss >> do >> a.b.c, then create s.ss >> do >> a.b and s.ss >> do >> a.
|
141
|
-
# And so on...
|
142
154
|
explorer = Explorer(self)
|
143
155
|
relationships = list(explorer.walk_relationships())
|
144
156
|
for relationship in relationships:
|
@@ -146,6 +158,10 @@ class Workspace(DslWorkspaceElement):
|
|
146
158
|
destination = relationship.destination
|
147
159
|
destination_parent = destination.parent
|
148
160
|
|
161
|
+
if isinstance(source, (SoftwareSystemInstance, ContainerInstance)) or \
|
162
|
+
isinstance(destination, (SoftwareSystemInstance, ContainerInstance)):
|
163
|
+
continue
|
164
|
+
|
149
165
|
while destination_parent is not None and \
|
150
166
|
isinstance(source, DslElement) and \
|
151
167
|
not isinstance(source.model, buildzr.models.Workspace) and \
|
@@ -201,8 +217,7 @@ class Workspace(DslWorkspaceElement):
|
|
201
217
|
else:
|
202
218
|
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(model)))
|
203
219
|
|
204
|
-
def apply_view(
|
205
|
-
self,
|
220
|
+
def apply_view( self,
|
206
221
|
view: Union[
|
207
222
|
'SystemLandscapeView',
|
208
223
|
'SystemContextView',
|
@@ -212,6 +227,8 @@ class Workspace(DslWorkspaceElement):
|
|
212
227
|
]
|
213
228
|
) -> None:
|
214
229
|
|
230
|
+
self._imply_relationships()
|
231
|
+
|
215
232
|
view._on_added(self)
|
216
233
|
|
217
234
|
if not self.model.views:
|
@@ -269,10 +286,14 @@ class Workspace(DslWorkspaceElement):
|
|
269
286
|
else:
|
270
287
|
self.model.views.configuration.styles.relationships = style.model
|
271
288
|
|
272
|
-
def to_json(self, path: str) -> None:
|
289
|
+
def to_json(self, path: str, pretty: bool=False) -> None:
|
290
|
+
|
291
|
+
self._imply_relationships()
|
292
|
+
|
273
293
|
from buildzr.sinks.json_sink import JsonSink, JsonSinkConfig
|
274
294
|
sink = JsonSink()
|
275
|
-
sink.write(workspace=self.model, config=JsonSinkConfig(path=path))
|
295
|
+
sink.write(workspace=self.model, config=JsonSinkConfig(path=path, pretty=pretty))
|
296
|
+
|
276
297
|
|
277
298
|
def _add_dynamic_attr(self, name: str, model: Union['Person', 'SoftwareSystem']) -> None:
|
278
299
|
if isinstance(model, Person):
|
@@ -9,13 +9,15 @@ from buildzr.sinks.interfaces import Sink
|
|
9
9
|
@dataclass
|
10
10
|
class JsonSinkConfig:
|
11
11
|
path: str
|
12
|
+
pretty: bool = False
|
12
13
|
|
13
14
|
class JsonSink(Sink[JsonSinkConfig]):
|
14
15
|
|
15
16
|
def write(self, workspace: Workspace, config: Optional[JsonSinkConfig]=None) -> None:
|
16
17
|
if config is not None:
|
18
|
+
indent = 2 if config.pretty else None
|
17
19
|
with open(config.path, 'w') as file:
|
18
|
-
file.write(JsonEncoder().encode(workspace))
|
20
|
+
file.write(JsonEncoder(indent=indent).encode(workspace))
|
19
21
|
else:
|
20
22
|
import os
|
21
23
|
workspace_name = workspace.name.replace(' ', '_').lower()
|
@@ -15,6 +15,7 @@ from buildzr.dsl import (
|
|
15
15
|
SystemContextView,
|
16
16
|
DeploymentEnvironment,
|
17
17
|
DeploymentNode,
|
18
|
+
DeploymentView,
|
18
19
|
InfrastructureNode,
|
19
20
|
DeploymentGroup,
|
20
21
|
SoftwareSystemInstance,
|
@@ -336,7 +337,10 @@ def test_implied_relationship() -> Optional[None]:
|
|
336
337
|
# relationship. For example, u -> s.database doesn't explicitly create a u
|
337
338
|
# -> s relationship in the workspace JSON.
|
338
339
|
|
339
|
-
#
|
340
|
+
# Conditions to take into account:
|
341
|
+
# 1. The implied relationships method be must idempotent (e.g., if it is
|
342
|
+
# called in `to_json` twice, or in other methods like `apply_view`, it
|
343
|
+
# doesn't create duplicates)
|
340
344
|
|
341
345
|
with Workspace("w", implied_relationships=True) as w:
|
342
346
|
u = Person('u')
|
@@ -354,18 +358,46 @@ def test_implied_relationship() -> Optional[None]:
|
|
354
358
|
|
355
359
|
u >> "Runs SQL queries" >> s.db # `u >> "Runs SQL queries" >> s`` should be implied
|
356
360
|
|
357
|
-
|
358
|
-
|
359
|
-
|
361
|
+
# Invoke imply relationships whenever a view is called.
|
362
|
+
#
|
363
|
+
# The implied relationship ids and related elements
|
364
|
+
# should appear in the view.
|
365
|
+
SystemContextView(
|
366
|
+
software_system_selector=s,
|
367
|
+
key='s_00',
|
368
|
+
description="App system context",
|
369
|
+
)
|
370
|
+
|
371
|
+
# Invoke imply relationships more than once.
|
372
|
+
# Should be no problem.
|
373
|
+
w.to_json('workspace.test.json')
|
374
|
+
w.to_json('workspace2.test.json')
|
375
|
+
|
376
|
+
assert isinstance(w.u, Person)
|
377
|
+
assert isinstance(w.s, SoftwareSystem)
|
378
|
+
assert len(w.u.model.relationships) == 2 # Should have u >> R >> s and u >> R >> s.database
|
379
|
+
|
380
|
+
assert w.u.model.relationships[0].description == "Runs SQL queries"
|
381
|
+
assert w.u.model.relationships[0].sourceId == w.u.model.id
|
382
|
+
assert w.u.model.relationships[0].destinationId == w.s.db.model.id
|
383
|
+
|
384
|
+
assert w.u.model.relationships[1].description == "Runs SQL queries"
|
385
|
+
assert w.u.model.relationships[1].sourceId == w.u.model.relationships[0].sourceId
|
386
|
+
assert w.u.model.relationships[1].destinationId == w.s.model.id
|
387
|
+
assert w.u.model.relationships[1].linkedRelationshipId == w.u.model.relationships[0].id
|
360
388
|
|
361
|
-
|
362
|
-
|
363
|
-
|
389
|
+
system_context_view_elements = [x.id for x in w._m.views.systemContextViews[0].elements]
|
390
|
+
assert u.model.id in system_context_view_elements
|
391
|
+
assert s.model.id in system_context_view_elements
|
364
392
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
393
|
+
system_context_view_relationships = [x.id for x in w._m.views.systemContextViews[0].relationships]
|
394
|
+
assert w.u.model.relationships[0].id not in system_context_view_relationships
|
395
|
+
assert w.u.model.relationships[1].id in system_context_view_relationships
|
396
|
+
assert w.u.model.relationships[1].linkedRelationshipId == w.u.model.relationships[0].id
|
397
|
+
|
398
|
+
import os
|
399
|
+
os.remove('workspace.test.json')
|
400
|
+
os.remove('workspace2.test.json')
|
369
401
|
|
370
402
|
def test_tags_on_elements() -> Optional[None]:
|
371
403
|
|
@@ -1072,4 +1104,82 @@ def test_json_sink_empty_views() -> Optional[None]:
|
|
1072
1104
|
assert data
|
1073
1105
|
|
1074
1106
|
import os
|
1075
|
-
os.remove("test.json")
|
1107
|
+
os.remove("test.json")
|
1108
|
+
def test_deployment_instance_relationships_with_implied_relationships() -> Optional[None]:
|
1109
|
+
"""
|
1110
|
+
Test that deployment instance relationships are created correctly when
|
1111
|
+
implied_relationships=True, without creating duplicates.
|
1112
|
+
|
1113
|
+
This test ensures:
|
1114
|
+
1. Container relationships automatically create ContainerInstance relationships
|
1115
|
+
2. No duplicate instance relationships are created when implied_relationships=True
|
1116
|
+
3. Instance relationships are only created once, even with multiple view/export calls
|
1117
|
+
"""
|
1118
|
+
|
1119
|
+
with Workspace('deployment-test', implied_relationships=True) as w:
|
1120
|
+
# Create containers with relationships
|
1121
|
+
ecommerce = SoftwareSystem('E-Commerce System')
|
1122
|
+
with ecommerce:
|
1123
|
+
api_gateway = Container('API Gateway', technology='Kong')
|
1124
|
+
order_svc = Container('Order Service', technology='Node.js')
|
1125
|
+
db = Container('Database', technology='MongoDB')
|
1126
|
+
|
1127
|
+
# Define container relationships
|
1128
|
+
api_gateway >> "Routes to" >> order_svc
|
1129
|
+
order_svc >> "Stores in" >> db
|
1130
|
+
|
1131
|
+
# Create deployment with container instances
|
1132
|
+
with DeploymentEnvironment('Production') as prod:
|
1133
|
+
with DeploymentNode('AWS', technology='Cloud Provider'):
|
1134
|
+
api_gw_instance = ContainerInstance(api_gateway)
|
1135
|
+
order_instance = ContainerInstance(order_svc)
|
1136
|
+
db_instance = ContainerInstance(db)
|
1137
|
+
|
1138
|
+
# Create views and export (triggers implied relationships multiple times)
|
1139
|
+
SystemContextView(
|
1140
|
+
software_system_selector=ecommerce,
|
1141
|
+
key='test-system-context',
|
1142
|
+
description="Test System Context",
|
1143
|
+
)
|
1144
|
+
|
1145
|
+
DeploymentView(
|
1146
|
+
environment=prod,
|
1147
|
+
key='test-deployment',
|
1148
|
+
)
|
1149
|
+
|
1150
|
+
# Export multiple times to ensure idempotency
|
1151
|
+
w.to_json('test_deployment1.json')
|
1152
|
+
w.to_json('test_deployment2.json')
|
1153
|
+
|
1154
|
+
# Verify instance relationships exist
|
1155
|
+
assert api_gw_instance.model.relationships is not None
|
1156
|
+
assert order_instance.model.relationships is not None
|
1157
|
+
|
1158
|
+
# Get all instance relationships
|
1159
|
+
api_gw_rels = api_gw_instance.model.relationships
|
1160
|
+
order_rels = order_instance.model.relationships
|
1161
|
+
|
1162
|
+
# Should have exactly 1 relationship from api_gw_instance to order_instance
|
1163
|
+
api_to_order_rels = [
|
1164
|
+
r for r in api_gw_rels
|
1165
|
+
if r.destinationId == order_instance.model.id
|
1166
|
+
]
|
1167
|
+
assert len(api_to_order_rels) == 1, f"Expected 1 relationship, found {len(api_to_order_rels)}"
|
1168
|
+
assert api_to_order_rels[0].description == "Routes to"
|
1169
|
+
|
1170
|
+
# Should have exactly 1 relationship from order_instance to db_instance
|
1171
|
+
order_to_db_rels = [
|
1172
|
+
r for r in order_rels
|
1173
|
+
if r.destinationId == db_instance.model.id
|
1174
|
+
]
|
1175
|
+
assert len(order_to_db_rels) == 1, f"Expected 1 relationship, found {len(order_to_db_rels)}"
|
1176
|
+
assert order_to_db_rels[0].description == "Stores in"
|
1177
|
+
|
1178
|
+
# Verify linkedRelationshipId is set correctly
|
1179
|
+
assert api_to_order_rels[0].linkedRelationshipId is not None
|
1180
|
+
assert order_to_db_rels[0].linkedRelationshipId is not None
|
1181
|
+
|
1182
|
+
# Clean up
|
1183
|
+
import os
|
1184
|
+
os.remove('test_deployment1.json')
|
1185
|
+
os.remove('test_deployment2.json')
|
@@ -90,7 +90,7 @@ def test_walk_relationships(workspace: Workspace) -> Optional[None]:
|
|
90
90
|
|
91
91
|
# 5 explicit relationships.
|
92
92
|
# Add one additional implied relationship.
|
93
|
-
# And four additional from container instances for each two container instance (2x2=4
|
93
|
+
# And four additional from container instances for each two container instance (2x2=4).
|
94
94
|
#
|
95
95
|
# Explanation: if we have containers A and B with relationship A >> "Uses" >> B,
|
96
96
|
# and container instances ci_A_1, ci_A_2, ci_B_1, ci_B_2, then we have the
|
@@ -99,7 +99,7 @@ def test_walk_relationships(workspace: Workspace) -> Optional[None]:
|
|
99
99
|
# ci_A_1 >> "Uses" >> ci_B_2
|
100
100
|
# ci_A_2 >> "Uses" >> ci_B_1
|
101
101
|
# ci_A_2 >> "Uses" >> ci_B_2
|
102
|
-
assert len(relationships) ==
|
102
|
+
assert len(relationships) == 10
|
103
103
|
|
104
104
|
for relationship in relationships:
|
105
105
|
relationship_set = (
|
@@ -1 +0,0 @@
|
|
1
|
-
VERSION = "0.0.16"
|
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
|
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
|
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
|
File without changes
|
File without changes
|