localstack-core 4.4.1.dev60__py3-none-any.whl → 4.4.1.dev65__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.
Files changed (21) hide show
  1. localstack/aws/api/ec2/__init__.py +70 -2
  2. localstack/aws/api/s3/__init__.py +2 -0
  3. localstack/services/cloudformation/engine/v2/change_set_model.py +106 -144
  4. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +9 -7
  5. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +7 -6
  6. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +223 -114
  7. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +10 -0
  8. localstack/services/s3/provider.py +3 -2
  9. localstack/services/sns/provider.py +27 -9
  10. localstack/version.py +2 -2
  11. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/METADATA +3 -3
  12. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/RECORD +20 -20
  13. localstack_core-4.4.1.dev65.dist-info/plux.json +1 -0
  14. localstack_core-4.4.1.dev60.dist-info/plux.json +0 -1
  15. {localstack_core-4.4.1.dev60.data → localstack_core-4.4.1.dev65.data}/scripts/localstack +0 -0
  16. {localstack_core-4.4.1.dev60.data → localstack_core-4.4.1.dev65.data}/scripts/localstack-supervisor +0 -0
  17. {localstack_core-4.4.1.dev60.data → localstack_core-4.4.1.dev65.data}/scripts/localstack.bat +0 -0
  18. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/WHEEL +0 -0
  19. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/entry_points.txt +0 -0
  20. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/licenses/LICENSE.txt +0 -0
  21. {localstack_core-4.4.1.dev60.dist-info → localstack_core-4.4.1.dev65.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,15 @@ from __future__ import annotations
3
3
  import re
4
4
  from typing import Any, Final, Generic, Optional, TypeVar
5
5
 
6
+ from localstack.services.cloudformation.engine.transformers import (
7
+ Transformer,
8
+ execute_macro,
9
+ transformers,
10
+ )
6
11
  from localstack.services.cloudformation.engine.v2.change_set_model import (
7
12
  ChangeSetEntity,
8
13
  ChangeType,
14
+ Maybe,
9
15
  NodeArray,
10
16
  NodeCondition,
11
17
  NodeDependsOn,
@@ -20,16 +26,19 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
20
26
  NodeProperty,
21
27
  NodeResource,
22
28
  NodeTemplate,
29
+ Nothing,
23
30
  Scope,
24
31
  TerminalValue,
25
32
  TerminalValueCreated,
26
33
  TerminalValueModified,
27
34
  TerminalValueRemoved,
28
35
  TerminalValueUnchanged,
36
+ is_nothing,
29
37
  )
30
38
  from localstack.services.cloudformation.engine.v2.change_set_model_visitor import (
31
39
  ChangeSetModelVisitor,
32
40
  )
41
+ from localstack.services.cloudformation.stores import get_cloudformation_store
33
42
  from localstack.services.cloudformation.v2.entities import ChangeSet
34
43
  from localstack.utils.aws.arns import get_partition
35
44
  from localstack.utils.urls import localstack_host
@@ -52,10 +61,10 @@ TAfter = TypeVar("TAfter")
52
61
 
53
62
 
54
63
  class PreprocEntityDelta(Generic[TBefore, TAfter]):
55
- before: Optional[TBefore]
56
- after: Optional[TAfter]
64
+ before: Maybe[TBefore]
65
+ after: Maybe[TAfter]
57
66
 
58
- def __init__(self, before: Optional[TBefore] = None, after: Optional[TAfter] = None):
67
+ def __init__(self, before: Maybe[TBefore] = Nothing, after: Maybe[TAfter] = Nothing):
59
68
  self.before = before
60
69
  self.after = after
61
70
 
@@ -194,7 +203,6 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
194
203
  _ = self._get_node_resource_for(
195
204
  resource_name=resource_logical_id, node_template=self._node_template
196
205
  )
197
-
198
206
  resolved_resource = resolved_resources.get(resource_logical_id)
199
207
  if resolved_resource is None:
200
208
  raise RuntimeError(
@@ -231,26 +239,25 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
231
239
  if mapping.name == map_name:
232
240
  self.visit(mapping)
233
241
  return mapping
234
- # TODO
235
- raise RuntimeError()
242
+ raise RuntimeError(f"Undefined '{map_name}' mapping")
236
243
 
237
- def _get_node_parameter_if_exists(self, parameter_name: str) -> Optional[NodeParameter]:
244
+ def _get_node_parameter_if_exists(self, parameter_name: str) -> Maybe[NodeParameter]:
238
245
  parameters: list[NodeParameter] = self._node_template.parameters.parameters
239
246
  # TODO: another scenarios suggesting property lookups might be preferable.
240
247
  for parameter in parameters:
241
248
  if parameter.name == parameter_name:
242
249
  self.visit(parameter)
243
250
  return parameter
244
- return None
251
+ return Nothing
245
252
 
246
- def _get_node_condition_if_exists(self, condition_name: str) -> Optional[NodeCondition]:
253
+ def _get_node_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondition]:
247
254
  conditions: list[NodeCondition] = self._node_template.conditions.conditions
248
255
  # TODO: another scenarios suggesting property lookups might be preferable.
249
256
  for condition in conditions:
250
257
  if condition.name == condition_name:
251
258
  self.visit(condition)
252
259
  return condition
253
- return None
260
+ return Nothing
254
261
 
255
262
  def _resolve_condition(self, logical_id: str) -> PreprocEntityDelta:
256
263
  node_condition = self._get_node_condition_if_exists(condition_name=logical_id)
@@ -274,14 +281,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
274
281
  case "AWS::URLSuffix":
275
282
  return _AWS_URL_SUFFIX
276
283
  case "AWS::NoValue":
277
- # TODO: add support for NoValue, None cannot be used to communicate a Null value in preproc classes.
278
- raise NotImplementedError("The use of AWS:NoValue is currently unsupported")
279
- case "AWS::NotificationARNs":
280
- raise NotImplementedError(
281
- "The use of AWS::NotificationARNs is currently unsupported"
282
- )
284
+ return None
283
285
  case _:
284
- raise RuntimeError(f"Unknown pseudo parameter value '{pseudo_parameter_name}'")
286
+ raise RuntimeError(f"The use of '{pseudo_parameter_name}' is currently unsupported")
285
287
 
286
288
  def _resolve_reference(self, logical_id: str) -> PreprocEntityDelta:
287
289
  if logical_id in _PSEUDO_PARAMETERS:
@@ -317,11 +319,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
317
319
  return mapping_value_delta
318
320
 
319
321
  def visit(self, change_set_entity: ChangeSetEntity) -> PreprocEntityDelta:
320
- delta = self._processed.get(change_set_entity.scope)
321
- if delta is not None:
322
+ scope = change_set_entity.scope
323
+ if scope in self._processed:
324
+ delta = self._processed[scope]
322
325
  return delta
323
326
  delta = super().visit(change_set_entity=change_set_entity)
324
- self._processed[change_set_entity.scope] = delta
327
+ self._processed[scope] = delta
325
328
  return delta
326
329
 
327
330
  def visit_terminal_value_modified(
@@ -356,21 +359,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
356
359
  return PreprocEntityDelta(before=before_delta.before, after=after_delta.after)
357
360
 
358
361
  def visit_node_object(self, node_object: NodeObject) -> PreprocEntityDelta:
359
- before = dict()
360
- after = dict()
362
+ node_change_type = node_object.change_type
363
+ before = dict() if node_change_type != ChangeType.CREATED else Nothing
364
+ after = dict() if node_change_type != ChangeType.REMOVED else Nothing
361
365
  for name, change_set_entity in node_object.bindings.items():
362
366
  delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity)
363
- match change_set_entity.change_type:
364
- case ChangeType.MODIFIED:
365
- before[name] = delta.before
366
- after[name] = delta.after
367
- case ChangeType.CREATED:
368
- after[name] = delta.after
369
- case ChangeType.REMOVED:
370
- before[name] = delta.before
371
- case ChangeType.UNCHANGED:
372
- before[name] = delta.before
373
- after[name] = delta.before
367
+ delta_before = delta.before
368
+ delta_after = delta.after
369
+ if not is_nothing(before) and not is_nothing(delta_before) and delta_before is not None:
370
+ before[name] = delta_before
371
+ if not is_nothing(after) and not is_nothing(delta_after) and delta_after is not None:
372
+ after[name] = delta_after
374
373
  return PreprocEntityDelta(before=before, after=after)
375
374
 
376
375
  def visit_node_intrinsic_function_fn_get_att(
@@ -378,14 +377,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
378
377
  ) -> PreprocEntityDelta:
379
378
  # TODO: validate the return value according to the spec.
380
379
  arguments_delta = self.visit(node_intrinsic_function.arguments)
381
- before_argument: Optional[list[str]] = arguments_delta.before
380
+ before_argument: Maybe[list[str]] = arguments_delta.before
382
381
  if isinstance(before_argument, str):
383
382
  before_argument = before_argument.split(".")
384
- after_argument: Optional[list[str]] = arguments_delta.after
383
+ after_argument: Maybe[list[str]] = arguments_delta.after
385
384
  if isinstance(after_argument, str):
386
385
  after_argument = after_argument.split(".")
387
386
 
388
- before = None
387
+ before = Nothing
389
388
  if before_argument:
390
389
  before_logical_name_of_resource = before_argument[0]
391
390
  before_attribute_name = before_argument[1]
@@ -408,7 +407,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
408
407
  property_name=before_attribute_name,
409
408
  )
