localstack-core 4.10.1.dev7__py3-none-any.whl → 4.11.2.dev14__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.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

Files changed (152) hide show
  1. localstack/aws/api/acm/__init__.py +122 -122
  2. localstack/aws/api/apigateway/__init__.py +604 -561
  3. localstack/aws/api/cloudcontrol/__init__.py +63 -63
  4. localstack/aws/api/cloudformation/__init__.py +1201 -969
  5. localstack/aws/api/cloudwatch/__init__.py +375 -375
  6. localstack/aws/api/config/__init__.py +784 -786
  7. localstack/aws/api/dynamodb/__init__.py +753 -759
  8. localstack/aws/api/dynamodbstreams/__init__.py +74 -74
  9. localstack/aws/api/ec2/__init__.py +10062 -8826
  10. localstack/aws/api/es/__init__.py +453 -453
  11. localstack/aws/api/events/__init__.py +552 -552
  12. localstack/aws/api/firehose/__init__.py +541 -543
  13. localstack/aws/api/iam/__init__.py +866 -572
  14. localstack/aws/api/kinesis/__init__.py +235 -147
  15. localstack/aws/api/kms/__init__.py +341 -336
  16. localstack/aws/api/lambda_/__init__.py +974 -621
  17. localstack/aws/api/logs/__init__.py +988 -675
  18. localstack/aws/api/opensearch/__init__.py +903 -785
  19. localstack/aws/api/pipes/__init__.py +336 -336
  20. localstack/aws/api/redshift/__init__.py +1257 -1166
  21. localstack/aws/api/resource_groups/__init__.py +175 -175
  22. localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
  23. localstack/aws/api/route53/__init__.py +296 -254
  24. localstack/aws/api/route53resolver/__init__.py +397 -396
  25. localstack/aws/api/s3/__init__.py +1412 -1349
  26. localstack/aws/api/s3control/__init__.py +594 -594
  27. localstack/aws/api/scheduler/__init__.py +118 -118
  28. localstack/aws/api/secretsmanager/__init__.py +221 -216
  29. localstack/aws/api/ses/__init__.py +227 -227
  30. localstack/aws/api/sns/__init__.py +115 -115
  31. localstack/aws/api/sqs/__init__.py +100 -100
  32. localstack/aws/api/ssm/__init__.py +1977 -1971
  33. localstack/aws/api/stepfunctions/__init__.py +375 -333
  34. localstack/aws/api/sts/__init__.py +142 -66
  35. localstack/aws/api/support/__init__.py +112 -112
  36. localstack/aws/api/swf/__init__.py +378 -386
  37. localstack/aws/api/transcribe/__init__.py +425 -425
  38. localstack/aws/handlers/logging.py +8 -4
  39. localstack/aws/handlers/service.py +22 -3
  40. localstack/aws/protocol/parser.py +1 -1
  41. localstack/aws/protocol/serializer.py +1 -1
  42. localstack/aws/scaffold.py +15 -17
  43. localstack/cli/localstack.py +6 -1
  44. localstack/deprecations.py +0 -6
  45. localstack/dev/kubernetes/__main__.py +38 -3
  46. localstack/services/acm/provider.py +4 -0
  47. localstack/services/apigateway/helpers.py +5 -9
  48. localstack/services/apigateway/legacy/provider.py +60 -24
  49. localstack/services/apigateway/patches.py +0 -9
  50. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  51. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
  52. localstack/services/cloudformation/provider.py +2 -2
  53. localstack/services/cloudformation/v2/provider.py +6 -6
  54. localstack/services/cloudwatch/provider.py +10 -3
  55. localstack/services/cloudwatch/provider_v2.py +6 -3
  56. localstack/services/configservice/provider.py +5 -1
  57. localstack/services/dynamodb/provider.py +1 -0
  58. localstack/services/dynamodb/v2/provider.py +1 -0
  59. localstack/services/dynamodbstreams/provider.py +6 -0
  60. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  61. localstack/services/ec2/provider.py +6 -0
  62. localstack/services/es/provider.py +6 -0
  63. localstack/services/events/provider.py +4 -0
  64. localstack/services/events/v1/provider.py +9 -0
  65. localstack/services/firehose/provider.py +5 -0
  66. localstack/services/iam/provider.py +4 -0
  67. localstack/services/kinesis/packages.py +1 -1
  68. localstack/services/kms/models.py +44 -24
  69. localstack/services/kms/provider.py +97 -16
  70. localstack/services/lambda_/api_utils.py +40 -21
  71. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  72. localstack/services/lambda_/invocation/assignment.py +4 -1
  73. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  74. localstack/services/lambda_/invocation/lambda_models.py +27 -2
  75. localstack/services/lambda_/invocation/lambda_service.py +51 -3
  76. localstack/services/lambda_/invocation/models.py +9 -1
  77. localstack/services/lambda_/invocation/version_manager.py +18 -3
  78. localstack/services/lambda_/packages.py +1 -1
  79. localstack/services/lambda_/provider.py +240 -96
  80. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  81. localstack/services/lambda_/runtimes.py +10 -3
  82. localstack/services/logs/provider.py +45 -19
  83. localstack/services/opensearch/provider.py +53 -3
  84. localstack/services/resource_groups/provider.py +5 -1
  85. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  86. localstack/services/s3/provider.py +29 -16
  87. localstack/services/s3/utils.py +35 -14
  88. localstack/services/s3control/provider.py +101 -2
  89. localstack/services/s3control/validation.py +50 -0
  90. localstack/services/sns/constants.py +3 -1
  91. localstack/services/sns/publisher.py +15 -6
  92. localstack/services/sns/v2/models.py +30 -1
  93. localstack/services/sns/v2/provider.py +794 -31
  94. localstack/services/sns/v2/utils.py +20 -0
  95. localstack/services/sqs/models.py +37 -10
  96. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  101. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  102. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  103. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  107. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  108. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  109. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  110. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  111. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  112. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  113. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  114. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  115. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  116. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  117. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
  118. localstack/services/stepfunctions/backend/execution.py +6 -6
  119. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  120. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  121. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  122. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  123. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  124. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  125. localstack/services/stepfunctions/provider.py +78 -27
  126. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  127. localstack/testing/pytest/fixtures.py +28 -0
  128. localstack/testing/snapshots/transformer_utility.py +7 -0
  129. localstack/testing/testselection/matching.py +0 -1
  130. localstack/utils/analytics/publisher.py +37 -155
  131. localstack/utils/analytics/service_request_aggregator.py +6 -4
  132. localstack/utils/aws/arns.py +7 -0
  133. localstack/utils/aws/client_types.py +0 -8
  134. localstack/utils/batching.py +258 -0
  135. localstack/utils/catalog/catalog_loader.py +111 -3
  136. localstack/utils/collections.py +23 -11
  137. localstack/utils/crypto.py +109 -0
  138. localstack/version.py +2 -2
  139. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
  140. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
  141. localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
  142. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  143. localstack/utils/batch_policy.py +0 -124
  144. localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
  145. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  146. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
  147. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
  148. {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
  149. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
  150. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
  151. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
  152. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,104 @@
1
- from localstack.aws.api.s3control import S3ControlApi
1
+ from localstack.aws.api import CommonServiceException, RequestContext
2
+ from localstack.aws.api.s3control import (
3
+ AccountId,
4
+ ListTagsForResourceResult,
5
+ S3ControlApi,
6
+ S3ResourceArn,
7
+ TagKeyList,
8
+ TagList,
9
+ TagResourceResult,
10
+ UntagResourceResult,
11
+ )
12
+ from localstack.aws.forwarder import NotImplementedAvoidFallbackError
13
+ from localstack.services.s3.models import s3_stores
14
+ from localstack.services.s3control.validation import validate_tags
15
+ from localstack.utils.tagging import TaggingService
16
+
17
+
18
+ class NoSuchResource(CommonServiceException):
19
+ def __init__(self, message=None):
20
+ super().__init__("NoSuchResource", status_code=404, message=message)
2
21
 
3
22
 
4
23
  class S3ControlProvider(S3ControlApi):
5
- pass
24
+ """
25
+ S3Control is a management interface for S3, and can access some of its internals with no public API
26
+ This requires us to access the s3 stores directly
27
+ """
28
+
29
+ @staticmethod
30
+ def _get_tagging_service_for_bucket(
31
+ resource_arn: S3ResourceArn,
32
+ partition: str,
33
+ region: str,
34
+ account_id: str,
35
+ ) -> TaggingService:
36
+ s3_prefix = f"arn:{partition}:s3:::"
37
+ if not resource_arn.startswith(s3_prefix):
38
+ # Moto does not support Tagging operations for S3 Control, so we should not forward those operations back
39
+ # to it
40
+ raise NotImplementedAvoidFallbackError(
41
+ "LocalStack only support Bucket tagging operations for S3Control"
42
+ )
43
+
44
+ store = s3_stores[account_id][region]
45
+ bucket_name = resource_arn.removeprefix(s3_prefix)
46
+ if bucket_name not in store.global_bucket_map:
47
+ raise NoSuchResource("The specified resource doesn't exist.")
48
+
49
+ return store.TAGS
50
+
51
+ def tag_resource(
52
+ self,
53
+ context: RequestContext,
54
+ account_id: AccountId,
55
+ resource_arn: S3ResourceArn,
56
+ tags: TagList,
57
+ **kwargs,
58
+ ) -> TagResourceResult:
59
+ # currently S3Control only supports tagging buckets
60
+ tagging_service = self._get_tagging_service_for_bucket(
61
+ resource_arn=resource_arn,
62
+ partition=context.partition,
63
+ region=context.region,
64
+ account_id=account_id,
65
+ )
66
+
67
+ validate_tags(tags=tags)
68
+ tagging_service.tag_resource(resource_arn, tags)
69
+
70
+ return TagResourceResult()
71
+
72
+ def untag_resource(
73
+ self,
74
+ context: RequestContext,
75
+ account_id: AccountId,
76
+ resource_arn: S3ResourceArn,
77
+ tag_keys: TagKeyList,
78
+ **kwargs,
79
+ ) -> UntagResourceResult:
80
+ # currently S3Control only supports tagging buckets
81
+ tagging_service = self._get_tagging_service_for_bucket(
82
+ resource_arn=resource_arn,
83
+ partition=context.partition,
84
+ region=context.region,
85
+ account_id=account_id,
86
+ )
87
+
88
+ tagging_service.untag_resource(resource_arn, tag_keys)
89
+
90
+ return TagResourceResult()
91
+
92
+ def list_tags_for_resource(
93
+ self, context: RequestContext, account_id: AccountId, resource_arn: S3ResourceArn, **kwargs
94
+ ) -> ListTagsForResourceResult:
95
+ # currently S3Control only supports tagging buckets
96
+ tagging_service = self._get_tagging_service_for_bucket(
97
+ resource_arn=resource_arn,
98
+ partition=context.partition,
99
+ region=context.region,
100
+ account_id=account_id,
101
+ )
102
+
103
+ tags = tagging_service.list_tags_for_resource(resource_arn)
104
+ return ListTagsForResourceResult(Tags=tags["Tags"])
@@ -0,0 +1,50 @@
1
+ from localstack.aws.api.s3 import InvalidTag
2
+ from localstack.aws.api.s3control import Tag, TagList
3
+ from localstack.services.s3.exceptions import MalformedXML
4
+ from localstack.services.s3.utils import TAG_REGEX
5
+
6
+
7
+ def validate_tags(tags: TagList):
8
+ """
9
+ Validate the tags provided. This is the same function as S3, but with different error messages
10
+ :param tags: a TagList object
11
+ :raises MalformedXML if the object does not conform to the schema
12
+ :raises InvalidTag if the tag key or value are outside the set of validations defined by S3 and S3Control
13
+ :return: None
14
+ """
15
+ keys = set()
16
+ for tag in tags:
17
+ tag: Tag
18
+ if set(tag) != {"Key", "Value"}:
19
+ raise MalformedXML()
20
+
21
+ key = tag["Key"]
22
+ value = tag["Value"]
23
+
24
+ if key is None or value is None:
25
+ raise MalformedXML()
26
+
27
+ if key in keys:
28
+ raise InvalidTag(
29
+ "There are duplicate tag keys in your request. Remove the duplicate tag keys and try again.",
30
+ TagKey=key,
31
+ )
32
+
33
+ if key.startswith("aws:"):
34
+ raise InvalidTag(
35
+ 'User-defined tag keys can\'t start with "aws:". This prefix is reserved for system tags. Remove "aws:" from your tag keys and try again.',
36
+ )
37
+
38
+ if not TAG_REGEX.match(key):
39
+ raise InvalidTag(
40
+ "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters.",
41
+ TagKey=key,
42
+ )
43
+ elif not TAG_REGEX.match(value):
44
+ raise InvalidTag(
45
+ "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters.",
46
+ TagKey=key,
47
+ TagValue=value,
48
+ )
49
+
50
+ keys.add(key)
@@ -28,6 +28,7 @@ VALID_SUBSCRIPTION_ATTR_NAME: list[str] = [
28
28
  MSG_ATTR_NAME_REGEX = re.compile(r"^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9_\-.]+$")
29
29
  ATTR_TYPE_REGEX = re.compile(r"^(String|Number|Binary)\..+$")
30
30
  VALID_MSG_ATTR_NAME_CHARS = set(ascii_letters + digits + "." + "-" + "_")
31
+ E164_REGEX = re.compile(r"^\+?[1-9]\d{1,14}$")
31
32
 
32
33
 
33
34
  GCM_URL = "https://fcm.googleapis.com/fcm/send"
@@ -42,6 +43,7 @@ SUBSCRIPTION_TOKENS_ENDPOINT = "/_aws/sns/subscription-tokens"
42
43
  SNS_CERT_ENDPOINT = "/_aws/sns/SimpleNotificationService-6c6f63616c737461636b69736e696365.pem"
43
44
 
44
45
  DUMMY_SUBSCRIPTION_PRINCIPAL = "arn:{partition}:iam::{account_id}:user/DummySNSPrincipal"
45
- E164_REGEX = re.compile(r"^\+?[1-9]\d{1,14}$")
46
46
 
47
47
  VALID_APPLICATION_PLATFORMS = list(get_args(SnsApplicationPlatforms))
48
+
49
+ MAXIMUM_MESSAGE_LENGTH = 262144
@@ -30,6 +30,7 @@ from localstack.services.sns.models import (
30
30
  SnsStore,
31
31
  SnsSubscription,
32
32
  )
33
+ from localstack.services.sns.v2.utils import get_topic_subscriptions
33
34
  from localstack.utils.aws.arns import (
34
35
  PARTITION_NAMES,
35
36
  extract_account_id_from_arn,
@@ -254,9 +255,11 @@ class LambdaTopicPublisher(TopicPublisher):
254
255
  "UnsubscribeUrl": unsubscribe_url,
255
256
  "MessageAttributes": message_attributes,
256
257
  }
257
-
258
+ # TODO: remove v1 "signature_version" access once v1 is retired
258
259
  signature_version = (
259
- topic_attributes.get("signature_version", "1") if topic_attributes else "1"
260
+ topic_attributes.get("signature_version", topic_attributes.get("SignatureVersion", "1"))
261
+ if topic_attributes
262
+ else "1"
260
263
  )
261
264
  canonical_string = compute_canonical_string(event_payload, message_context.type)
262
265
  signature = get_message_signature(canonical_string, signature_version=signature_version)
@@ -558,7 +561,10 @@ class HttpTopicPublisher(TopicPublisher):
558
561
  ):
559
562
  return sub_content_type
560
563
 
561
- if json_topic_delivery_policy := topic_attributes.get("delivery_policy"):
564
+ # TODO: remove lower case access once legacy v1 provider is removed
565
+ if json_topic_delivery_policy := topic_attributes.get(
566
+ "delivery_policy", topic_attributes.get("DeliveryPolicy")
567
+ ):
562
568
  topic_delivery_policy = json.loads(json_topic_delivery_policy)
563
569
  if not (
564
570
  topic_content_type := topic_delivery_policy.get(subscriber["Protocol"].lower())
@@ -1009,7 +1015,10 @@ def create_sns_message_body(
1009
1015
  # FIFO topics do not add the signature in the message
1010
1016
  if not subscriber.get("TopicArn", "").endswith(".fifo"):
1011
1017
  signature_version = (
1012
- topic_attributes.get("signature_version", "1") if topic_attributes else "1"
1018
+ # we allow for both casings, depending on v1 or v2 provider
1019
+ topic_attributes.get("signature_version", topic_attributes.get("SignatureVersion", "1"))
1020
+ if topic_attributes
1021
+ else "1"
1013
1022
  )
1014
1023
  canonical_string = compute_canonical_string(data, message_type)
1015
1024
  signature = get_message_signature(canonical_string, signature_version=signature_version)
@@ -1234,7 +1243,7 @@ class PublishDispatcher:
1234
1243
  )
1235
1244
 
1236
1245
  def publish_to_topic(self, ctx: SnsPublishContext, topic_arn: str) -> None:
1237
- subscriptions = ctx.store.get_topic_subscriptions(topic_arn)
1246
+ subscriptions = get_topic_subscriptions(ctx.store, topic_arn)
1238
1247
  for subscriber in subscriptions:
1239
1248
  if self._should_publish(ctx.store.subscription_filter_policy, ctx.message, subscriber):
1240
1249
  notifier = self.topic_notifiers[subscriber["Protocol"]]
@@ -1249,7 +1258,7 @@ class PublishDispatcher:
1249
1258
  self._submit_notification(notifier, ctx, subscriber)
1250
1259
 
1251
1260
  def publish_batch_to_topic(self, ctx: SnsBatchPublishContext, topic_arn: str) -> None:
1252
- subscriptions = ctx.store.get_topic_subscriptions(topic_arn)
1261
+ subscriptions = get_topic_subscriptions(ctx.store, topic_arn)
1253
1262
  for subscriber in subscriptions:
1254
1263
  protocol = subscriber["Protocol"]
1255
1264
  notifier = self.batch_topic_notifiers.get(protocol)
@@ -5,6 +5,7 @@ from enum import StrEnum
5
5
  from typing import Literal, TypedDict
6
6
 
7
7
  from localstack.aws.api.sns import (
8
+ Endpoint,
8
9
  MessageAttributeMap,
9
10
  PlatformApplication,
10
11
  PublishBatchRequestEntry,
@@ -39,6 +40,12 @@ SnsApplicationPlatforms = Literal[
39
40
  ]
40
41
 
41
42
 
43
+ class EndpointAttributeNames(StrEnum):
44
+ CUSTOM_USER_DATA = "CustomUserData"
45
+ Token = "Token"
46
+ ENABLED = "Enabled"
47
+
48
+
42
49
  SMS_ATTRIBUTE_NAMES = [
43
50
  "DeliveryStatusIAMRole",
44
51
  "DeliveryStatusSuccessSamplingRate",
@@ -143,6 +150,19 @@ class SnsMessage:
143
150
  )
144
151
 
145
152
 
153
+ @dataclass
154
+ class PlatformEndpoint:
155
+ platform_application_arn: str
156
+ platform_endpoint: Endpoint
157
+
158
+
159
+ @dataclass
160
+ class PlatformApplicationDetails:
161
+ platform_application: PlatformApplication
162
+ # maps all Endpoints of the PlatformApplication, from their Token to their ARN
163
+ platform_endpoints: dict[str, str]
164
+
165
+
146
166
  class SnsStore(BaseStore):
147
167
  topics: dict[str, Topic] = LocalAttribute(default=dict)
148
168
 
@@ -156,11 +176,20 @@ class SnsStore(BaseStore):
156
176
  subscription_tokens: dict[str, str] = LocalAttribute(default=dict)
157
177
 
158
178
  # maps platform application arns to platform applications
159
- platform_applications: dict[str, PlatformApplication] = LocalAttribute(default=dict)
179
+ platform_applications: dict[str, PlatformApplicationDetails] = LocalAttribute(default=dict)
180
+
181
+ # maps endpoint arns to platform endpoints
182
+ platform_endpoints: dict[str, PlatformEndpoint] = LocalAttribute(default=dict)
183
+
184
+ # cache of topic ARN to platform endpoint messages (used primarily for testing)
185
+ platform_endpoint_messages: dict[str, list[dict]] = LocalAttribute(default=dict)
160
186
 
161
187
  # topic/subscription independent default values for sending sms messages
162
188
  sms_attributes: dict[str, str] = LocalAttribute(default=dict)
163
189
 
190
+ # list of sent SMS messages
191
+ sms_messages: list[dict] = LocalAttribute(default=list)
192
+
164
193
  TAGS: TaggingService = CrossRegionAttribute(default=TaggingService)
165
194
 
166
195