Skip to content

Commit 6d67514

Browse files
Merge branch 'develop' into feat/openapi-response-fields-enhancement
2 parents b603674 + 15b54c4 commit 6d67514

File tree

39 files changed

+850
-496
lines changed

39 files changed

+850
-496
lines changed

.github/workflows/quality_check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
- name: Complexity baseline
7777
run: make complexity-baseline
7878
- name: Upload coverage to Codecov
79-
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # 5.5.0
79+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # 5.5.1
8080
with:
8181
token: ${{ secrets.CODECOV_TOKEN }}
8282
files: ./coverage.xml

CHANGELOG.md

Lines changed: 86 additions & 56 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Exposes version constant to avoid circular dependencies."""
22

3-
VERSION = "3.19.1a15"
3+
VERSION = "3.20.1a0"

aws_lambda_powertools/utilities/parser/functions.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,15 @@ def _validate_source_ip(value):
101101
IPvAnyNetwork(value)
102102
except ValueError:
103103
try:
104-
ip_part = value.split(":")[0]
104+
# Handle IPv6 with port: [IPv6]:port
105+
if value.startswith("[") and "]:" in value:
106+
ip_part = value.split("]:")[0][1:] # Remove "[" and get IP part
107+
elif ":" in value and value.count(":") <= 1:
108+
ip_part = value.split(":")[0]
109+
else:
110+
# If it"s not in IP:port format, validate as-is
111+
ip_part = value
112+
105113
IPvAnyNetwork(ip_part)
106114
except (ValueError, IndexError) as e:
107115
raise ValueError(f"Invalid IP address in sourceIp: {ip_part}") from e
Lines changed: 187 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,109 @@
11
from typing import Any, Dict, List, Optional, Union
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44

55

66
class AppSyncIamIdentity(BaseModel):
7-
accountId: str
8-
cognitoIdentityPoolId: Optional[str]
9-
cognitoIdentityId: Optional[str]
10-
sourceIp: List[str]
11-
username: str
12-
userArn: str
13-
cognitoIdentityAuthType: Optional[str]
14-
cognitoIdentityAuthProvider: Optional[str]
7+
accountId: str = Field(description="The AWS account ID of the caller.", examples=["123456789012"])
8+
cognitoIdentityPoolId: Optional[str] = Field(
9+
default=None,
10+
description="The Amazon Cognito identity pool ID associated with the caller.",
11+
examples=["us-east-1:12345678-1234-1234-1234-123456789012"],
12+
)
13+
cognitoIdentityId: Optional[str] = Field(
14+
default=None,
15+
description="The Amazon Cognito identity ID of the caller.",
16+
examples=["us-east-1:12345678-1234-1234-1234-123456789012"],
17+
)
18+
sourceIp: List[str] = Field(
19+
description=(
20+
"The source IP address of the caller that AWS AppSync receives. "
21+
"If the request includes a x-forwarded-for header, this is a list of IP addresses."
22+
),
23+
)
24+
username: str = Field(
25+
description="The IAM user principal name.",
26+
examples=["AIDAJEXAMPLE1234", "appsync-user"],
27+
)
28+
userArn: str = Field(
29+
description="The Amazon Resource Name (ARN) of the IAM user.",
30+
examples=["arn:aws:iam::123456789012:user/appsync", "arn:aws:iam::123456789012:user/service-user"],
31+
)
32+
cognitoIdentityAuthType: Optional[str] = Field(
33+
default=None,
34+
description="Either authenticated or unauthenticated based on the identity type.",
35+
examples=["authenticated", "unauthenticated"],
36+
)
37+
cognitoIdentityAuthProvider: Optional[str] = Field(
38+
default=None,
39+
description=(
40+
"A comma-separated list of external identity provider information "
41+
"used in obtaining the credentials used to sign the request."
42+
),
43+
examples=[
44+
"cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
45+
"graph.facebook.com,cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
46+
],
47+
)
1548

1649

1750
class AppSyncCognitoIdentity(BaseModel):
18-
sub: str
19-
issuer: str
20-
username: str
21-
claims: Dict[str, Any]
22-
sourceIp: List[str]
23-
defaultAuthStrategy: str
24-
groups: Optional[List[str]]
51+
sub: str = Field(
52+
description="The UUID of the authenticated user from Cognito User Pool.",
53+
examples=["user-uuid-1234-5678-9012-123456789012", "user-uuid-abcd-efgh-ijkl-mnopqrstuvwx"],
54+
)
55+
issuer: str = Field(
56+
description="The token issuer URL from Cognito User Pool.",
57+
examples=[
58+
"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
59+
"https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx",
60+
],
61+
)
62+
username: str = Field(
63+
description="The username of the authenticated user (cognito:username attribute).",
64+
examples=["mike", "jdoe", "user123"],
65+
)
66+
claims: Dict[str, Any] = Field(description="The JWT claims that the user has from Cognito User Pool.")
67+
sourceIp: List[str] = Field(
68+
description=(
69+
"The source IP address of the caller that AWS AppSync receives. "
70+
"If the request includes a x-forwarded-for header, this is a list of IP addresses."
71+
),
72+
)
73+
defaultAuthStrategy: str = Field(
74+
description="The default authorization strategy for this caller (ALLOW or DENY).",
75+
examples=["ALLOW", "DENY"],
76+
)
77+
groups: Optional[List[str]] = Field(
78+
default=None,
79+
description="The Cognito User Pool groups that the user belongs to.",
80+
examples=[["admin", "users"], ["developers"]],
81+
)
2582

2683

2784
class AppSyncOidcIdentity(BaseModel):
28-
claims: Dict[str, Any]
29-
issuer: str
30-
sub: str
85+
claims: Dict[str, Any] = Field(description="The JWT claims from the OpenID Connect provider.")
86+
issuer: str = Field(
87+
description="The token issuer URL from the OpenID Connect provider.",
88+
examples=["https://accounts.google.com", "https://login.microsoftonline.com/tenant-id/v2.0"],
89+
)
90+
sub: str = Field(
91+
description="The subject identifier from the OpenID Connect provider.",
92+
examples=["248289761001", "provider-subject-identifier"],
93+
)
3194

3295

3396
class AppSyncLambdaIdentity(BaseModel):
34-
resolverContext: Dict[str, Any]
97+
resolverContext: Dict[str, Any] = Field(
98+
description=(
99+
"The resolver context returned by the Lambda function authorizing the request. "
100+
"Contains custom authorization data from AWS_LAMBDA authorization."
101+
),
102+
examples=[
103+
{"userId": "user123", "role": "admin", "permissions": ["read", "write"]},
104+
{"customClaim": "value", "authLevel": "premium"},
105+
],
106+
)
35107

36108

37109
AppSyncIdentity = Union[
@@ -43,30 +115,110 @@ class AppSyncLambdaIdentity(BaseModel):
43115

44116

45117
class AppSyncRequestModel(BaseModel):
46-
domainName: Optional[str]
47-
headers: Dict[str, str]
118+
domainName: Optional[str] = Field(
119+
default=None,
120+
description=(
121+
"The custom domain name used to access the GraphQL endpoint. "
122+
"Returns null when using the default GraphQL endpoint domain name."
123+
),
124+
examples=["api.example.com", "graphql.mycompany.com"],
125+
)
126+
headers: Dict[str, str] = Field(
127+
description="HTTP headers from the GraphQL request, including custom headers.",
128+
examples=[
129+
{
130+
"cloudfront-viewer-country": "US",
131+
"host": "example.appsync-api.us-east-1.amazonaws.com",
132+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
133+
"content-type": "application/json",
134+
},
135+
],
136+
)
48137

49138

50139
class AppSyncInfoModel(BaseModel):
51-
selectionSetList: List[str]
52-
selectionSetGraphQL: str
53-
parentTypeName: str
54-
fieldName: str
55-
variables: Dict[str, Any]
140+
selectionSetList: List[str] = Field(
141+
description=(
142+
"A list representation of the fields in the GraphQL selection set. "
143+
"Fields that are aliased are referenced only by the alias name."
144+
),
145+
examples=[["id", "field1", "field2"], ["postId", "title", "content", "author", "author/id", "author/name"]],
146+
)
147+
selectionSetGraphQL: str = Field(
148+
description=(
149+
"A string representation of the selection set, formatted as GraphQL SDL. "
150+
"Inline fragments are preserved but fragments are not merged."
151+
),
152+
examples=[
153+
"{\n id\n field1\n field2\n}",
154+
"{\n postId\n title\n content\n author {\n id\n name\n }\n}",
155+
],
156+
)
157+
parentTypeName: str = Field(
158+
description="The name of the parent type for the field that is currently being resolved.",
159+
examples=["Query", "Mutation", "Subscription", "User", "Post"],
160+
)
161+
fieldName: str = Field(
162+
description="The name of the field that is currently being resolved.",
163+
examples=["getUser", "createPost", "locations", "updateProfile"],
164+
)
165+
variables: Dict[str, Any] = Field(
166+
description="A map which holds all variables that are passed into the GraphQL request.",
167+
examples=[{"userId": "123", "limit": 10}, {"input": {"name": "John", "email": "[email protected]"}}, {}],
168+
)
56169

57170

58171
class AppSyncPrevModel(BaseModel):
59-
result: Dict[str, Any]
172+
result: Dict[str, Any] = Field(
173+
description=(
174+
"The result of whatever previous operation was executed in a pipeline resolver. "
175+
"Contains the output from the previous function or Before mapping template."
176+
),
177+
examples=[
178+
{"userId": "123", "posts": [{"id": "1", "title": "Hello World"}]},
179+
{"data": {"field1": "value1", "field2": "value2"}},
180+
],
181+
)
60182

61183

62184
class AppSyncResolverEventModel(BaseModel):
63-
arguments: Dict[str, Any]
64-
identity: Optional[AppSyncIdentity]
65-
source: Optional[Dict[str, Any]]
66-
request: AppSyncRequestModel
67-
info: AppSyncInfoModel
68-
prev: Optional[AppSyncPrevModel]
69-
stash: Dict[str, Any]
185+
arguments: Dict[str, Any] = Field(
186+
description="The arguments passed to the GraphQL field.",
187+
examples=[
188+
{"id": "123", "limit": 10},
189+
{"input": {"name": "John", "email": "[email protected]"}},
190+
{"page": 2, "size": 1, "name": "value"},
191+
],
192+
)
193+
identity: Optional[AppSyncIdentity] = Field(
194+
default=None,
195+
description="Information about the caller identity (authenticated user or API key).",
196+
)
197+
source: Optional[Dict[str, Any]] = Field(
198+
default=None,
199+
description="The parent object for the field. For top-level fields, this will be null.",
200+
examples=[
201+
None,
202+
{"id": "user123", "name": "John Doe"},
203+
{"name": "Value", "nested": {"name": "value", "list": []}},
204+
{"postId": "post456", "title": "My Post"},
205+
],
206+
)
207+
request: AppSyncRequestModel = Field(description="Information about the GraphQL request context.")
208+
info: AppSyncInfoModel = Field(
209+
description="Information about the GraphQL request including selection set and field details.",
210+
)
211+
prev: Optional[AppSyncPrevModel] = Field(
212+
default=None,
213+
description="Results from the previous resolver in a pipeline resolver.",
214+
)
215+
stash: Dict[str, Any] = Field(
216+
description=(
217+
"The stash is a map that is made available inside each resolver and function mapping template. "
218+
"The same stash instance lives through a single resolver execution."
219+
),
220+
examples=[{"customData": "value", "userId": "123"}],
221+
)
70222

71223

72224
AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel]
Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,86 @@
11
from typing import Any, Dict, List, Literal, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44

55
from aws_lambda_powertools.utilities.parser.models.appsync import AppSyncIdentity, AppSyncRequestModel
66

77

88
class AppSyncEventsInfoChannelModel(BaseModel):
9-
path: str
10-
segments: List[str]
9+
path: str = Field(
10+
description="The full path of the AppSync Events channel.",
11+
examples=["/default/channel", "/notifications/user-updates", "/chat/room-123"],
12+
)
13+
segments: List[str] = Field(
14+
description="The path segments of the channel, split by forward slashes.",
15+
examples=[["default", "channel"], ["notifications", "user-updates"], ["chat", "room-123"]],
16+
)
1117

1218

1319
class AppSyncEventsInfoChannelNamespaceModel(BaseModel):
14-
name: str
20+
name: str = Field(
21+
description="The namespace name for the AppSync Events channel.",
22+
examples=["default", "notifications", "chat", "user-events"],
23+
)
1524

1625

1726
class AppSyncEventsInfoModel(BaseModel):
18-
channel: AppSyncEventsInfoChannelModel
19-
channelNamespace: AppSyncEventsInfoChannelNamespaceModel
20-
operation: Literal["PUBLISH", "SUBSCRIBE"]
27+
channel: AppSyncEventsInfoChannelModel = Field(description="Information about the AppSync Events channel.")
28+
channelNamespace: AppSyncEventsInfoChannelNamespaceModel = Field(
29+
description="The namespace information for the channel.",
30+
)
31+
operation: Literal["PUBLISH", "SUBSCRIBE"] = Field(
32+
description="The type of operation being performed on the channel.",
33+
examples=["PUBLISH", "SUBSCRIBE"],
34+
)
2135

2236

2337
class AppSyncEventsEventModel(BaseModel):
24-
id: str
25-
payload: Dict[str, Any]
38+
id: str = Field(
39+
description="The unique identifier for the event.",
40+
examples=["1", "2", "event-123", "notification-456"],
41+
)
42+
payload: Dict[str, Any] = Field(
43+
description="The event data payload containing the actual event information.",
44+
examples=[
45+
{"event_1": "data_1"},
46+
{"event_2": "data_2"},
47+
{"userId": "123", "action": "login", "timestamp": "2023-01-01T00:00:00Z"},
48+
{"message": "Hello World", "type": "notification"},
49+
],
50+
)
2651

2752

2853
class AppSyncEventsModel(BaseModel):
29-
identity: Optional[AppSyncIdentity] = None
30-
request: AppSyncRequestModel
31-
info: AppSyncEventsInfoModel
32-
prev: Optional[str] = None
33-
outErrors: Optional[List[str]] = None
34-
stash: Optional[Dict[str, Any]] = None
35-
events: Optional[List[AppSyncEventsEventModel]] = None
54+
identity: Optional[AppSyncIdentity] = Field(
55+
default=None,
56+
description="Information about the caller identity (authenticated user or API key).",
57+
)
58+
request: AppSyncRequestModel = Field(description="Information about the GraphQL request context.")
59+
info: AppSyncEventsInfoModel = Field(
60+
description="Information about the AppSync Events operation including channel details.",
61+
)
62+
prev: Optional[str] = Field(
63+
default=None,
64+
description="Results from the previous operation in a pipeline resolver.",
65+
examples=["previous-result-data"],
66+
)
67+
outErrors: Optional[List[str]] = Field(
68+
default=None,
69+
description="List of output errors that occurred during event processing.",
70+
examples=[["Error message 1", "Error message 2"]],
71+
)
72+
stash: Optional[Dict[str, Any]] = Field(
73+
default=None,
74+
description=(
75+
"The stash is a map that is made available inside each resolver and function mapping template. "
76+
"The same stash instance lives through a single resolver execution."
77+
),
78+
examples=[{"customData": "value", "userId": "123"}],
79+
)
80+
events: Optional[List[AppSyncEventsEventModel]] = Field(
81+
default=None,
82+
description="List of events being published or subscribed to in the AppSync Events operation.",
83+
examples=[
84+
[{"id": "1", "payload": {"event_1": "data_1"}}, {"id": "2", "payload": {"event_2": "data_2"}}],
85+
],
86+
)

0 commit comments

Comments
 (0)