410
409
 
411
- after = None
410
+ after = Nothing
412
411
  if after_argument:
413
412
  after_logical_name_of_resource = after_argument[0]
414
413
  after_attribute_name = after_argument[1]
@@ -438,10 +437,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
438
437
  arguments_delta = self.visit(node_intrinsic_function.arguments)
439
438
  before_values = arguments_delta.before
440
439
  after_values = arguments_delta.after
441
- before = None
440
+ before = Nothing
442
441
  if before_values:
443
442
  before = before_values[0] == before_values[1]
444
- after = None
443
+ after = Nothing
445
444
  if after_values:
446
445
  after = after_values[0] == after_values[1]
447
446
  return PreprocEntityDelta(before=before, after=after)
@@ -450,6 +449,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
450
449
  self, node_intrinsic_function: NodeIntrinsicFunction
451
450
  ) -> PreprocEntityDelta:
452
451
  arguments_delta = self.visit(node_intrinsic_function.arguments)
452
+ arguments_before = arguments_delta.before
453
+ arguments_after = arguments_delta.after
453
454
 
454
455
  def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta:
455
456
  condition_name = args[0]
@@ -460,13 +461,13 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
460
461
  )
461
462
 
462
463
  # TODO: add support for this being created or removed.
463
- before = None
464
- if arguments_delta.before:
465
- before_outcome_delta = _compute_delta_for_if_statement(arguments_delta.before)
464
+ before = Nothing
465
+ if not is_nothing(arguments_before):
466
+ before_outcome_delta = _compute_delta_for_if_statement(arguments_before)
466
467
  before = before_outcome_delta.before
