aws-lambda-powertools 3.0.1.dev3__py3-none-any.whl → 3.0.2.dev1__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.
- aws_lambda_powertools/event_handler/api_gateway.py +227 -54
- aws_lambda_powertools/event_handler/appsync.py +343 -97
- aws_lambda_powertools/event_handler/bedrock_agent.py +121 -0
- aws_lambda_powertools/event_handler/exceptions.py +1 -1
- aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
- aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +78 -0
- aws_lambda_powertools/event_handler/graphql_appsync/base.py +160 -0
- aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +10 -0
- aws_lambda_powertools/event_handler/graphql_appsync/router.py +62 -0
- aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +1 -1
- aws_lambda_powertools/event_handler/openapi/encoders.py +94 -70
- aws_lambda_powertools/event_handler/openapi/exceptions.py +12 -0
- aws_lambda_powertools/event_handler/openapi/models.py +38 -5
- aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +1 -4
- aws_lambda_powertools/event_handler/util.py +55 -2
- aws_lambda_powertools/logging/utils.py +5 -5
- aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +1 -0
- aws_lambda_powertools/shared/cookies.py +2 -2
- aws_lambda_powertools/shared/dynamodb_deserializer.py +13 -0
- aws_lambda_powertools/shared/version.py +1 -1
- aws_lambda_powertools/tracing/tracer.py +4 -2
- aws_lambda_powertools/utilities/batch/base.py +13 -4
- aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +260 -1
- aws_lambda_powertools/utilities/data_classes/kafka_event.py +2 -2
- aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +7 -0
- aws_lambda_powertools/utilities/data_masking/base.py +4 -4
- aws_lambda_powertools/utilities/feature_flags/appconfig.py +21 -2
- aws_lambda_powertools/utilities/idempotency/idempotency.py +22 -2
- aws_lambda_powertools/utilities/idempotency/persistence/base.py +4 -1
- aws_lambda_powertools/utilities/parameters/secrets.py +1 -1
- aws_lambda_powertools/utilities/parser/models/__init__.py +6 -0
- aws_lambda_powertools/utilities/parser/models/apigw.py +12 -1
- aws_lambda_powertools/utilities/parser/models/apigwv2.py +7 -1
- aws_lambda_powertools/utilities/validation/base.py +27 -2
- aws_lambda_powertools/utilities/validation/validator.py +49 -4
- {aws_lambda_powertools-3.0.1.dev3.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/METADATA +12 -2
- {aws_lambda_powertools-3.0.1.dev3.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/RECORD +39 -34
- {aws_lambda_powertools-3.0.1.dev3.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/LICENSE +0 -0
- {aws_lambda_powertools-3.0.1.dev3.dist-info → aws_lambda_powertools-3.0.2.dev1.dist-info}/WHEEL +0 -0
|
@@ -241,6 +241,18 @@ class UserMigrationTriggerEventResponse(DictWrapper):
|
|
|
241
241
|
"""
|
|
242
242
|
self["response"]["forceAliasCreation"] = value
|
|
243
243
|
|
|
244
|
+
@property
|
|
245
|
+
def enable_sms_mfa(self) -> bool | None:
|
|
246
|
+
return self["response"].get("enableSMSMFA")
|
|
247
|
+
|
|
248
|
+
@enable_sms_mfa.setter
|
|
249
|
+
def enable_sms_mfa(self, value: bool):
|
|
250
|
+
"""Set this parameter to "true" to require that your migrated user complete SMS text message multi-factor
|
|
251
|
+
authentication (MFA) to sign in. Your user pool must have MFA enabled. Your user's attributes
|
|
252
|
+
in the request parameters must include a phone number, or else the migration of that user will fail.
|
|
253
|
+
"""
|
|
254
|
+
self["response"]["enableSMSMFA"] = value
|
|
255
|
+
|
|
244
256
|
|
|
245
257
|
class UserMigrationTriggerEvent(BaseTriggerEvent):
|
|
246
258
|
"""Migrate User Lambda Trigger
|
|
@@ -272,6 +284,11 @@ class CustomMessageTriggerEventRequest(DictWrapper):
|
|
|
272
284
|
"""A string for you to use as the placeholder for the verification code in the custom message."""
|
|
273
285
|
return self["request"]["codeParameter"]
|
|
274
286
|
|
|
287
|
+
@property
|
|
288
|
+
def link_parameter(self) -> str:
|
|
289
|
+
"""A string for you to use as a placeholder for the verification link in the custom message."""
|
|
290
|
+
return self["request"]["linkParameter"]
|
|
291
|
+
|
|
275
292
|
@property
|
|
276
293
|
def username_parameter(self) -> str:
|
|
277
294
|
"""The username parameter. It is a required request parameter for the admin create user flow."""
|
|
@@ -459,7 +476,7 @@ class PreTokenGenerationTriggerEventRequest(DictWrapper):
|
|
|
459
476
|
@property
|
|
460
477
|
def user_attributes(self) -> dict[str, str]:
|
|
461
478
|
"""One or more name-value pairs representing user attributes."""
|
|
462
|
-
return self["request"]
|
|
479
|
+
return self["request"].get("userAttributes") or {}
|
|
463
480
|
|
|
464
481
|
@property
|
|
465
482
|
def client_metadata(self) -> dict[str, str]:
|
|
@@ -468,6 +485,16 @@ class PreTokenGenerationTriggerEventRequest(DictWrapper):
|
|
|
468
485
|
return self["request"].get("clientMetadata") or {}
|
|
469
486
|
|
|
470
487
|
|
|
488
|
+
class PreTokenGenerationTriggerV2EventRequest(PreTokenGenerationTriggerEventRequest):
|
|
489
|
+
@property
|
|
490
|
+
def scopes(self) -> list[str]:
|
|
491
|
+
"""Your user's OAuth 2.0 scopes. The scopes that are present in an access token are
|
|
492
|
+
the user pool standard and custom scopes that your user requested,
|
|
493
|
+
and that you authorized your app client to issue.
|
|
494
|
+
"""
|
|
495
|
+
return self["request"].get("scopes")
|
|
496
|
+
|
|
497
|
+
|
|
471
498
|
class ClaimsOverrideDetails(DictWrapper):
|
|
472
499
|
@property
|
|
473
500
|
def claims_to_add_or_override(self) -> dict[str, str]:
|
|
@@ -522,6 +549,123 @@ class ClaimsOverrideDetails(DictWrapper):
|
|
|
522
549
|
self["groupOverrideDetails"]["preferredRole"] = value
|
|
523
550
|
|
|
524
551
|
|
|
552
|
+
class TokenClaimsAndScopeOverrideDetails(DictWrapper):
|
|
553
|
+
@property
|
|
554
|
+
def claims_to_add_or_override(self) -> dict[str, str]:
|
|
555
|
+
return self.get("claimsToAddOrOverride") or {}
|
|
556
|
+
|
|
557
|
+
@claims_to_add_or_override.setter
|
|
558
|
+
def claims_to_add_or_override(self, value: dict[str, str]):
|
|
559
|
+
"""A map of one or more key-value pairs of claims to add or override.
|
|
560
|
+
For group related claims, use groupOverrideDetails instead."""
|
|
561
|
+
self._data["claimsToAddOrOverride"] = value
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def claims_to_suppress(self) -> list[str]:
|
|
565
|
+
return self.get("claimsToSuppress") or []
|
|
566
|
+
|
|
567
|
+
@claims_to_suppress.setter
|
|
568
|
+
def claims_to_suppress(self, value: list[str]):
|
|
569
|
+
"""A list that contains claims to be suppressed from the identity token."""
|
|
570
|
+
self._data["claimsToSuppress"] = value
|
|
571
|
+
|
|
572
|
+
@property
|
|
573
|
+
def scopes_to_add(self) -> list[str]:
|
|
574
|
+
return self.get("scopesToAdd") or []
|
|
575
|
+
|
|
576
|
+
@scopes_to_add.setter
|
|
577
|
+
def scopes_to_add(self, value: list[str]):
|
|
578
|
+
self._data["scopesToAdd"] = value
|
|
579
|
+
|
|
580
|
+
@property
|
|
581
|
+
def scopes_to_suppress(self) -> list[str]:
|
|
582
|
+
return self.get("scopesToSuppress") or []
|
|
583
|
+
|
|
584
|
+
@scopes_to_suppress.setter
|
|
585
|
+
def scopes_to_suppress(self, value: list[str]):
|
|
586
|
+
self._data["scopesToSuppress"] = value
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
class ClaimsAndScopeOverrideDetails(DictWrapper):
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def id_token_generation(self) -> TokenClaimsAndScopeOverrideDetails | None:
|
|
593
|
+
id_token_generation_details = self._data.get("idTokenGeneration")
|
|
594
|
+
return (
|
|
595
|
+
None
|
|
596
|
+
if id_token_generation_details is None
|
|
597
|
+
else TokenClaimsAndScopeOverrideDetails(id_token_generation_details)
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
@id_token_generation.setter
|
|
601
|
+
def id_token_generation(self, value: dict[str, Any]):
|
|
602
|
+
"""The output object containing the current id token's claims and scope configuration.
|
|
603
|
+
|
|
604
|
+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
|
|
605
|
+
|
|
606
|
+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
|
|
607
|
+
If you provide an empty or null object in the response, then the groups are suppressed.
|
|
608
|
+
To leave the existing group configuration as is, copy the value of the token's object
|
|
609
|
+
to the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
|
|
610
|
+
"""
|
|
611
|
+
self._data["idTokenGeneration"] = value
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def access_token_generation(self) -> TokenClaimsAndScopeOverrideDetails | None:
|
|
615
|
+
access_token_generation_details = self._data.get("accessTokenGeneration")
|
|
616
|
+
return (
|
|
617
|
+
None
|
|
618
|
+
if access_token_generation_details is None
|
|
619
|
+
else TokenClaimsAndScopeOverrideDetails(access_token_generation_details)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
@access_token_generation.setter
|
|
623
|
+
def access_token_generation(self, value: dict[str, Any]):
|
|
624
|
+
"""The output object containing the current access token's claims and scope configuration.
|
|
625
|
+
|
|
626
|
+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
|
|
627
|
+
|
|
628
|
+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
|
|
629
|
+
If you provide an empty or null object in the response, then the groups are suppressed.
|
|
630
|
+
To leave the existing group configuration as is, copy the value of the token's object to
|
|
631
|
+
the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
|
|
632
|
+
"""
|
|
633
|
+
self._data["accessTokenGeneration"] = value
|
|
634
|
+
|
|
635
|
+
@property
|
|
636
|
+
def group_configuration(self) -> GroupOverrideDetails | None:
|
|
637
|
+
group_override_details = self.get("groupOverrideDetails")
|
|
638
|
+
return None if group_override_details is None else GroupOverrideDetails(group_override_details)
|
|
639
|
+
|
|
640
|
+
@group_configuration.setter
|
|
641
|
+
def group_configuration(self, value: dict[str, Any]):
|
|
642
|
+
"""The output object containing the current group configuration.
|
|
643
|
+
|
|
644
|
+
It includes groupsToOverride, iamRolesToOverride, and preferredRole.
|
|
645
|
+
|
|
646
|
+
The groupOverrideDetails object is replaced with the one you provide. If you provide an empty or null
|
|
647
|
+
object in the response, then the groups are suppressed. To leave the existing group configuration
|
|
648
|
+
as is, copy the value of the request's groupConfiguration object to the groupOverrideDetails object
|
|
649
|
+
in the response, and pass it back to the service.
|
|
650
|
+
"""
|
|
651
|
+
self._data["groupOverrideDetails"] = value
|
|
652
|
+
|
|
653
|
+
def set_group_configuration_groups_to_override(self, value: list[str]):
|
|
654
|
+
"""A list of the group names that are associated with the user that the identity token is issued for."""
|
|
655
|
+
self._data.setdefault("groupOverrideDetails", {})
|
|
656
|
+
self["groupOverrideDetails"]["groupsToOverride"] = value
|
|
657
|
+
|
|
658
|
+
def set_group_configuration_iam_roles_to_override(self, value: list[str]):
|
|
659
|
+
"""A list of the current IAM roles associated with these groups."""
|
|
660
|
+
self._data.setdefault("groupOverrideDetails", {})
|
|
661
|
+
self["groupOverrideDetails"]["iamRolesToOverride"] = value
|
|
662
|
+
|
|
663
|
+
def set_group_configuration_preferred_role(self, value: str):
|
|
664
|
+
"""A string indicating the preferred IAM role."""
|
|
665
|
+
self._data.setdefault("groupOverrideDetails", {})
|
|
666
|
+
self["groupOverrideDetails"]["preferredRole"] = value
|
|
667
|
+
|
|
668
|
+
|
|
525
669
|
class PreTokenGenerationTriggerEventResponse(DictWrapper):
|
|
526
670
|
@property
|
|
527
671
|
def claims_override_details(self) -> ClaimsOverrideDetails:
|
|
@@ -531,6 +675,15 @@ class PreTokenGenerationTriggerEventResponse(DictWrapper):
|
|
|
531
675
|
return ClaimsOverrideDetails(self._data["response"]["claimsOverrideDetails"])
|
|
532
676
|
|
|
533
677
|
|
|
678
|
+
class PreTokenGenerationTriggerV2EventResponse(DictWrapper):
|
|
679
|
+
@property
|
|
680
|
+
def claims_scope_override_details(self) -> ClaimsAndScopeOverrideDetails:
|
|
681
|
+
# Ensure we have a `claimsAndScopeOverrideDetails` element and is not set to None
|
|
682
|
+
if self._data["response"].get("claimsAndScopeOverrideDetails") is None:
|
|
683
|
+
self._data["response"]["claimsAndScopeOverrideDetails"] = {}
|
|
684
|
+
return ClaimsAndScopeOverrideDetails(self._data["response"]["claimsAndScopeOverrideDetails"])
|
|
685
|
+
|
|
686
|
+
|
|
534
687
|
class PreTokenGenerationTriggerEvent(BaseTriggerEvent):
|
|
535
688
|
"""Pre Token Generation Lambda Trigger
|
|
536
689
|
|
|
@@ -563,6 +716,38 @@ class PreTokenGenerationTriggerEvent(BaseTriggerEvent):
|
|
|
563
716
|
return PreTokenGenerationTriggerEventResponse(self._data)
|
|
564
717
|
|
|
565
718
|
|
|
719
|
+
class PreTokenGenerationV2TriggerEvent(BaseTriggerEvent):
|
|
720
|
+
"""Pre Token Generation Lambda Trigger for the V2 Event
|
|
721
|
+
|
|
722
|
+
Amazon Cognito invokes this trigger before token generation allowing you to customize identity token claims.
|
|
723
|
+
|
|
724
|
+
Notes:
|
|
725
|
+
----
|
|
726
|
+
`triggerSource` can be one of the following:
|
|
727
|
+
|
|
728
|
+
- `TokenGeneration_HostedAuth` Called during authentication from the Amazon Cognito hosted UI sign-in page.
|
|
729
|
+
- `TokenGeneration_Authentication` Called after user authentication flows have completed.
|
|
730
|
+
- `TokenGeneration_NewPasswordChallenge` Called after the user is created by an admin. This flow is invoked
|
|
731
|
+
when the user has to change a temporary password.
|
|
732
|
+
- `TokenGeneration_AuthenticateDevice` Called at the end of the authentication of a user device.
|
|
733
|
+
- `TokenGeneration_RefreshTokens` Called when a user tries to refresh the identity and access tokens.
|
|
734
|
+
|
|
735
|
+
Documentation:
|
|
736
|
+
--------------
|
|
737
|
+
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
@property
|
|
741
|
+
def request(self) -> PreTokenGenerationTriggerV2EventRequest:
|
|
742
|
+
"""Pre Token Generation Request V2 Parameters"""
|
|
743
|
+
return PreTokenGenerationTriggerV2EventRequest(self._data)
|
|
744
|
+
|
|
745
|
+
@property
|
|
746
|
+
def response(self) -> PreTokenGenerationTriggerV2EventResponse:
|
|
747
|
+
"""Pre Token Generation Response V2 Parameters"""
|
|
748
|
+
return PreTokenGenerationTriggerV2EventResponse(self._data)
|
|
749
|
+
|
|
750
|
+
|
|
566
751
|
class ChallengeResult(DictWrapper):
|
|
567
752
|
@property
|
|
568
753
|
def challenge_name(self) -> str:
|
|
@@ -824,3 +1009,77 @@ class VerifyAuthChallengeResponseTriggerEvent(BaseTriggerEvent):
|
|
|
824
1009
|
def response(self) -> VerifyAuthChallengeResponseTriggerEventResponse:
|
|
825
1010
|
"""Verify Auth Challenge Response Parameters"""
|
|
826
1011
|
return VerifyAuthChallengeResponseTriggerEventResponse(self._data)
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
class CustomEmailSenderTriggerEventRequest(DictWrapper):
|
|
1015
|
+
@property
|
|
1016
|
+
def type(self) -> str:
|
|
1017
|
+
"""The request version. For a custom email sender event, the value of this string
|
|
1018
|
+
is always customEmailSenderRequestV1.
|
|
1019
|
+
"""
|
|
1020
|
+
return self["request"]["type"]
|
|
1021
|
+
|
|
1022
|
+
@property
|
|
1023
|
+
def code(self) -> str:
|
|
1024
|
+
"""The encrypted code that your function can decrypt and send to your user."""
|
|
1025
|
+
return self["request"]["code"]
|
|
1026
|
+
|
|
1027
|
+
@property
|
|
1028
|
+
def user_attributes(self) -> dict[str, str]:
|
|
1029
|
+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
|
|
1030
|
+
return self["request"]["userAttributes"]
|
|
1031
|
+
|
|
1032
|
+
@property
|
|
1033
|
+
def client_metadata(self) -> dict[str, str]:
|
|
1034
|
+
"""One or more key-value pairs that you can provide as custom input to the
|
|
1035
|
+
custom email sender Lambda function trigger. To pass this data to your Lambda function,
|
|
1036
|
+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
|
|
1037
|
+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
|
|
1038
|
+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
|
|
1039
|
+
in the request that it passes to the post authentication function.
|
|
1040
|
+
"""
|
|
1041
|
+
return self["request"].get("clientMetadata") or {}
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
class CustomEmailSenderTriggerEvent(BaseTriggerEvent):
|
|
1045
|
+
@property
|
|
1046
|
+
def request(self) -> CustomEmailSenderTriggerEventRequest:
|
|
1047
|
+
"""Custom Email Sender Request Parameters"""
|
|
1048
|
+
return CustomEmailSenderTriggerEventRequest(self._data)
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
class CustomSMSSenderTriggerEventRequest(DictWrapper):
|
|
1052
|
+
@property
|
|
1053
|
+
def type(self) -> str:
|
|
1054
|
+
"""The request version. For a custom SMS sender event, the value of this string is always
|
|
1055
|
+
customSMSSenderRequestV1.
|
|
1056
|
+
"""
|
|
1057
|
+
return self["request"]["type"]
|
|
1058
|
+
|
|
1059
|
+
@property
|
|
1060
|
+
def code(self) -> str:
|
|
1061
|
+
"""The encrypted code that your function can decrypt and send to your user."""
|
|
1062
|
+
return self["request"]["code"]
|
|
1063
|
+
|
|
1064
|
+
@property
|
|
1065
|
+
def user_attributes(self) -> dict[str, str]:
|
|
1066
|
+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
|
|
1067
|
+
return self["request"].get("userAttributes") or {}
|
|
1068
|
+
|
|
1069
|
+
@property
|
|
1070
|
+
def client_metadata(self) -> dict[str, str]:
|
|
1071
|
+
"""One or more key-value pairs that you can provide as custom input to the
|
|
1072
|
+
custom SMS sender Lambda function trigger. To pass this data to your Lambda function,
|
|
1073
|
+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
|
|
1074
|
+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
|
|
1075
|
+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
|
|
1076
|
+
in the request that it passes to the post authentication function.
|
|
1077
|
+
"""
|
|
1078
|
+
return self["request"].get("clientMetadata") or {}
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
class CustomSMSSenderTriggerEvent(BaseTriggerEvent):
|
|
1082
|
+
@property
|
|
1083
|
+
def request(self) -> CustomSMSSenderTriggerEventRequest:
|
|
1084
|
+
"""Custom SMS Sender Request Parameters"""
|
|
1085
|
+
return CustomSMSSenderTriggerEventRequest(self._data)
|
|
@@ -14,12 +14,12 @@ class KafkaEventRecord(DictWrapper):
|
|
|
14
14
|
return self["topic"]
|
|
15
15
|
|
|
16
16
|
@property
|
|
17
|
-
def partition(self) ->
|
|
17
|
+
def partition(self) -> int:
|
|
18
18
|
"""The Kafka record parition."""
|
|
19
19
|
return self["partition"]
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
|
-
def offset(self) ->
|
|
22
|
+
def offset(self) -> int:
|
|
23
23
|
"""The Kafka record offset."""
|
|
24
24
|
return self["offset"]
|
|
25
25
|
|
|
@@ -193,3 +193,10 @@ class VPCLatticeEventV2(VPCLatticeEventBase):
|
|
|
193
193
|
"""
|
|
194
194
|
params = self.get("queryStringParameters") or {}
|
|
195
195
|
return {k: ",".join(v) for k, v in params.items()}
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def resolved_headers_field(self) -> dict[str, str]:
|
|
199
|
+
if self.headers is not None:
|
|
200
|
+
return {key.lower(): value for key, value in self.headers.items()}
|
|
201
|
+
|
|
202
|
+
return {}
|
|
@@ -204,7 +204,7 @@ class DataMasking:
|
|
|
204
204
|
self._call_action,
|
|
205
205
|
action=action,
|
|
206
206
|
provider_options=provider_options,
|
|
207
|
-
**encryption_context,
|
|
207
|
+
**encryption_context, # type: ignore[arg-type]
|
|
208
208
|
)
|
|
209
209
|
|
|
210
210
|
# Iterate over each field to be parsed.
|
|
@@ -229,12 +229,12 @@ class DataMasking:
|
|
|
229
229
|
self._call_action,
|
|
230
230
|
action=action,
|
|
231
231
|
provider_options=provider_options,
|
|
232
|
-
**encryption_context,
|
|
232
|
+
**encryption_context, # type: ignore[arg-type]
|
|
233
233
|
)
|
|
234
234
|
|
|
235
235
|
json_parse.update(
|
|
236
236
|
data_parsed,
|
|
237
|
-
lambda field_value, fields, field_name: update_callback(field_value, fields, field_name), # noqa: B023
|
|
237
|
+
lambda field_value, fields, field_name: update_callback(field_value, fields, field_name), # type: ignore[misc] # noqa: B023
|
|
238
238
|
)
|
|
239
239
|
|
|
240
240
|
return data_parsed
|
|
@@ -245,7 +245,7 @@ class DataMasking:
|
|
|
245
245
|
fields: dict[str, Any],
|
|
246
246
|
field_name: str,
|
|
247
247
|
action: Callable,
|
|
248
|
-
provider_options: dict | None = None,
|
|
248
|
+
provider_options: dict[str, Any] | None = None,
|
|
249
249
|
**encryption_context,
|
|
250
250
|
) -> None:
|
|
251
251
|
"""
|
|
@@ -4,6 +4,8 @@ import logging
|
|
|
4
4
|
import traceback
|
|
5
5
|
from typing import TYPE_CHECKING, Any, cast
|
|
6
6
|
|
|
7
|
+
from botocore.config import Config
|
|
8
|
+
|
|
7
9
|
from aws_lambda_powertools.utilities import jmespath_utils
|
|
8
10
|
from aws_lambda_powertools.utilities.feature_flags.base import StoreProvider
|
|
9
11
|
from aws_lambda_powertools.utilities.feature_flags.exceptions import ConfigurationStoreError, StoreClientError
|
|
@@ -14,7 +16,9 @@ from aws_lambda_powertools.utilities.parameters import (
|
|
|
14
16
|
)
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
19
|
+
import boto3
|
|
17
20
|
from botocore.config import Config
|
|
21
|
+
from mypy_boto3_appconfigdata import AppConfigDataClient
|
|
18
22
|
|
|
19
23
|
from aws_lambda_powertools.logging import Logger
|
|
20
24
|
|
|
@@ -30,6 +34,9 @@ class AppConfigStore(StoreProvider):
|
|
|
30
34
|
envelope: str | None = "",
|
|
31
35
|
jmespath_options: dict | None = None,
|
|
32
36
|
logger: logging.Logger | Logger | None = None,
|
|
37
|
+
boto_config: Config | None = None,
|
|
38
|
+
boto3_session: boto3.session.Session | None = None,
|
|
39
|
+
boto3_client: AppConfigDataClient | None = None,
|
|
33
40
|
):
|
|
34
41
|
"""This class fetches JSON schemas from AWS AppConfig
|
|
35
42
|
|
|
@@ -51,6 +58,12 @@ class AppConfigStore(StoreProvider):
|
|
|
51
58
|
Alternative JMESPath options to be included when filtering expr
|
|
52
59
|
logger: A logging object
|
|
53
60
|
Used to log messages. If None is supplied, one will be created.
|
|
61
|
+
boto_config: botocore.config.Config, optional
|
|
62
|
+
Botocore configuration to pass during client initialization
|
|
63
|
+
boto3_session : boto3.Session, optional
|
|
64
|
+
Boto3 session to use for AWS API communication
|
|
65
|
+
boto3_client : AppConfigDataClient, optional
|
|
66
|
+
Boto3 AppConfigDataClient Client to use, boto3_session and boto_config will be ignored if both are provided
|
|
54
67
|
"""
|
|
55
68
|
super().__init__()
|
|
56
69
|
self.logger = logger or logging.getLogger(__name__)
|
|
@@ -58,10 +71,16 @@ class AppConfigStore(StoreProvider):
|
|
|
58
71
|
self.application = application
|
|
59
72
|
self.name = name
|
|
60
73
|
self.cache_seconds = max_age
|
|
61
|
-
self.config = sdk_config
|
|
74
|
+
self.config = sdk_config or boto_config
|
|
62
75
|
self.envelope = envelope
|
|
63
76
|
self.jmespath_options = jmespath_options
|
|
64
|
-
self._conf_store = AppConfigProvider(
|
|
77
|
+
self._conf_store = AppConfigProvider(
|
|
78
|
+
environment=environment,
|
|
79
|
+
application=application,
|
|
80
|
+
config=sdk_config or boto_config,
|
|
81
|
+
boto3_client=boto3_client,
|
|
82
|
+
boto3_session=boto3_session,
|
|
83
|
+
)
|
|
65
84
|
|
|
66
85
|
@property
|
|
67
86
|
def get_raw_configuration(self) -> dict[str, Any]:
|
|
@@ -7,11 +7,13 @@ from __future__ import annotations
|
|
|
7
7
|
import functools
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
+
import warnings
|
|
10
11
|
from inspect import isclass
|
|
11
12
|
from typing import TYPE_CHECKING, Any, Callable, cast
|
|
12
13
|
|
|
13
14
|
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
|
|
14
15
|
from aws_lambda_powertools.shared import constants
|
|
16
|
+
from aws_lambda_powertools.shared.functions import strtobool
|
|
15
17
|
from aws_lambda_powertools.shared.types import AnyCallableT
|
|
16
18
|
from aws_lambda_powertools.utilities.idempotency.base import IdempotencyHandler
|
|
17
19
|
from aws_lambda_powertools.utilities.idempotency.config import IdempotencyConfig
|
|
@@ -26,6 +28,8 @@ if TYPE_CHECKING:
|
|
|
26
28
|
)
|
|
27
29
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
|
28
30
|
|
|
31
|
+
from aws_lambda_powertools.warnings import PowertoolsUserWarning
|
|
32
|
+
|
|
29
33
|
logger = logging.getLogger(__name__)
|
|
30
34
|
|
|
31
35
|
|
|
@@ -70,7 +74,15 @@ def idempotent(
|
|
|
70
74
|
>>> return {"StatusCode": 200}
|
|
71
75
|
"""
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
|
|
78
|
+
# Raises a warning if not running in development mode
|
|
79
|
+
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
|
|
80
|
+
warnings.warn(
|
|
81
|
+
message="Disabling idempotency is intended for development environments only "
|
|
82
|
+
"and should not be used in production.",
|
|
83
|
+
category=PowertoolsUserWarning,
|
|
84
|
+
stacklevel=2,
|
|
85
|
+
)
|
|
74
86
|
return handler(event, context, **kwargs)
|
|
75
87
|
|
|
76
88
|
config = config or IdempotencyConfig()
|
|
@@ -154,7 +166,15 @@ def idempotent_function(
|
|
|
154
166
|
|
|
155
167
|
@functools.wraps(function)
|
|
156
168
|
def decorate(*args, **kwargs):
|
|
157
|
-
|
|
169
|
+
# Skip idempotency controls when POWERTOOLS_IDEMPOTENCY_DISABLED has a truthy value
|
|
170
|
+
# Raises a warning if not running in development mode
|
|
171
|
+
if strtobool(os.getenv(constants.IDEMPOTENCY_DISABLED_ENV, "false")):
|
|
172
|
+
warnings.warn(
|
|
173
|
+
message="Disabling idempotency is intended for development environments only "
|
|
174
|
+
"and should not be used in production.",
|
|
175
|
+
category=PowertoolsUserWarning,
|
|
176
|
+
stacklevel=2,
|
|
177
|
+
)
|
|
158
178
|
return function(*args, **kwargs)
|
|
159
179
|
|
|
160
180
|
if data_keyword_argument not in kwargs:
|
|
@@ -305,7 +305,10 @@ class BasePersistenceLayer(ABC):
|
|
|
305
305
|
payload_hash=self._get_hashed_payload(data=data),
|
|
306
306
|
)
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
# When Lambda kills the container after timeout, the remaining_time_in_millis is 0, which is considered False.
|
|
309
|
+
# Therefore, we need to check if remaining_time_in_millis is not None (>=0) to handle this case.
|
|
310
|
+
# See: https://github.com/aws-powertools/powertools-lambda-python/issues/4759
|
|
311
|
+
if remaining_time_in_millis is not None:
|
|
309
312
|
now = datetime.datetime.now()
|
|
310
313
|
period = datetime.timedelta(milliseconds=remaining_time_in_millis)
|
|
311
314
|
timestamp = (now + period).timestamp()
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from .alb import AlbModel, AlbRequestContext, AlbRequestContextData
|
|
2
2
|
from .apigw import (
|
|
3
|
+
ApiGatewayAuthorizerRequest,
|
|
4
|
+
ApiGatewayAuthorizerToken,
|
|
3
5
|
APIGatewayEventAuthorizer,
|
|
4
6
|
APIGatewayEventIdentity,
|
|
5
7
|
APIGatewayEventRequestContext,
|
|
6
8
|
APIGatewayProxyEventModel,
|
|
7
9
|
)
|
|
8
10
|
from .apigwv2 import (
|
|
11
|
+
ApiGatewayAuthorizerRequestV2,
|
|
9
12
|
APIGatewayProxyEventV2Model,
|
|
10
13
|
RequestContextV2,
|
|
11
14
|
RequestContextV2Authorizer,
|
|
@@ -101,6 +104,7 @@ from .vpc_latticev2 import VpcLatticeV2Model
|
|
|
101
104
|
|
|
102
105
|
__all__ = [
|
|
103
106
|
"APIGatewayProxyEventV2Model",
|
|
107
|
+
"ApiGatewayAuthorizerRequestV2",
|
|
104
108
|
"RequestContextV2",
|
|
105
109
|
"RequestContextV2Http",
|
|
106
110
|
"RequestContextV2Authorizer",
|
|
@@ -161,6 +165,8 @@ __all__ = [
|
|
|
161
165
|
"APIGatewayEventRequestContext",
|
|
162
166
|
"APIGatewayEventAuthorizer",
|
|
163
167
|
"APIGatewayEventIdentity",
|
|
168
|
+
"ApiGatewayAuthorizerRequest",
|
|
169
|
+
"ApiGatewayAuthorizerToken",
|
|
164
170
|
"KafkaSelfManagedEventModel",
|
|
165
171
|
"KafkaRecordModel",
|
|
166
172
|
"KafkaMskEventModel",
|
|
@@ -88,5 +88,16 @@ class APIGatewayProxyEventModel(BaseModel):
|
|
|
88
88
|
requestContext: APIGatewayEventRequestContext
|
|
89
89
|
pathParameters: Optional[Dict[str, str]] = None
|
|
90
90
|
stageVariables: Optional[Dict[str, str]] = None
|
|
91
|
-
isBase64Encoded: bool
|
|
91
|
+
isBase64Encoded: Optional[bool] = None
|
|
92
92
|
body: Optional[Union[str, Type[BaseModel]]] = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ApiGatewayAuthorizerToken(BaseModel):
|
|
96
|
+
type: Literal["TOKEN"]
|
|
97
|
+
methodArn: str
|
|
98
|
+
authorizationToken: str
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ApiGatewayAuthorizerRequest(APIGatewayProxyEventModel):
|
|
102
|
+
type: Literal["REQUEST"]
|
|
103
|
+
methodArn: str
|
|
@@ -66,4 +66,10 @@ class APIGatewayProxyEventV2Model(BaseModel):
|
|
|
66
66
|
stageVariables: Optional[Dict[str, str]] = None
|
|
67
67
|
requestContext: RequestContextV2
|
|
68
68
|
body: Optional[Union[str, Type[BaseModel]]] = None
|
|
69
|
-
isBase64Encoded: bool
|
|
69
|
+
isBase64Encoded: Optional[bool] = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ApiGatewayAuthorizerRequestV2(APIGatewayProxyEventV2Model):
|
|
73
|
+
type: Literal["REQUEST"]
|
|
74
|
+
routeArn: str
|
|
75
|
+
identitySource: List[str]
|
|
@@ -9,7 +9,13 @@ from aws_lambda_powertools.utilities.validation.exceptions import InvalidSchemaF
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def validate_data_against_schema(
|
|
12
|
+
def validate_data_against_schema(
|
|
13
|
+
data: dict | str,
|
|
14
|
+
schema: dict,
|
|
15
|
+
formats: dict | None = None,
|
|
16
|
+
handlers: dict | None = None,
|
|
17
|
+
provider_options: dict | None = None,
|
|
18
|
+
) -> dict | str:
|
|
13
19
|
"""Validate dict data against given JSON Schema
|
|
14
20
|
|
|
15
21
|
Parameters
|
|
@@ -20,6 +26,17 @@ def validate_data_against_schema(data: dict | str, schema: dict, formats: dict |
|
|
|
20
26
|
JSON Schema to validate against
|
|
21
27
|
formats: dict
|
|
22
28
|
Custom formats containing a key (e.g. int64) and a value expressed as regex or callback returning bool
|
|
29
|
+
handlers: Dict
|
|
30
|
+
Custom methods to retrieve remote schemes, keyed off of URI scheme
|
|
31
|
+
provider_options: Dict
|
|
32
|
+
Arguments that will be passed directly to the underlying validation call, in this case fastjsonchema.validate.
|
|
33
|
+
For all supported arguments see: https://horejsek.github.io/python-fastjsonschema/#fastjsonschema.validate
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
Dict
|
|
38
|
+
The validated event. If the schema specifies a `default` value for fields that are omitted,
|
|
39
|
+
those default values will be included in the response.
|
|
23
40
|
|
|
24
41
|
Raises
|
|
25
42
|
------
|
|
@@ -30,7 +47,15 @@ def validate_data_against_schema(data: dict | str, schema: dict, formats: dict |
|
|
|
30
47
|
"""
|
|
31
48
|
try:
|
|
32
49
|
formats = formats or {}
|
|
33
|
-
|
|
50
|
+
handlers = handlers or {}
|
|
51
|
+
provider_options = provider_options or {}
|
|
52
|
+
return fastjsonschema.validate(
|
|
53
|
+
definition=schema,
|
|
54
|
+
data=data,
|
|
55
|
+
formats=formats,
|
|
56
|
+
handlers=handlers,
|
|
57
|
+
**provider_options,
|
|
58
|
+
)
|
|
34
59
|
except (TypeError, AttributeError, fastjsonschema.JsonSchemaDefinitionException) as e:
|
|
35
60
|
raise InvalidSchemaFormatError(f"Schema received: {schema}, Formats: {formats}. Error: {e}")
|
|
36
61
|
except fastjsonschema.JsonSchemaValueException as e:
|