localstack-core 4.2.1.dev80__py3-none-any.whl → 4.2.1.dev82__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.
- localstack/services/cloudformation/engine/v2/change_set_model.py +625 -142
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +227 -43
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +34 -0
- localstack/version.py +2 -2
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/METADATA +1 -1
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/RECORD +14 -14
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/WHEEL +1 -1
- localstack_core-4.2.1.dev82.dist-info/plux.json +1 -0
- localstack_core-4.2.1.dev80.dist-info/plux.json +0 -1
- {localstack_core-4.2.1.dev80.data → localstack_core-4.2.1.dev82.data}/scripts/localstack +0 -0
- {localstack_core-4.2.1.dev80.data → localstack_core-4.2.1.dev82.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.2.1.dev80.data → localstack_core-4.2.1.dev82.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.2.1.dev80.dist-info → localstack_core-4.2.1.dev82.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import abc
|
4
4
|
import enum
|
5
5
|
from itertools import zip_longest
|
6
|
-
from typing import Any, Final, Generator, Optional, Union
|
6
|
+
from typing import Any, Final, Generator, Optional, Union, cast
|
7
7
|
|
8
8
|
from typing_extensions import TypeVar
|
9
9
|
|
@@ -29,11 +29,34 @@ class NothingType:
|
|
29
29
|
def __repr__(self) -> str:
|
30
30
|
return "Nothing"
|
31
31
|
|
32
|
+
def __bool__(self):
|
33
|
+
return False
|
34
|
+
|
35
|
+
def __iter__(self):
|
36
|
+
return iter(())
|
37
|
+
|
32
38
|
|
33
39
|
Maybe = Union[T, NothingType]
|
34
40
|
Nothing = NothingType()
|
35
41
|
|
36
42
|
|
43
|
+
class Scope(str):
|
44
|
+
_ROOT_SCOPE: Final[str] = str()
|
45
|
+
_SEPARATOR: Final[str] = "/"
|
46
|
+
|
47
|
+
def __new__(cls, scope: str = _ROOT_SCOPE) -> Scope:
|
48
|
+
return cast(Scope, super().__new__(cls, scope))
|
49
|
+
|
50
|
+
def open_scope(self, name: Scope | str) -> Scope:
|
51
|
+
return Scope(self._SEPARATOR.join([self, name]))
|
52
|
+
|
53
|
+
def open_index(self, index: int) -> Scope:
|
54
|
+
return Scope(self._SEPARATOR.join([self, str(index)]))
|
55
|
+
|
56
|
+
def unwrap(self) -> list[str]:
|
57
|
+
return self.split(self._SEPARATOR)
|
58
|
+
|
59
|
+
|
37
60
|
class ChangeType(enum.Enum):
|
38
61
|
UNCHANGED = "Unchanged"
|
39
62
|
CREATED = "Created"
|
@@ -43,11 +66,21 @@ class ChangeType(enum.Enum):
|
|
43
66
|
def __str__(self):
|
44
67
|
return self.value
|
45
68
|
|
69
|
+
def for_child(self, child_change_type: ChangeType) -> ChangeType:
|
70
|
+
if child_change_type == self:
|
71
|
+
return self
|
72
|
+
elif self == ChangeType.UNCHANGED:
|
73
|
+
return child_change_type
|
74
|
+
else:
|
75
|
+
return ChangeType.MODIFIED
|
76
|
+
|
46
77
|
|
47
78
|
class ChangeSetEntity(abc.ABC):
|
79
|
+
scope: Final[Scope]
|
48
80
|
change_type: Final[ChangeType]
|
49
81
|
|
50
|
-
def __init__(self, change_type: ChangeType):
|
82
|
+
def __init__(self, scope: Scope, change_type: ChangeType):
|
83
|
+
self.scope = scope
|
51
84
|
self.change_type = change_type
|
52
85
|
|
53
86
|
def get_children(self) -> Generator[ChangeSetEntity]:
|
@@ -80,44 +113,114 @@ class ChangeSetTerminal(ChangeSetEntity, abc.ABC): ...
|
|
80
113
|
|
81
114
|
|
82
115
|
class NodeTemplate(ChangeSetNode):
|
116
|
+
parameters: Final[NodeParameters]
|
117
|
+
conditions: Final[NodeConditions]
|
83
118
|
resources: Final[NodeResources]
|
84
119
|
|
85
|
-
def __init__(
|
86
|
-
|
120
|
+
def __init__(
|
121
|
+
self,
|
122
|
+
scope: Scope,
|
123
|
+
change_type: ChangeType,
|
124
|
+
parameters: NodeParameters,
|
125
|
+
conditions: NodeConditions,
|
126
|
+
resources: NodeResources,
|
127
|
+
):
|
128
|
+
super().__init__(scope=scope, change_type=change_type)
|
129
|
+
self.parameters = parameters
|
130
|
+
self.conditions = conditions
|
87
131
|
self.resources = resources
|
88
132
|
|
89
133
|
|
134
|
+
class NodeDivergence(ChangeSetNode):
|
135
|
+
value: Final[ChangeSetEntity]
|
136
|
+
divergence: Final[ChangeSetEntity]
|
137
|
+
|
138
|
+
def __init__(self, scope: Scope, value: ChangeSetEntity, divergence: ChangeSetEntity):
|
139
|
+
super().__init__(scope=scope, change_type=ChangeType.MODIFIED)
|
140
|
+
self.value = value
|
141
|
+
self.divergence = divergence
|
142
|
+
|
143
|
+
|
144
|
+
class NodeParameter(ChangeSetNode):
|
145
|
+
name: Final[str]
|
146
|
+
value: Final[ChangeSetEntity]
|
147
|
+
dynamic_value: Final[ChangeSetEntity]
|
148
|
+
|
149
|
+
def __init__(
|
150
|
+
self,
|
151
|
+
scope: Scope,
|
152
|
+
change_type: ChangeType,
|
153
|
+
name: str,
|
154
|
+
value: ChangeSetEntity,
|
155
|
+
dynamic_value: ChangeSetEntity,
|
156
|
+
):
|
157
|
+
super().__init__(scope=scope, change_type=change_type)
|
158
|
+
self.name = name
|
159
|
+
self.value = value
|
160
|
+
self.dynamic_value = dynamic_value
|
161
|
+
|
162
|
+
|
163
|
+
class NodeParameters(ChangeSetNode):
|
164
|
+
parameters: Final[list[NodeParameter]]
|
165
|
+
|
166
|
+
def __init__(self, scope: Scope, change_type: ChangeType, parameters: list[NodeParameter]):
|
167
|
+
super().__init__(scope=scope, change_type=change_type)
|
168
|
+
self.parameters = parameters
|
169
|
+
|
170
|
+
|
171
|
+
class NodeCondition(ChangeSetNode):
|
172
|
+
name: Final[str]
|
173
|
+
body: Final[ChangeSetEntity]
|
174
|
+
|
175
|
+
def __init__(self, scope: Scope, change_type: ChangeType, name: str, body: ChangeSetEntity):
|
176
|
+
super().__init__(scope=scope, change_type=change_type)
|
177
|
+
self.name = name
|
178
|
+
self.body = body
|
179
|
+
|
180
|
+
|
181
|
+
class NodeConditions(ChangeSetNode):
|
182
|
+
conditions: Final[list[NodeCondition]]
|
183
|
+
|
184
|
+
def __init__(self, scope: Scope, change_type: ChangeType, conditions: list[NodeCondition]):
|
185
|
+
super().__init__(scope=scope, change_type=change_type)
|
186
|
+
self.conditions = conditions
|
187
|
+
|
188
|
+
|
90
189
|
class NodeResources(ChangeSetNode):
|
91
190
|
resources: Final[list[NodeResource]]
|
92
191
|
|
93
|
-
def __init__(self, change_type: ChangeType, resources: list[NodeResource]):
|
94
|
-
super().__init__(change_type=change_type)
|
192
|
+
def __init__(self, scope: Scope, change_type: ChangeType, resources: list[NodeResource]):
|
193
|
+
super().__init__(scope=scope, change_type=change_type)
|
95
194
|
self.resources = resources
|
96
195
|
|
97
196
|
|
98
197
|
class NodeResource(ChangeSetNode):
|
99
198
|
name: Final[str]
|
100
199
|
type_: Final[ChangeSetTerminal]
|
200
|
+
condition_reference: Final[TerminalValue]
|
101
201
|
properties: Final[NodeProperties]
|
102
202
|
|
103
203
|
def __init__(
|
104
204
|
self,
|
205
|
+
scope: Scope,
|
105
206
|
change_type: ChangeType,
|
106
207
|
name: str,
|
107
208
|
type_: ChangeSetTerminal,
|
209
|
+
condition_reference: TerminalValue,
|
108
210
|
properties: NodeProperties,
|
109
211
|
):
|
110
|
-
super().__init__(change_type=change_type)
|
212
|
+
super().__init__(scope=scope, change_type=change_type)
|
111
213
|
self.name = name
|
112
214
|
self.type_ = type_
|
215
|
+
self.condition_reference = condition_reference
|
113
216
|
self.properties = properties
|
114
217
|
|
115
218
|
|
116
219
|
class NodeProperties(ChangeSetNode):
|
117
220
|
properties: Final[list[NodeProperty]]
|
118
221
|
|
119
|
-
def __init__(self, change_type: ChangeType, properties: list[NodeProperty]):
|
120
|
-
super().__init__(change_type=change_type)
|
222
|
+
def __init__(self, scope: Scope, change_type: ChangeType, properties: list[NodeProperty]):
|
223
|
+
super().__init__(scope=scope, change_type=change_type)
|
121
224
|
self.properties = properties
|
122
225
|
|
123
226
|
|
@@ -125,8 +228,8 @@ class NodeProperty(ChangeSetNode):
|
|
125
228
|
name: Final[str]
|
126
229
|
value: Final[ChangeSetEntity]
|
127
230
|
|
128
|
-
def __init__(self, change_type: ChangeType, name: str, value: ChangeSetEntity):
|
129
|
-
super().__init__(change_type=change_type)
|
231
|
+
def __init__(self, scope: Scope, change_type: ChangeType, name: str, value: ChangeSetEntity):
|
232
|
+
super().__init__(scope=scope, change_type=change_type)
|
130
233
|
self.name = name
|
131
234
|
self.value = value
|
132
235
|
|
@@ -136,9 +239,13 @@ class NodeIntrinsicFunction(ChangeSetNode):
|
|
136
239
|
arguments: Final[ChangeSetEntity]
|
137
240
|
|
138
241
|
def __init__(
|
139
|
-
self,
|
242
|
+
self,
|
243
|
+
scope: Scope,
|
244
|
+
change_type: ChangeType,
|
245
|
+
intrinsic_function: str,
|
246
|
+
arguments: ChangeSetEntity,
|
140
247
|
):
|
141
|
-
super().__init__(change_type=change_type)
|
248
|
+
super().__init__(scope=scope, change_type=change_type)
|
142
249
|
self.intrinsic_function = intrinsic_function
|
143
250
|
self.arguments = arguments
|
144
251
|
|
@@ -146,56 +253,63 @@ class NodeIntrinsicFunction(ChangeSetNode):
|
|
146
253
|
class NodeObject(ChangeSetNode):
|
147
254
|
bindings: Final[dict[str, ChangeSetEntity]]
|
148
255
|
|
149
|
-
def __init__(self, change_type: ChangeType, bindings: dict[str, ChangeSetEntity]):
|
150
|
-
super().__init__(change_type=change_type)
|
256
|
+
def __init__(self, scope: Scope, change_type: ChangeType, bindings: dict[str, ChangeSetEntity]):
|
257
|
+
super().__init__(scope=scope, change_type=change_type)
|
151
258
|
self.bindings = bindings
|
152
259
|
|
153
260
|
|
154
261
|
class NodeArray(ChangeSetNode):
|
155
262
|
array: Final[list[ChangeSetEntity]]
|
156
263
|
|
157
|
-
def __init__(self, change_type: ChangeType, array: list[ChangeSetEntity]):
|
158
|
-
super().__init__(change_type=change_type)
|
264
|
+
def __init__(self, scope: Scope, change_type: ChangeType, array: list[ChangeSetEntity]):
|
265
|
+
super().__init__(scope=scope, change_type=change_type)
|
159
266
|
self.array = array
|
160
267
|
|
161
268
|
|
162
269
|
class TerminalValue(ChangeSetTerminal, abc.ABC):
|
163
270
|
value: Final[Any]
|
164
271
|
|
165
|
-
def __init__(self, change_type: ChangeType, value: Any):
|
166
|
-
super().__init__(change_type=change_type)
|
272
|
+
def __init__(self, scope: Scope, change_type: ChangeType, value: Any):
|
273
|
+
super().__init__(scope=scope, change_type=change_type)
|
167
274
|
self.value = value
|
168
275
|
|
169
276
|
|
170
277
|
class TerminalValueModified(TerminalValue):
|
171
278
|
modified_value: Final[Any]
|
172
279
|
|
173
|
-
def __init__(self, value: Any, modified_value: Any):
|
174
|
-
super().__init__(change_type=ChangeType.MODIFIED, value=value)
|
280
|
+
def __init__(self, scope: Scope, value: Any, modified_value: Any):
|
281
|
+
super().__init__(scope=scope, change_type=ChangeType.MODIFIED, value=value)
|
175
282
|
self.modified_value = modified_value
|
176
283
|
|
177
284
|
|
178
285
|
class TerminalValueCreated(TerminalValue):
|
179
|
-
def __init__(self, value: Any):
|
180
|
-
super().__init__(change_type=ChangeType.CREATED, value=value)
|
286
|
+
def __init__(self, scope: Scope, value: Any):
|
287
|
+
super().__init__(scope=scope, change_type=ChangeType.CREATED, value=value)
|
181
288
|
|
182
289
|
|
183
290
|
class TerminalValueRemoved(TerminalValue):
|
184
|
-
def __init__(self, value: Any):
|
185
|
-
super().__init__(change_type=ChangeType.REMOVED, value=value)
|
291
|
+
def __init__(self, scope: Scope, value: Any):
|
292
|
+
super().__init__(scope=scope, change_type=ChangeType.REMOVED, value=value)
|
186
293
|
|
187
294
|
|
188
295
|
class TerminalValueUnchanged(TerminalValue):
|
189
|
-
def __init__(self, value: Any):
|
190
|
-
super().__init__(change_type=ChangeType.UNCHANGED, value=value)
|
296
|
+
def __init__(self, scope: Scope, value: Any):
|
297
|
+
super().__init__(scope=scope, change_type=ChangeType.UNCHANGED, value=value)
|
191
298
|
|
192
299
|
|
300
|
+
TypeKey: Final[str] = "Type"
|
301
|
+
ConditionKey: Final[str] = "Condition"
|
302
|
+
ConditionsKey: Final[str] = "Conditions"
|
193
303
|
ResourcesKey: Final[str] = "Resources"
|
194
304
|
PropertiesKey: Final[str] = "Properties"
|
195
|
-
|
305
|
+
ParametersKey: Final[str] = "Parameters"
|
196
306
|
# TODO: expand intrinsic functions set.
|
307
|
+
RefKey: Final[str] = "Ref"
|
308
|
+
FnIf: Final[str] = "Fn::If"
|
309
|
+
FnNot: Final[str] = "Fn::Not"
|
197
310
|
FnGetAttKey: Final[str] = "Fn::GetAtt"
|
198
|
-
|
311
|
+
FnEqualsKey: Final[str] = "Fn::Equals"
|
312
|
+
INTRINSIC_FUNCTIONS: Final[set[str]] = {RefKey, FnIf, FnNot, FnEqualsKey, FnGetAttKey}
|
199
313
|
|
200
314
|
|
201
315
|
class ChangeSetModel:
|
@@ -208,14 +322,23 @@ class ChangeSetModel:
|
|
208
322
|
|
209
323
|
_before_template: Final[Maybe[dict]]
|
210
324
|
_after_template: Final[Maybe[dict]]
|
211
|
-
|
212
|
-
|
325
|
+
_before_parameters: Final[Maybe[dict]]
|
326
|
+
_after_parameters: Final[Maybe[dict]]
|
327
|
+
_visited_scopes: Final[dict[str, ChangeSetEntity]]
|
213
328
|
_node_template: Final[NodeTemplate]
|
214
329
|
|
215
|
-
def __init__(
|
330
|
+
def __init__(
|
331
|
+
self,
|
332
|
+
before_template: Optional[dict],
|
333
|
+
after_template: Optional[dict],
|
334
|
+
before_parameters: Optional[dict],
|
335
|
+
after_parameters: Optional[dict],
|
336
|
+
):
|
216
337
|
self._before_template = before_template or Nothing
|
217
338
|
self._after_template = after_template or Nothing
|
218
|
-
self.
|
339
|
+
self._before_parameters = before_parameters or Nothing
|
340
|
+
self._after_parameters = after_parameters or Nothing
|
341
|
+
self._visited_scopes = dict()
|
219
342
|
self._node_template = self._model(
|
220
343
|
before_template=self._before_template, after_template=self._after_template
|
221
344
|
)
|
@@ -226,21 +349,37 @@ class ChangeSetModel:
|
|
226
349
|
return self._node_template
|
227
350
|
|
228
351
|
def _visit_terminal_value(
|
229
|
-
self, before_value: Maybe[Any], after_value: Maybe[Any]
|
352
|
+
self, scope: Scope, before_value: Maybe[Any], after_value: Maybe[Any]
|
230
353
|
) -> TerminalValue:
|
354
|
+
terminal_value = self._visited_scopes.get(scope)
|
355
|
+
if isinstance(terminal_value, TerminalValue):
|
356
|
+
return terminal_value
|
231
357
|
if self._is_created(before=before_value, after=after_value):
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
358
|
+
terminal_value = TerminalValueCreated(scope=scope, value=after_value)
|
359
|
+
elif self._is_removed(before=before_value, after=after_value):
|
360
|
+
terminal_value = TerminalValueRemoved(scope=scope, value=before_value)
|
361
|
+
elif before_value == after_value:
|
362
|
+
terminal_value = TerminalValueUnchanged(scope=scope, value=before_value)
|
363
|
+
else:
|
364
|
+
terminal_value = TerminalValueModified(
|
365
|
+
scope=scope, value=before_value, modified_value=after_value
|
366
|
+
)
|
367
|
+
self._visited_scopes[scope] = terminal_value
|
368
|
+
return terminal_value
|
238
369
|
|
239
370
|
def _visit_intrinsic_function(
|
240
|
-
self,
|
371
|
+
self,
|
372
|
+
scope: Scope,
|
373
|
+
intrinsic_function: str,
|
374
|
+
before_arguments: Maybe[Any],
|
375
|
+
after_arguments: Maybe[Any],
|
241
376
|
) -> NodeIntrinsicFunction:
|
242
|
-
|
243
|
-
|
377
|
+
node_intrinsic_function = self._visited_scopes.get(scope)
|
378
|
+
if isinstance(node_intrinsic_function, NodeIntrinsicFunction):
|
379
|
+
return node_intrinsic_function
|
380
|
+
arguments = self._visit_value(
|
381
|
+
scope=scope, before_value=before_arguments, after_value=after_arguments
|
382
|
+
)
|
244
383
|
if self._is_created(before=before_arguments, after=after_arguments):
|
245
384
|
change_type = ChangeType.CREATED
|
246
385
|
elif self._is_removed(before=before_arguments, after=after_arguments):
|
@@ -248,13 +387,20 @@ class ChangeSetModel:
|
|
248
387
|
else:
|
249
388
|
function_name = intrinsic_function.replace("::", "_")
|
250
389
|
function_name = camel_to_snake_case(function_name)
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
390
|
+
resolve_function_name = f"_resolve_intrinsic_function_{function_name}"
|
391
|
+
if hasattr(self, resolve_function_name):
|
392
|
+
resolve_function = getattr(self, resolve_function_name)
|
393
|
+
change_type = resolve_function(arguments)
|
394
|
+
else:
|
395
|
+
change_type = arguments.change_type
|
396
|
+
node_intrinsic_function = NodeIntrinsicFunction(
|
397
|
+
scope=scope,
|
398
|
+
change_type=change_type,
|
399
|
+
intrinsic_function=intrinsic_function,
|
400
|
+
arguments=arguments,
|
257
401
|
)
|
402
|
+
self._visited_scopes[scope] = node_intrinsic_function
|
403
|
+
return node_intrinsic_function
|
258
404
|
|
259
405
|
def _resolve_intrinsic_function_fn_get_att(self, arguments: ChangeSetEntity) -> ChangeType:
|
260
406
|
# TODO: add support for nested intrinsic functions.
|
@@ -288,119 +434,217 @@ class ChangeSetModel:
|
|
288
434
|
|
289
435
|
return ChangeType.UNCHANGED
|
290
436
|
|
291
|
-
def
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
437
|
+
def _resolve_intrinsic_function_ref(self, arguments: ChangeSetEntity) -> ChangeType:
|
438
|
+
if arguments.change_type != ChangeType.UNCHANGED:
|
439
|
+
return arguments.change_type
|
440
|
+
# TODO: add support for nested functions, here we assume the argument is a logicalID.
|
441
|
+
if not isinstance(arguments, TerminalValue):
|
442
|
+
return arguments.change_type
|
443
|
+
|
444
|
+
logical_id = arguments.value
|
445
|
+
|
446
|
+
node_condition = self._retrieve_condition_if_exists(condition_name=logical_id)
|
447
|
+
if isinstance(node_condition, NodeCondition):
|
448
|
+
return node_condition.change_type
|
449
|
+
|
450
|
+
node_parameter = self._retrieve_parameter_if_exists(parameter_name=logical_id)
|
451
|
+
if isinstance(node_parameter, NodeParameter):
|
452
|
+
return node_parameter.dynamic_value.change_type
|
453
|
+
|
454
|
+
# TODO: this should check the replacement flag for a resource update.
|
455
|
+
node_resource = self._retrieve_or_visit_resource(resource_name=logical_id)
|
456
|
+
return node_resource.change_type
|
457
|
+
|
458
|
+
def _resolve_intrinsic_function_fn_if(self, arguments: ChangeSetEntity) -> ChangeType:
|
459
|
+
# TODO: validate arguments structure and type.
|
460
|
+
if not isinstance(arguments, NodeArray) or not arguments.array:
|
461
|
+
raise RuntimeError()
|
462
|
+
logical_name_of_condition_entity = arguments.array[0]
|
463
|
+
if not isinstance(logical_name_of_condition_entity, TerminalValue):
|
464
|
+
raise RuntimeError()
|
465
|
+
logical_name_of_condition: str = logical_name_of_condition_entity.value
|
466
|
+
if not isinstance(logical_name_of_condition, str):
|
467
|
+
raise RuntimeError()
|
468
|
+
|
469
|
+
node_condition = self._retrieve_condition_if_exists(
|
470
|
+
condition_name=logical_name_of_condition
|
302
471
|
)
|
472
|
+
if not isinstance(node_condition, NodeCondition):
|
473
|
+
raise RuntimeError()
|
474
|
+
change_types = [node_condition.change_type, *arguments.array[1:]]
|
475
|
+
change_type = self._change_type_for_parent_of(change_types=change_types)
|
476
|
+
return change_type
|
303
477
|
|
304
|
-
def _visit_array(
|
478
|
+
def _visit_array(
|
479
|
+
self, scope: Scope, before_array: Maybe[list], after_array: Maybe[list]
|
480
|
+
) -> NodeArray:
|
305
481
|
change_type = ChangeType.UNCHANGED
|
306
482
|
array: list[ChangeSetEntity] = list()
|
307
|
-
for before_value, after_value in
|
308
|
-
|
483
|
+
for index, (before_value, after_value) in enumerate(
|
484
|
+
zip_longest(before_array, after_array, fillvalue=Nothing)
|
485
|
+
):
|
486
|
+
# TODO: should extract this scoping logic.
|
487
|
+
value_scope = scope.open_index(index=index)
|
488
|
+
value = self._visit_value(
|
489
|
+
scope=value_scope, before_value=before_value, after_value=after_value
|
490
|
+
)
|
309
491
|
array.append(value)
|
310
492
|
if value.change_type != ChangeType.UNCHANGED:
|
311
493
|
change_type = ChangeType.MODIFIED
|
312
|
-
return NodeArray(change_type=change_type, array=array)
|
494
|
+
return NodeArray(scope=scope, change_type=change_type, array=array)
|
495
|
+
|
496
|
+
def _visit_object(
|
497
|
+
self, scope: Scope, before_object: Maybe[dict], after_object: Maybe[dict]
|
498
|
+
) -> NodeObject:
|
499
|
+
node_object = self._visited_scopes.get(scope)
|
500
|
+
if isinstance(node_object, NodeObject):
|
501
|
+
return node_object
|
313
502
|
|
314
|
-
def _visit_object(self, before_object: Maybe[dict], after_object: Maybe[dict]) -> NodeObject:
|
315
503
|
change_type = ChangeType.UNCHANGED
|
316
|
-
binding_names = self.
|
504
|
+
binding_names = self._safe_keys_of(before_object, after_object)
|
317
505
|
bindings: dict[str, ChangeSetEntity] = dict()
|
318
506
|
for binding_name in binding_names:
|
319
|
-
before_value, after_value = self.
|
507
|
+
binding_scope, (before_value, after_value) = self._safe_access_in(
|
508
|
+
scope, binding_name, before_object, after_object
|
509
|
+
)
|
320
510
|
if self._is_intrinsic_function_name(function_name=binding_name):
|
321
511
|
value = self._visit_intrinsic_function(
|
512
|
+
scope=binding_scope,
|
322
513
|
intrinsic_function=binding_name,
|
323
514
|
before_arguments=before_value,
|
324
515
|
after_arguments=after_value,
|
325
516
|
)
|
326
517
|
else:
|
327
|
-
value = self._visit_value(
|
518
|
+
value = self._visit_value(
|
519
|
+
scope=binding_scope, before_value=before_value, after_value=after_value
|
520
|
+
)
|
328
521
|
bindings[binding_name] = value
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
522
|
+
change_type = change_type.for_child(value.change_type)
|
523
|
+
node_object = NodeObject(scope=scope, change_type=change_type, bindings=bindings)
|
524
|
+
self._visited_scopes[scope] = node_object
|
525
|
+
return node_object
|
526
|
+
|
527
|
+
def _visit_divergence(
|
528
|
+
self, scope: Scope, before_value: Maybe[Any], after_value: Maybe[Any]
|
529
|
+
) -> NodeDivergence:
|
530
|
+
scope_value = scope.open_scope("value")
|
531
|
+
value = self._visit_value(scope=scope_value, before_value=before_value, after_value=Nothing)
|
532
|
+
scope_divergence = scope.open_scope("divergence")
|
533
|
+
divergence = self._visit_value(
|
534
|
+
scope=scope_divergence, before_value=Nothing, after_value=after_value
|
535
|
+
)
|
536
|
+
return NodeDivergence(scope=scope, value=value, divergence=divergence)
|
341
537
|
|
342
|
-
|
343
|
-
|
344
|
-
|
538
|
+
def _visit_value(
|
539
|
+
self, scope: Scope, before_value: Maybe[Any], after_value: Maybe[Any]
|
540
|
+
) -> ChangeSetEntity:
|
541
|
+
value = self._visited_scopes.get(scope)
|
542
|
+
if isinstance(value, ChangeSetEntity):
|
543
|
+
return value
|
544
|
+
unset = object()
|
545
|
+
if type(before_value) is type(after_value):
|
546
|
+
dominant_value = before_value
|
547
|
+
elif self._is_created(before=before_value, after=after_value):
|
548
|
+
dominant_value = after_value
|
549
|
+
elif self._is_removed(before=before_value, after=after_value):
|
550
|
+
dominant_value = before_value
|
551
|
+
else:
|
552
|
+
dominant_value = unset
|
553
|
+
if dominant_value is not unset:
|
554
|
+
if self._is_terminal(value=dominant_value):
|
345
555
|
value = self._visit_terminal_value(
|
346
|
-
before_value=before_value, after_value=after_value
|
556
|
+
scope=scope, before_value=before_value, after_value=after_value
|
557
|
+
)
|
558
|
+
elif self._is_object(value=dominant_value):
|
559
|
+
value = self._visit_object(
|
560
|
+
scope=scope, before_object=before_value, after_object=after_value
|
561
|
+
)
|
562
|
+
elif self._is_array(value=dominant_value):
|
563
|
+
value = self._visit_array(
|
564
|
+
scope=scope, before_array=before_value, after_array=after_value
|
347
565
|
)
|
348
|
-
elif self._is_object(value=before_value):
|
349
|
-
value = self._visit_object(before_object=before_value, after_object=after_value)
|
350
|
-
elif self._is_array(value=before_value):
|
351
|
-
value = self._visit_array(before_array=before_value, after_array=after_value)
|
352
566
|
else:
|
353
|
-
raise RuntimeError(f"Unsupported type {
|
354
|
-
|
355
|
-
# Case: update to new type.
|
567
|
+
raise RuntimeError(f"Unsupported type {type(dominant_value)}")
|
568
|
+
# Case: type divergence.
|
356
569
|
else:
|
357
|
-
|
570
|
+
value = self._visit_divergence(
|
571
|
+
scope=scope, before_value=before_value, after_value=after_value
|
572
|
+
)
|
573
|
+
self._visited_scopes[scope] = value
|
574
|
+
return value
|
358
575
|
|
359
576
|
def _visit_property(
|
360
|
-
self,
|
577
|
+
self,
|
578
|
+
scope: Scope,
|
579
|
+
property_name: str,
|
580
|
+
before_property: Maybe[Any],
|
581
|
+
after_property: Maybe[Any],
|
361
582
|
) -> NodeProperty:
|
583
|
+
node_property = self._visited_scopes.get(scope)
|
584
|
+
if isinstance(node_property, NodeProperty):
|
585
|
+
return node_property
|
586
|
+
|
362
587
|
if self._is_created(before=before_property, after=after_property):
|
363
|
-
|
588
|
+
node_property = NodeProperty(
|
589
|
+
scope=scope,
|
364
590
|
change_type=ChangeType.CREATED,
|
365
591
|
name=property_name,
|
366
|
-
value=TerminalValueCreated(value=after_property),
|
592
|
+
value=TerminalValueCreated(scope=scope, value=after_property),
|
367
593
|
)
|
368
|
-
|
369
|
-
|
594
|
+
elif self._is_removed(before=before_property, after=after_property):
|
595
|
+
node_property = NodeProperty(
|
596
|
+
scope=scope,
|
370
597
|
change_type=ChangeType.REMOVED,
|
371
598
|
name=property_name,
|
372
|
-
value=TerminalValueRemoved(value=before_property),
|
599
|
+
value=TerminalValueRemoved(scope=scope, value=before_property),
|
600
|
+
)
|
601
|
+
else:
|
602
|
+
value = self._visit_value(
|
603
|
+
scope=scope, before_value=before_property, after_value=after_property
|
604
|
+
)
|
605
|
+
node_property = NodeProperty(
|
606
|
+
scope=scope, change_type=value.change_type, name=property_name, value=value
|
373
607
|
)
|
374
|
-
|
375
|
-
return
|
608
|
+
self._visited_scopes[scope] = node_property
|
609
|
+
return node_property
|
376
610
|
|
377
611
|
def _visit_properties(
|
378
|
-
self, before_properties: Maybe[dict], after_properties: Maybe[dict]
|
612
|
+
self, scope: Scope, before_properties: Maybe[dict], after_properties: Maybe[dict]
|
379
613
|
) -> NodeProperties:
|
614
|
+
node_properties = self._visited_scopes.get(scope)
|
615
|
+
if isinstance(node_properties, NodeProperties):
|
616
|
+
return node_properties
|
380
617
|
# TODO: double check we are sure not to have this be a NodeObject
|
381
|
-
property_names:
|
618
|
+
property_names: list[str] = self._safe_keys_of(before_properties, after_properties)
|
382
619
|
properties: list[NodeProperty] = list()
|
383
620
|
change_type = ChangeType.UNCHANGED
|
384
621
|
for property_name in property_names:
|
385
|
-
before_property, after_property = self.
|
386
|
-
property_name, before_properties, after_properties
|
622
|
+
property_scope, (before_property, after_property) = self._safe_access_in(
|
623
|
+
scope, property_name, before_properties, after_properties
|
387
624
|
)
|
388
625
|
property_ = self._visit_property(
|
626
|
+
scope=property_scope,
|
389
627
|
property_name=property_name,
|
390
628
|
before_property=before_property,
|
391
629
|
after_property=after_property,
|
392
630
|
)
|
393
631
|
properties.append(property_)
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
632
|
+
change_type = change_type.for_child(property_.change_type)
|
633
|
+
node_properties = NodeProperties(
|
634
|
+
scope=scope, change_type=change_type, properties=properties
|
635
|
+
)
|
636
|
+
self._visited_scopes[scope] = node_properties
|
637
|
+
return node_properties
|
398
638
|
|
399
639
|
def _visit_resource(
|
400
|
-
self,
|
640
|
+
self,
|
641
|
+
scope: Scope,
|
642
|
+
resource_name: str,
|
643
|
+
before_resource: Maybe[dict],
|
644
|
+
after_resource: Maybe[dict],
|
401
645
|
) -> NodeResource:
|
402
|
-
node_resource = self.
|
403
|
-
if node_resource
|
646
|
+
node_resource = self._visited_scopes.get(scope)
|
647
|
+
if isinstance(node_resource, NodeResource):
|
404
648
|
return node_resource
|
405
649
|
|
406
650
|
if self._is_created(before=before_resource, after=after_resource):
|
@@ -411,68 +655,294 @@ class ChangeSetModel:
|
|
411
655
|
change_type = ChangeType.UNCHANGED
|
412
656
|
|
413
657
|
# TODO: investigate behaviour with type changes, for now this is filler code.
|
414
|
-
type_str = self.
|
658
|
+
_, type_str = self._safe_access_in(scope, TypeKey, before_resource)
|
415
659
|
|
416
|
-
|
417
|
-
|
660
|
+
scope_condition, (before_condition, after_condition) = self._safe_access_in(
|
661
|
+
scope, ConditionKey, before_resource, after_resource
|
418
662
|
)
|
419
|
-
|
420
|
-
|
663
|
+
condition_reference = self._visit_terminal_value(
|
664
|
+
scope_condition, before_condition, after_condition
|
421
665
|
)
|
422
666
|
|
423
|
-
|
424
|
-
|
425
|
-
|
667
|
+
scope_properties, (before_properties, after_properties) = self._safe_access_in(
|
668
|
+
scope, PropertiesKey, before_resource, after_resource
|
669
|
+
)
|
670
|
+
properties = self._visit_properties(
|
671
|
+
scope=scope_properties,
|
672
|
+
before_properties=before_properties,
|
673
|
+
after_properties=after_properties,
|
674
|
+
)
|
675
|
+
change_type = change_type.for_child(properties.change_type)
|
426
676
|
node_resource = NodeResource(
|
677
|
+
scope=scope,
|
427
678
|
change_type=change_type,
|
428
679
|
name=resource_name,
|
429
|
-
type_=TerminalValueUnchanged(value=type_str),
|
680
|
+
type_=TerminalValueUnchanged(scope=scope, value=type_str),
|
681
|
+
condition_reference=condition_reference,
|
430
682
|
properties=properties,
|
431
683
|
)
|
432
|
-
self.
|
684
|
+
self._visited_scopes[scope] = node_resource
|
433
685
|
return node_resource
|
434
686
|
|
435
687
|
def _visit_resources(
|
436
|
-
self, before_resources: Maybe[dict], after_resources: Maybe[dict]
|
688
|
+
self, scope: Scope, before_resources: Maybe[dict], after_resources: Maybe[dict]
|
437
689
|
) -> NodeResources:
|
438
690
|
# TODO: investigate type changes behavior.
|
439
691
|
change_type = ChangeType.UNCHANGED
|
440
692
|
resources: list[NodeResource] = list()
|
441
|
-
resource_names = self.
|
693
|
+
resource_names = self._safe_keys_of(before_resources, after_resources)
|
442
694
|
for resource_name in resource_names:
|
443
|
-
before_resource, after_resource = self.
|
444
|
-
resource_name, before_resources, after_resources
|
695
|
+
resource_scope, (before_resource, after_resource) = self._safe_access_in(
|
696
|
+
scope, resource_name, before_resources, after_resources
|
445
697
|
)
|
446
698
|
resource = self._visit_resource(
|
699
|
+
scope=resource_scope,
|
447
700
|
resource_name=resource_name,
|
448
701
|
before_resource=before_resource,
|
449
702
|
after_resource=after_resource,
|
450
703
|
)
|
451
704
|
resources.append(resource)
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
705
|
+
change_type = change_type.for_child(resource.change_type)
|
706
|
+
return NodeResources(scope=scope, change_type=change_type, resources=resources)
|
707
|
+
|
708
|
+
def _visit_dynamic_parameter(self, parameter_name: str) -> ChangeSetEntity:
|
709
|
+
scope = Scope("Dynamic").open_scope("Parameters")
|
710
|
+
scope_parameter, (before_parameter, after_parameter) = self._safe_access_in(
|
711
|
+
scope, parameter_name, self._before_parameters, self._after_parameters
|
712
|
+
)
|
713
|
+
parameter = self._visit_value(
|
714
|
+
scope=scope_parameter, before_value=before_parameter, after_value=after_parameter
|
715
|
+
)
|
716
|
+
return parameter
|
717
|
+
|
718
|
+
def _visit_parameter(
|
719
|
+
self,
|
720
|
+
scope: Scope,
|
721
|
+
parameter_name: str,
|
722
|
+
before_parameter: Maybe[dict],
|
723
|
+
after_parameter: Maybe[dict],
|
724
|
+
) -> NodeParameter:
|
725
|
+
node_parameter = self._visited_scopes.get(scope)
|
726
|
+
if isinstance(node_parameter, NodeParameter):
|
727
|
+
return node_parameter
|
728
|
+
# TODO: add logic to compute defaults already in the graph building process?
|
729
|
+
dynamic_value = self._visit_dynamic_parameter(parameter_name=parameter_name)
|
730
|
+
if self._is_created(before=before_parameter, after=after_parameter):
|
731
|
+
node_parameter = NodeParameter(
|
732
|
+
scope=scope,
|
733
|
+
change_type=ChangeType.CREATED,
|
734
|
+
name=parameter_name,
|
735
|
+
value=TerminalValueCreated(scope=scope, value=after_parameter),
|
736
|
+
dynamic_value=dynamic_value,
|
737
|
+
)
|
738
|
+
elif self._is_removed(before=before_parameter, after=after_parameter):
|
739
|
+
node_parameter = NodeParameter(
|
740
|
+
scope=scope,
|
741
|
+
change_type=ChangeType.REMOVED,
|
742
|
+
name=parameter_name,
|
743
|
+
value=TerminalValueRemoved(scope=scope, value=before_parameter),
|
744
|
+
dynamic_value=dynamic_value,
|
745
|
+
)
|
746
|
+
else:
|
747
|
+
value = self._visit_value(
|
748
|
+
scope=scope, before_value=before_parameter, after_value=after_parameter
|
749
|
+
)
|
750
|
+
change_type = self._change_type_for_parent_of(
|
751
|
+
change_types=[dynamic_value.change_type, value.change_type]
|
752
|
+
)
|
753
|
+
node_parameter = NodeParameter(
|
754
|
+
scope=scope,
|
755
|
+
change_type=change_type,
|
756
|
+
name=parameter_name,
|
757
|
+
value=value,
|
758
|
+
dynamic_value=dynamic_value,
|
759
|
+
)
|
760
|
+
self._visited_scopes[scope] = node_parameter
|
761
|
+
return node_parameter
|
762
|
+
|
763
|
+
def _visit_parameters(
|
764
|
+
self, scope: Scope, before_parameters: Maybe[dict], after_parameters: Maybe[dict]
|
765
|
+
) -> NodeParameters:
|
766
|
+
node_parameters = self._visited_scopes.get(scope)
|
767
|
+
if isinstance(node_parameters, NodeParameters):
|
768
|
+
return node_parameters
|
769
|
+
parameter_names: list[str] = self._safe_keys_of(before_parameters, after_parameters)
|
770
|
+
parameters: list[NodeParameter] = list()
|
771
|
+
change_type = ChangeType.UNCHANGED
|
772
|
+
for parameter_name in parameter_names:
|
773
|
+
parameter_scope, (before_parameter, after_parameter) = self._safe_access_in(
|
774
|
+
scope, parameter_name, before_parameters, after_parameters
|
775
|
+
)
|
776
|
+
parameter = self._visit_parameter(
|
777
|
+
scope=parameter_scope,
|
778
|
+
parameter_name=parameter_name,
|
779
|
+
before_parameter=before_parameter,
|
780
|
+
after_parameter=after_parameter,
|
781
|
+
)
|
782
|
+
parameters.append(parameter)
|
783
|
+
change_type = change_type.for_child(parameter.change_type)
|
784
|
+
node_parameters = NodeParameters(
|
785
|
+
scope=scope, change_type=change_type, parameters=parameters
|
786
|
+
)
|
787
|
+
self._visited_scopes[scope] = node_parameters
|
788
|
+
return node_parameters
|
789
|
+
|
790
|
+
def _visit_condition(
|
791
|
+
self,
|
792
|
+
scope: Scope,
|
793
|
+
condition_name: str,
|
794
|
+
before_condition: Maybe[dict],
|
795
|
+
after_condition: Maybe[dict],
|
796
|
+
) -> NodeCondition:
|
797
|
+
node_condition = self._visited_scopes.get(scope)
|
798
|
+
if isinstance(node_condition, NodeCondition):
|
799
|
+
return node_condition
|
800
|
+
|
801
|
+
# TODO: is schema validation/check necessary or can we trust the input at this point?
|
802
|
+
function_names: list[str] = self._safe_keys_of(before_condition, after_condition)
|
803
|
+
if len(function_names) == 1:
|
804
|
+
body = self._visit_object(
|
805
|
+
scope=scope, before_object=before_condition, after_object=after_condition
|
806
|
+
)
|
807
|
+
else:
|
808
|
+
body = self._visit_divergence(
|
809
|
+
scope=scope, before_value=before_condition, after_value=after_condition
|
810
|
+
)
|
811
|
+
|
812
|
+
node_condition = NodeCondition(
|
813
|
+
scope=scope, change_type=body.change_type, name=condition_name, body=body
|
814
|
+
)
|
815
|
+
self._visited_scopes[scope] = node_condition
|
816
|
+
return node_condition
|
817
|
+
|
818
|
+
def _visit_conditions(
|
819
|
+
self, scope: Scope, before_conditions: Maybe[dict], after_conditions: Maybe[dict]
|
820
|
+
) -> NodeConditions:
|
821
|
+
node_conditions = self._visited_scopes.get(scope)
|
822
|
+
if isinstance(node_conditions, NodeConditions):
|
823
|
+
return node_conditions
|
824
|
+
condition_names: list[str] = self._safe_keys_of(before_conditions, after_conditions)
|
825
|
+
conditions: list[NodeCondition] = list()
|
826
|
+
change_type = ChangeType.UNCHANGED
|
827
|
+
for condition_name in condition_names:
|
828
|
+
condition_scope, (before_condition, after_condition) = self._safe_access_in(
|
829
|
+
scope, condition_name, before_conditions, after_conditions
|
830
|
+
)
|
831
|
+
condition = self._visit_condition(
|
832
|
+
scope=condition_scope,
|
833
|
+
condition_name=condition_name,
|
834
|
+
before_condition=before_condition,
|
835
|
+
after_condition=after_condition,
|
836
|
+
)
|
837
|
+
conditions.append(condition)
|
838
|
+
change_type = change_type.for_child(child_change_type=condition.change_type)
|
839
|
+
node_conditions = NodeConditions(
|
840
|
+
scope=scope, change_type=change_type, conditions=conditions
|
841
|
+
)
|
842
|
+
self._visited_scopes[scope] = node_conditions
|
843
|
+
return node_conditions
|
456
844
|
|
457
845
|
def _model(self, before_template: Maybe[dict], after_template: Maybe[dict]) -> NodeTemplate:
|
846
|
+
root_scope = Scope()
|
458
847
|
# TODO: visit other child types
|
459
|
-
|
460
|
-
|
848
|
+
parameters_scope, (before_parameters, after_parameters) = self._safe_access_in(
|
849
|
+
root_scope, ParametersKey, before_template, after_template
|
850
|
+
)
|
851
|
+
parameters = self._visit_parameters(
|
852
|
+
scope=parameters_scope,
|
853
|
+
before_parameters=before_parameters,
|
854
|
+
after_parameters=after_parameters,
|
855
|
+
)
|
856
|
+
|
857
|
+
conditions_scope, (before_conditions, after_conditions) = self._safe_access_in(
|
858
|
+
root_scope, ConditionsKey, before_template, after_template
|
859
|
+
)
|
860
|
+
conditions = self._visit_conditions(
|
861
|
+
scope=conditions_scope,
|
862
|
+
before_conditions=before_conditions,
|
863
|
+
after_conditions=after_conditions,
|
864
|
+
)
|
865
|
+
|
866
|
+
resources_scope, (before_resources, after_resources) = self._safe_access_in(
|
867
|
+
root_scope, ResourcesKey, before_template, after_template
|
461
868
|
)
|
462
869
|
resources = self._visit_resources(
|
463
|
-
|
870
|
+
scope=resources_scope,
|
871
|
+
before_resources=before_resources,
|
872
|
+
after_resources=after_resources,
|
873
|
+
)
|
874
|
+
|
875
|
+
# TODO: compute the change_type of the template properly.
|
876
|
+
return NodeTemplate(
|
877
|
+
scope=root_scope,
|
878
|
+
change_type=resources.change_type,
|
879
|
+
parameters=parameters,
|
880
|
+
conditions=conditions,
|
881
|
+
resources=resources,
|
882
|
+
)
|
883
|
+
|
884
|
+
def _retrieve_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]:
|
885
|
+
conditions_scope, (before_conditions, after_conditions) = self._safe_access_in(
|
886
|
+
Scope(), ConditionsKey, self._before_template, self._after_template
|
887
|
+
)
|
888
|
+
before_conditions = before_conditions or dict()
|
889
|
+
after_conditions = after_conditions or dict()
|
890
|
+
if condition_name in before_conditions or condition_name in after_conditions:
|
891
|
+
condition_scope, (before_condition, after_condition) = self._safe_access_in(
|
892
|
+
conditions_scope, condition_name, before_conditions, after_conditions
|
893
|
+
)
|
894
|
+
node_condition = self._visit_condition(
|
895
|
+
conditions_scope,
|
896
|
+
condition_name,
|
897
|
+
before_condition=before_condition,
|
898
|
+
after_condition=after_condition,
|
899
|
+
)
|
900
|
+
return node_condition
|
901
|
+
return None
|
902
|
+
|
903
|
+
def _retrieve_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]:
|
904
|
+
parameters_scope, (before_parameters, after_parameters) = self._safe_access_in(
|
905
|
+
Scope(), ParametersKey, self._before_template, self._after_template
|
906
|
+
)
|
907
|
+
before_parameters = before_parameters or dict()
|
908
|
+
after_parameters = after_parameters or dict()
|
909
|
+
if parameter_name in before_parameters or parameter_name in after_parameters:
|
910
|
+
parameter_scope, (before_parameter, after_parameter) = self._safe_access_in(
|
911
|
+
parameters_scope, parameter_name, before_parameters, after_parameters
|
912
|
+
)
|
913
|
+
node_parameter = self._visit_parameter(
|
914
|
+
parameters_scope,
|
915
|
+
parameter_name,
|
916
|
+
before_parameter=before_parameter,
|
917
|
+
after_parameter=after_parameter,
|
918
|
+
)
|
919
|
+
return node_parameter
|
920
|
+
return None
|
921
|
+
|
922
|
+
def _retrieve_or_visit_resource(self, resource_name: str) -> NodeResource:
|
923
|
+
resources_scope, (before_resources, after_resources) = self._safe_access_in(
|
924
|
+
Scope(),
|
925
|
+
ResourcesKey,
|
926
|
+
self._before_template,
|
927
|
+
self._after_template,
|
928
|
+
)
|
929
|
+
resource_scope, (before_resource, after_resource) = self._safe_access_in(
|
930
|
+
resources_scope, resource_name, before_resources, after_resources
|
931
|
+
)
|
932
|
+
return self._visit_resource(
|
933
|
+
scope=resource_scope,
|
934
|
+
resource_name=resource_name,
|
935
|
+
before_resource=before_resource,
|
936
|
+
after_resource=after_resource,
|
464
937
|
)
|
465
|
-
# TODO: what is a change type for templates?
|
466
|
-
return NodeTemplate(change_type=resources.change_type, resources=resources)
|
467
938
|
|
468
939
|
@staticmethod
|
469
940
|
def _is_intrinsic_function_name(function_name: str) -> bool:
|
470
|
-
# TODO: expand vocabulary.
|
471
941
|
# TODO: are intrinsic functions soft keywords?
|
472
942
|
return function_name in INTRINSIC_FUNCTIONS
|
473
943
|
|
474
944
|
@staticmethod
|
475
|
-
def
|
945
|
+
def _safe_access_in(scope: Scope, key: str, *objects: Maybe[dict]) -> tuple[Scope, Maybe[Any]]:
|
476
946
|
results = list()
|
477
947
|
for obj in objects:
|
478
948
|
# TODO: raise errors if not dict
|
@@ -480,20 +950,33 @@ class ChangeSetModel:
|
|
480
950
|
results.append(obj.get(key, Nothing))
|
481
951
|
else:
|
482
952
|
results.append(obj)
|
483
|
-
|
953
|
+
new_scope = scope.open_scope(name=key)
|
954
|
+
return new_scope, results[0] if len(objects) == 1 else tuple(results)
|
484
955
|
|
485
956
|
@staticmethod
|
486
|
-
def
|
487
|
-
|
957
|
+
def _safe_keys_of(*objects: Maybe[dict]) -> list[str]:
|
958
|
+
key_set: set[str] = set()
|
488
959
|
for obj in objects:
|
489
960
|
# TODO: raise errors if not dict
|
490
961
|
if isinstance(obj, dict):
|
491
|
-
|
492
|
-
|
962
|
+
key_set.update(obj.keys())
|
963
|
+
# The keys list is sorted to increase reproducibility of the
|
964
|
+
# update graph build process or downstream logics.
|
965
|
+
keys = sorted(key_set)
|
966
|
+
return keys
|
967
|
+
|
968
|
+
@staticmethod
|
969
|
+
def _change_type_for_parent_of(change_types: list[ChangeType]) -> ChangeType:
|
970
|
+
parent_change_type = ChangeType.UNCHANGED
|
971
|
+
for child_change_type in change_types:
|
972
|
+
parent_change_type = parent_change_type.for_child(child_change_type)
|
973
|
+
if parent_change_type == ChangeType.MODIFIED:
|
974
|
+
break
|
975
|
+
return parent_change_type
|
493
976
|
|
494
977
|
@staticmethod
|
495
978
|
def _is_terminal(value: Any) -> bool:
|
496
|
-
return type(value) in {int, float, bool, str, None}
|
979
|
+
return type(value) in {int, float, bool, str, None, NothingType}
|
497
980
|
|
498
981
|
@staticmethod
|
499
982
|
def _is_object(value: Any) -> bool:
|