467
- after = None
468
- if arguments_delta.after:
469
- after_outcome_delta = _compute_delta_for_if_statement(arguments_delta.after)
468
+ after = Nothing
469
+ if not is_nothing(arguments_after):
470
+ after_outcome_delta = _compute_delta_for_if_statement(arguments_after)
470
471
  after = after_outcome_delta.after
471
472
  return PreprocEntityDelta(before=before, after=after)
472
473
 
@@ -476,20 +477,89 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
476
477
  arguments_delta = self.visit(node_intrinsic_function.arguments)
477
478
  before_condition = arguments_delta.before
478
479
  after_condition = arguments_delta.after
479
- if before_condition:
480
+ before = Nothing
481
+ if not is_nothing(before_condition):
480
482
  before_condition_outcome = before_condition[0]
481
483
  before = not before_condition_outcome
482
- else:
483
- before = None
484
-
485
- if after_condition:
484
+ after = Nothing
485
+ if not is_nothing(after_condition):
486
486
  after_condition_outcome = after_condition[0]
487
487
  after = not after_condition_outcome
488
- else:
489
- after = None
490
488
  # Implicit change type computation.
491
489
  return PreprocEntityDelta(before=before, after=after)
492
490
 
491
+ def _compute_fn_transform(self, args: dict[str, Any]) -> Any:
492
+ # TODO: add typing to arguments before this level.
493
+ # TODO: add schema validation
494
+ # TODO: add support for other transform types
495
+
496
+ account_id = self._change_set.account_id
497
+ region_name = self._change_set.region_name
498
+ transform_name: str = args.get("Name")
499
+ if not isinstance(transform_name, str):
500
+ raise RuntimeError("Invalid or missing Fn::Transform 'Name' argument")
501
+ transform_parameters: dict = args.get("Parameters")
502
+ if not isinstance(transform_parameters, dict):
503
+ raise RuntimeError("Invalid or missing Fn::Transform 'Parameters' argument")
504
+
505
+ if transform_name in transformers:
506
+ # TODO: port and refactor this 'transformers' logic to this package.
507
+ builtin_transformer_class = transformers[transform_name]
508
+ builtin_transformer: Transformer = builtin_transformer_class()
509
+ transform_output: Any = builtin_transformer.transform(
510
+ account_id=account_id, region_name=region_name, parameters=transform_parameters
511
+ )
512
+ return transform_output
513
+
514
+ macros_store = get_cloudformation_store(
515
+ account_id=account_id, region_name=region_name
516
+ ).macros
517
+ if transform_name in macros_store:
518
+ # TODO: this formatting of stack parameters is odd but required to integrate with v1 execute_macro util.
519
+ # consider porting this utils and passing the plain list of parameters instead.
520
+ stack_parameters = {
521
+ parameter["ParameterKey"]: parameter
522
+ for parameter in self._change_set.stack.parameters
523
+ }
524
+ transform_output: Any = execute_macro(
525
+ account_id=account_id,
526
+ region_name=region_name,
527
+ parsed_template=dict(), # TODO: review the requirements for this argument.
528
+ macro=args, # TODO: review support for non dict bindings (v1).
529
+ stack_parameters=stack_parameters,
530
+ transformation_parameters=transform_parameters,
531
+ is_intrinsic=True,
532
+ )
533
+ return transform_output
534
+
535
+ raise RuntimeError(
536
+ f"Unsupported transform function '{transform_name}' in '{self._change_set.stack.stack_name}'"
537
+ )
538
+
539
+ def visit_node_intrinsic_function_fn_transform(
540
+ self, node_intrinsic_function: NodeIntrinsicFunction
541
+ ) -> PreprocEntityDelta:
542
+ arguments_delta = self.visit(node_intrinsic_function.arguments)
543
+ arguments_before = arguments_delta.before
544
+ arguments_after = arguments_delta.after
545
+
546
+ # TODO: review the use of cache in self.precessed from the 'before' run to
547
+ # ensure changes to the lambda (such as after UpdateFunctionCode) do not
548
+ # generalise tot he before value at this depth (thus making it seems as
549
+ # though for this transformation before==after). Another options may be to
550
+ # have specialised caching for transformations.
551
+
552
+ # TODO: add tests to review the behaviour of CFN with changes to transformation
553
+ # function code and no changes to the template.
554
+
555
+ before = Nothing
556
+ if not is_nothing(arguments_before):
557
+ before = self._compute_fn_transform(args=arguments_before)
558
+ after = Nothing
559
+ if not is_nothing(arguments_after):
560
+ after = self._compute_fn_transform(args=arguments_after)
561
+ return PreprocEntityDelta(before=before, after=after)
562
+
493
563
  def visit_node_intrinsic_function_fn_sub(
494
564
  self, node_intrinsic_function: NodeIntrinsicFunction
495
565
  ) -> PreprocEntityDelta:
@@ -528,10 +598,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
528
598
  template_variable_value = sub_parameters[template_variable_name]
529
599
  else:
530
600
  try:
531
- reference_delta = self._resolve_reference(logical_id=template_variable_name)
601
+ resource_delta = self._resolve_reference(logical_id=template_variable_name)
532
602
  template_variable_value = (
533
- reference_delta.before if select_before else reference_delta.after
603
+ resource_delta.before if select_before else resource_delta.after
534
604
  )
605
+ if isinstance(template_variable_value, PreprocResource):
606
+ template_variable_value = template_variable_value.logical_id
535
607
  except RuntimeError:
536
608
  raise RuntimeError(
537
609
  f"Undefined variable name in Fn::Sub string template '{template_variable_name}'"
@@ -541,19 +613,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
541
613
  )
542
614
  return sub_string
543
615
 
544
- before = None
545
- if (
546
- isinstance(arguments_before, str)
547
- or isinstance(arguments_before, list)
548
- and len(arguments_before) == 2
549
- ):
616
+ before = Nothing
617
+ if not is_nothing(arguments_before):
550
618
  before = _compute_sub(args=arguments_before, select_before=True)
551
- after = None
552
- if (
553
- isinstance(arguments_after, str)
554
- or isinstance(arguments_after, list)
555
- and len(arguments_after) == 2
556
- ):
619
+ after = Nothing
620
+ if not is_nothing(arguments_after):
557
621
  after = _compute_sub(args=arguments_after)
558
622
  return PreprocEntityDelta(before=before, after=after)
559
623
 
@@ -574,14 +638,43 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
574
638
  join_result = delimiter.join(map(str, values))
575
639
  return join_result
576
640
 
577
- before = None
641
+ before = Nothing
578
642
  if isinstance(arguments_before, list) and len(arguments_before) == 2:
579
643
  before = _compute_join(arguments_before)
580
- after = None
644
+ after = Nothing
581
645
  if isinstance(arguments_after, list) and len(arguments_after) == 2:
582
646
  after = _compute_join(arguments_after)
583
647
  return PreprocEntityDelta(before=before, after=after)
584
648
 
649
+ def visit_node_intrinsic_function_fn_select(
650
+ self, node_intrinsic_function: NodeIntrinsicFunction
651
+ ):
652
+ # TODO: add further support for schema validation
653
+ arguments_delta = self.visit(node_intrinsic_function.arguments)
654
+ arguments_before = arguments_delta.before
655
+ arguments_after = arguments_delta.after
656
+
657
+ def _compute_fn_select(args: list[Any]) -> Any:
658
+ values: list[Any] = args[1]
659
+ if not isinstance(values, list) or not values:
660
+ raise RuntimeError(f"Invalid arguments list value for Fn::Select: '{values}'")
661
+ values_len = len(values)
662
+ index: int = int(args[0])
663
+ if not isinstance(index, int) or index < 0 or index > values_len:
664
+ raise RuntimeError(f"Invalid or out of range index value for Fn::Select: '{index}'")
665
+ selection = values[index]
666
+ return selection
667
+
668
+ before = Nothing
669
+ if not is_nothing(arguments_before):
670
+ before = _compute_fn_select(arguments_before)
671
+
672
+ after = Nothing
673
+ if not is_nothing(arguments_after):
674
+ after = _compute_fn_select(arguments_after)
675
+
676
+ return PreprocEntityDelta(before=before, after=after)
677
+
585
678
  def visit_node_intrinsic_function_fn_find_in_map(
586
679
  self, node_intrinsic_function: NodeIntrinsicFunction
587
680
  ) -> PreprocEntityDelta:
@@ -589,16 +682,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
589
682
  arguments_delta = self.visit(node_intrinsic_function.arguments)
590
683
  before_arguments = arguments_delta.before
591
684
  after_arguments = arguments_delta.after
685
+ before = Nothing
592
686
  if before_arguments:
593
687
  before_value_delta = self._resolve_mapping(*before_arguments)
594
688
  before = before_value_delta.before
595
- else:
596
- before = None
689
+ after = Nothing
597
690
  if after_arguments:
598
691
  after_value_delta = self._resolve_mapping(*after_arguments)
599
692
  after = after_value_delta.after
600
- else:
601
- after = None
602
693
  return PreprocEntityDelta(before=before, after=after)
603
694
 
604
695
  def visit_node_mapping(self, node_mapping: NodeMapping) -> PreprocEntityDelta:
@@ -653,15 +744,15 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
653
744
  after_logical_id = arguments_delta.after
654
745
 
655
746
  # TODO: extend this to support references to other types.
656
- before = None
657
- if before_logical_id is not None:
747
+ before = Nothing
748
+ if not is_nothing(before_logical_id):
658
749
  before_delta = self._resolve_reference(logical_id=before_logical_id)
659
750
  before = before_delta.before
660
751
  if isinstance(before, PreprocResource):
661
752
  before = before.physical_resource_id
662
753
 
663
- after = None
664
- if after_logical_id is not None:
754
+ after = Nothing
755
+ if not is_nothing(after_logical_id):
665
756
  after_delta = self._resolve_reference(logical_id=after_logical_id)
666
757
  after = after_delta.after
667
758
  if isinstance(after, PreprocResource):
@@ -670,14 +761,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
670
761
  return PreprocEntityDelta(before=before, after=after)
671
762
 
672
763
  def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta:
673
- before = list()
674
- after = list()
764
+ node_change_type = node_array.change_type
765
+ before = list() if node_change_type != ChangeType.CREATED else Nothing
766
+ after = list() if node_change_type != ChangeType.REMOVED else Nothing
675
767
  for change_set_entity in node_array.array:
676
768
  delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity)
677
- if delta.before is not None:
678
- before.append(delta.before)
679
- if delta.after is not None:
680
- after.append(delta.after)
769
+ delta_before = delta.before
770
+ delta_after = delta.after
771
+ if not is_nothing(before) and not is_nothing(delta_before):
772
+ before.append(delta_before)
773
+ if not is_nothing(after) and not is_nothing(delta_after):
774
+ after.append(delta_after)
681
775
  return PreprocEntityDelta(before=before, after=after)
682
776
 
683
777
  def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta:
@@ -686,29 +780,44 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
686
780
  def visit_node_properties(
687
781
  self, node_properties: NodeProperties
688
782
  ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]:
689
- before_bindings: dict[str, Any] = dict()
690
- after_bindings: dict[str, Any] = dict()
783
+ node_change_type = node_properties.change_type
784
+ before_bindings = dict() if node_change_type != ChangeType.CREATED else Nothing
785
+ after_bindings = dict() if node_change_type != ChangeType.REMOVED else Nothing
691
786
  for node_property in node_properties.properties:
692
- delta = self.visit(node_property)
693
787
  property_name = node_property.name
694
- if node_property.change_type != ChangeType.CREATED:
695
- before_bindings[property_name] = delta.before
696
- if node_property.change_type != ChangeType.REMOVED:
697
- after_bindings[property_name] = delta.after
698
- before = PreprocProperties(properties=before_bindings)
699
- after = PreprocProperties(properties=after_bindings)
788
+ delta = self.visit(node_property)
789
+ delta_before = delta.before
790
+ delta_after = delta.after
791
+ if (
792
+ not is_nothing(before_bindings)
793
+ and not is_nothing(delta_before)
794
+ and delta_before is not None
795
+ ):
796
+ before_bindings[property_name] = delta_before
797
+ if (
798
+ not is_nothing(after_bindings)
799
+ and not is_nothing(delta_after)
800
+ and delta_after is not None
801
+ ):
802
+ after_bindings[property_name] = delta_after
803
+ before = Nothing
804
+ if not is_nothing(before_bindings):
805
+ before = PreprocProperties(properties=before_bindings)
806
+ after = Nothing
807
+ if not is_nothing(after_bindings):
808
+ after = PreprocProperties(properties=after_bindings)
700
809
  return PreprocEntityDelta(before=before, after=after)
701
810
 
702
811
  def _resolve_resource_condition_reference(self, reference: TerminalValue) -> PreprocEntityDelta:
703
812
  reference_delta = self.visit(reference)
704
813
  before_reference = reference_delta.before
705
- before = None
706
- if before_reference is not None:
814
+ before = Nothing
815
+ if isinstance(before_reference, str):
707
816
  before_delta = self._resolve_condition(logical_id=before_reference)
708
817
  before = before_delta.before
709
- after = None
818
+ after = Nothing
710
819
  after_reference = reference_delta.after
711
- if after_reference is not None:
820
+ if isinstance(after_reference, str):
712
821
  after_delta = self._resolve_condition(logical_id=after_reference)
713
822
  after = after_delta.after
714
823
  return PreprocEntityDelta(before=before, after=after)
@@ -717,19 +826,19 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
717
826
  self, node_resource: NodeResource
718
827
  ) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
719
828
  change_type = node_resource.change_type
720
- condition_before = None
721
- condition_after = None
722
- if node_resource.condition_reference is not None:
829
+ condition_before = Nothing
830
+ condition_after = Nothing
831
+ if not is_nothing(node_resource.condition_reference):
723
832
  condition_delta = self._resolve_resource_condition_reference(
724
833
  node_resource.condition_reference
725
834
  )
726
835
  condition_before = condition_delta.before
727
836
  condition_after = condition_delta.after
728
837
 
729
- depends_on_before = None
730
- depends_on_after = None
731
- if node_resource.depends_on is not None:
732
- depends_on_delta = self.visit_node_depends_on(node_resource.depends_on)
838
+ depends_on_before = Nothing
839
+ depends_on_after = Nothing
840
+ if not is_nothing(node_resource.depends_on):
841
+ depends_on_delta = self.visit(node_resource.depends_on)
733
842
  depends_on_before = depends_on_delta.before
734
843
  depends_on_after = depends_on_delta.after
735
844
 
@@ -738,9 +847,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
738
847
  node_resource.properties
739
848
  )
740
849
 
741
- before = None
742
- after = None
743
- if change_type != ChangeType.CREATED and condition_before is None or condition_before:
850
+ before = Nothing
851
+ after = Nothing
852
+ if change_type != ChangeType.CREATED and is_nothing(condition_before) or condition_before:
744
853
  logical_resource_id = node_resource.name
745
854
  before_physical_resource_id = self._before_resource_physical_id(
746
855
  resource_logical_id=logical_resource_id
@@ -753,7 +862,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
753
862
  properties=properties_delta.before,
754
863
  depends_on=depends_on_before,
755
864
  )
756
- if change_type != ChangeType.REMOVED and condition_after is None or condition_after:
865
+ if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after:
757
866
  logical_resource_id = node_resource.name
758
867
  try:
759
868
  after_physical_resource_id = self._after_resource_physical_id(
@@ -777,8 +886,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
777
886
  change_type = node_output.change_type
778
887
  value_delta = self.visit(node_output.value)
779
888
 
780
- condition_delta = None
781
- if node_output.condition_reference is not None:
889
+ condition_delta = Nothing
890
+ if not is_nothing(node_output.condition_reference):
782
891
  condition_delta = self._resolve_resource_condition_reference(
783
892
  node_output.condition_reference
784
893
  )
@@ -789,11 +898,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
789
898
  elif condition_before and not condition_after:
790
899
  change_type = ChangeType.REMOVED
791
900
 
792
- export_delta = None
793
- if node_output.export is not None:
901
+ export_delta = Nothing
902
+ if not is_nothing(node_output.export):
794
903
  export_delta = self.visit(node_output.export)
795
904
 
796
- before: Optional[PreprocOutput] = None
905
+ before: Maybe[PreprocOutput] = Nothing
797
906
  if change_type != ChangeType.CREATED:
798
907
  before = PreprocOutput(
799
908
  name=node_output.name,
@@ -801,7 +910,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
801
910
  export=export_delta.before if export_delta else None,
802
911
  condition=condition_delta.before if condition_delta else None,
803
912
  )
804
- after: Optional[PreprocOutput] = None
913
+ after: Maybe[PreprocOutput] = Nothing
805
914
  if change_type != ChangeType.REMOVED:
806
915
  after = PreprocOutput(
807
916
  name=node_output.name,
@@ -820,8 +929,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
820
929
  output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output)
821
930
  output_before = output_delta.before
822
931
  output_after = output_delta.after
823
- if output_before:
932
+ if not is_nothing(output_before):
824
933
  before.append(output_before)
825
- if output_after:
934
+ if not is_nothing(output_after):
826
935
  after.append(output_after)
827
936
  return PreprocEntityDelta(before=before, after=after)
@@ -113,6 +113,16 @@ class ChangeSetModelVisitor(abc.ABC):
113
113
  ):
114
114
  self.visit_children(node_intrinsic_function)
115
115
 
116
+ def visit_node_intrinsic_function_fn_transform(
117
+ self, node_intrinsic_function: NodeIntrinsicFunction
118
+ ):
119
+ self.visit_children(node_intrinsic_function)
120
+
121
+ def visit_node_intrinsic_function_fn_select(
122
+ self, node_intrinsic_function: NodeIntrinsicFunction
123
+ ):
124
+ self.visit_children(node_intrinsic_function)
125
+
116
126
  def visit_node_intrinsic_function_fn_sub(self, node_intrinsic_function: NodeIntrinsicFunction):
117
127
  self.visit_children(node_intrinsic_function)
118
128
 
@@ -3815,8 +3815,9 @@ class S3Provider(S3Api, ServiceLifecycleHook):
3815
3815
  context: RequestContext,
3816
3816
  bucket: BucketName,
3817
3817
  ownership_controls: OwnershipControls,
3818
- content_md5: ContentMD5 = None,
3819
- expected_bucket_owner: AccountId = None,
3818
+ content_md5: ContentMD5 | None = None,
3819
+ expected_bucket_owner: AccountId | None = None,
3820
+ checksum_algorithm: ChecksumAlgorithm | None = None,
3820
3821
  **kwargs,
3821
3822
  ) -> None:
3822
3823
  # TODO: this currently only mock the operation, but its actual effect is not emulated