Skip to content

Commit ceca92e

Browse files
committed
Merge branch 'develop' into chore/support-cdk-environment-e2e
* develop: chore(deps-dev): bump aws-cdk-lib from 2.40.0 to 2.41.0 (#1507) update changelog with latest changes feat(tracer): support methods with the same name (ABCs) by including fully qualified name in v2 (#1486) chore(deps-dev): bump aws-cdk-aws-apigatewayv2-integrations-alpha from 2.39.1a0 to 2.40.0a0 (#1496) chore(deps-dev): bump mkdocs-material from 8.4.2 to 8.4.3 (#1504) chore(deps): bump pydantic from 1.10.1 to 1.10.2 (#1502) update changelog with latest changes feat(data-classes): add KafkaEvent and KafkaEventRecord (#1485) chore(deps-dev): bump pytest from 7.1.2 to 7.1.3 (#1497) update changelog with latest changes feat(event_handler): add cookies as 1st class citizen in v2 (#1487) chore(deps-dev): bump black from 22.6.0 to 22.8.0 (#1494) chore(deps-dev): bump aws-cdk-lib from 2.39.1 to 2.40.0 (#1495) chore(maintenance): add discord link to first PR and first issue (#1493) update changelog with latest changes refactor(batch): remove legacy sqs_batch_processor (#1492) chore(deps): bump pydantic from 1.10.0 to 1.10.1 (#1491) chore(deps-dev): bump flake8-variables-names from 0.0.4 to 0.0.5 (#1490)
2 parents af90f3f + 217bd6c commit ceca92e

37 files changed

+1641
-1243
lines changed

.github/boring-cyborg.yml

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ labelPRBasedOnFilePath:
9595
firstPRWelcomeComment: >
9696
Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
9797
98+
In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: [Invite link](https://discord.gg/B8zZKbbyET)
99+
98100
# Comment to be posted to congratulate user on their first merged PR
99101
firstPRMergeComment: >
100102
Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!
@@ -103,6 +105,8 @@ firstPRMergeComment: >
103105
firstIssueWelcomeComment: >
104106
Thanks for opening your first issue here! We'll come back to you as soon as we can.
105107
108+
In the meantime, check out the #python channel on our AWS Lambda Powertools Discord: [Invite link](https://discord.gg/B8zZKbbyET)
109+
106110
###### IssueLink Adder #################################################################################################
107111
# Insert Issue (Jira/Github etc) link in PR description based on the Issue ID in PR title.
108112
#insertIssueLinkInPrDescription:

CHANGELOG.md

+26-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
* **ci:** event resolution for on_label_added workflow
1414
* **event_handler:** fix bug with previous array implementation
1515

16+
## Code Refactoring
17+
18+
* **batch:** remove legacy sqs_batch_processor ([#1492](https://github.com/awslabs/aws-lambda-powertools-python/issues/1492))
19+
1620
## Documentation
1721

1822
* **homepage:** note about v2 version
@@ -21,33 +25,45 @@
2125
## Features
2226

2327
* **ci:** add actionlint in pre-commit hook
28+
* **data-classes:** add KafkaEvent and KafkaEventRecord ([#1485](https://github.com/awslabs/aws-lambda-powertools-python/issues/1485))
29+
* **event_handler:** add cookies as 1st class citizen in v2 ([#1487](https://github.com/awslabs/aws-lambda-powertools-python/issues/1487))
2430
* **event_handler:** improved support for headers and cookies in v2 ([#1455](https://github.com/awslabs/aws-lambda-powertools-python/issues/1455))
2531
* **event_sources:** add CloudWatch dashboard custom widget event ([#1474](https://github.com/awslabs/aws-lambda-powertools-python/issues/1474))
32+
* **tracer:** support methods with the same name (ABCs) by including fully qualified name in v2 ([#1486](https://github.com/awslabs/aws-lambda-powertools-python/issues/1486))
2633

2734
## Maintenance
2835

2936
* **bandit:** update baseline
37+
* **ci:** enable ci checks for v2
38+
* **ci:** destructure assignment on comment_large_pr
3039
* **ci:** add missing description fields
40+
* **ci:** fix invalid dependency leftover
41+
* **ci:** remove dangling debug step
3142
* **ci:** limit E2E workflow run for source code change
43+
* **ci:** add note for state persistence on comment_large_pr
44+
* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479))
45+
* **ci:** remove unused and undeclared OS matrix env
46+
* **ci:** sync package version with pypi
47+
* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480))
3248
* **ci:** create adhoc docs workflow for v2
3349
* **ci:** create adhoc docs workflow for v2
3450
* **ci:** create docs workflow for v2
3551
* **ci:** create reusable docs publishing workflow ([#1482](https://github.com/awslabs/aws-lambda-powertools-python/issues/1482))
3652
* **ci:** format comment on comment_large_pr script
37-
* **ci:** add note for state persistence on comment_large_pr
38-
* **ci:** destructure assignment on comment_large_pr
3953
* **ci:** record pr details upon labeling
40-
* **ci:** sync package version with pypi
41-
* **ci:** remove unused and undeclared OS matrix env
42-
* **ci:** enable ci checks for v2
43-
* **ci:** add workflow to suggest splitting large PRs ([#1480](https://github.com/awslabs/aws-lambda-powertools-python/issues/1480))
44-
* **ci:** add linter for GitHub Actions as pre-commit hook ([#1479](https://github.com/awslabs/aws-lambda-powertools-python/issues/1479))
45-
* **ci:** remove dangling debug step
46-
* **ci:** fix invalid dependency leftover
47-
* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/awslabs/aws-lambda-powertools-python/issues/306))
54+
* **deps:** bump pydantic from 1.10.0 to 1.10.1 ([#1491](https://github.com/awslabs/aws-lambda-powertools-python/issues/1491))
55+
* **deps:** bump pydantic from 1.10.1 to 1.10.2 ([#1502](https://github.com/awslabs/aws-lambda-powertools-python/issues/1502))
56+
* **deps-dev:** bump aws-cdk-aws-apigatewayv2-integrations-alpha from 2.39.1a0 to 2.40.0a0 ([#1496](https://github.com/awslabs/aws-lambda-powertools-python/issues/1496))
4857
* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#1481](https://github.com/awslabs/aws-lambda-powertools-python/issues/1481))
58+
* **deps-dev:** bump mypy-boto3-dynamodb from 1.24.55.post1 to 1.24.60 ([#306](https://github.com/awslabs/aws-lambda-powertools-python/issues/306))
4959
* **deps-dev:** bump mkdocs-material from 8.4.1 to 8.4.2 ([#1483](https://github.com/awslabs/aws-lambda-powertools-python/issues/1483))
60+
* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1490](https://github.com/awslabs/aws-lambda-powertools-python/issues/1490))
61+
* **deps-dev:** bump aws-cdk-lib from 2.39.1 to 2.40.0 ([#1495](https://github.com/awslabs/aws-lambda-powertools-python/issues/1495))
62+
* **deps-dev:** bump black from 22.6.0 to 22.8.0 ([#1494](https://github.com/awslabs/aws-lambda-powertools-python/issues/1494))
63+
* **deps-dev:** bump pytest from 7.1.2 to 7.1.3 ([#1497](https://github.com/awslabs/aws-lambda-powertools-python/issues/1497))
64+
* **deps-dev:** bump mkdocs-material from 8.4.2 to 8.4.3 ([#1504](https://github.com/awslabs/aws-lambda-powertools-python/issues/1504))
5065
* **maintainers:** update release workflow link
66+
* **maintenance:** add discord link to first PR and first issue ([#1493](https://github.com/awslabs/aws-lambda-powertools-python/issues/1493))
5167

5268

5369
<a name="v1.28.0"></a>

aws_lambda_powertools/event_handler/api_gateway.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from aws_lambda_powertools.event_handler import content_types
1616
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
1717
from aws_lambda_powertools.shared import constants
18+
from aws_lambda_powertools.shared.cookies import Cookie
1819
from aws_lambda_powertools.shared.functions import resolve_truthy_env_var_choice
1920
from aws_lambda_powertools.shared.json_encoder import Encoder
2021
from aws_lambda_powertools.utilities.data_classes import (
@@ -147,7 +148,7 @@ def __init__(
147148
content_type: Optional[str],
148149
body: Union[str, bytes, None],
149150
headers: Optional[Dict[str, Union[str, List[str]]]] = None,
150-
cookies: Optional[List[str]] = None,
151+
cookies: Optional[List[Cookie]] = None,
151152
):
152153
"""
153154
@@ -162,7 +163,7 @@ def __init__(
162163
Optionally set the response body. Note: bytes body will be automatically base64 encoded
163164
headers: dict[str, Union[str, List[str]]]
164165
Optionally set specific http headers. Setting "Content-Type" here would override the `content_type` value.
165-
cookies: list[str]
166+
cookies: list[Cookie]
166167
Optionally set cookies.
167168
"""
168169
self.status_code = status_code
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from datetime import datetime
2+
from enum import Enum
3+
from io import StringIO
4+
from typing import List, Optional
5+
6+
7+
class SameSite(Enum):
8+
"""
9+
SameSite allows a server to define a cookie attribute making it impossible for
10+
the browser to send this cookie along with cross-site requests. The main
11+
goal is to mitigate the risk of cross-origin information leakage, and provide
12+
some protection against cross-site request forgery attacks.
13+
14+
See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
15+
"""
16+
17+
DEFAULT_MODE = ""
18+
LAX_MODE = "Lax"
19+
STRICT_MODE = "Strict"
20+
NONE_MODE = "None"
21+
22+
23+
def _format_date(timestamp: datetime) -> str:
24+
# Specification example: Wed, 21 Oct 2015 07:28:00 GMT
25+
return timestamp.strftime("%a, %d %b %Y %H:%M:%S GMT")
26+
27+
28+
class Cookie:
29+
"""
30+
A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
31+
HTTP response or the Cookie header of an HTTP request.
32+
33+
See https://tools.ietf.org/html/rfc6265 for details.
34+
"""
35+
36+
def __init__(
37+
self,
38+
name: str,
39+
value: str,
40+
path: str = "",
41+
domain: str = "",
42+
secure: bool = True,
43+
http_only: bool = False,
44+
max_age: Optional[int] = None,
45+
expires: Optional[datetime] = None,
46+
same_site: Optional[SameSite] = None,
47+
custom_attributes: Optional[List[str]] = None,
48+
):
49+
"""
50+
51+
Parameters
52+
----------
53+
name: str
54+
The name of this cookie, for example session_id
55+
value: str
56+
The cookie value, for instance an uuid
57+
path: str
58+
The path for which this cookie is valid. Optional
59+
domain: str
60+
The domain for which this cookie is valid. Optional
61+
secure: bool
62+
Marks the cookie as secure, only sendable to the server with an encrypted request over the HTTPS protocol
63+
http_only: bool
64+
Enabling this attribute makes the cookie inaccessible to the JavaScript `Document.cookie` API
65+
max_age: Optional[int]
66+
Defines the period of time after which the cookie is invalid. Use negative values to force cookie deletion.
67+
expires: Optional[datetime]
68+
Defines a date where the permanent cookie expires.
69+
same_site: Optional[SameSite]
70+
Determines if the cookie should be sent to third party websites
71+
custom_attributes: Optional[List[str]]
72+
List of additional custom attributes to set on the cookie
73+
"""
74+
self.name = name
75+
self.value = value
76+
self.path = path
77+
self.domain = domain
78+
self.secure = secure
79+
self.expires = expires
80+
self.max_age = max_age
81+
self.http_only = http_only
82+
self.same_site = same_site
83+
self.custom_attributes = custom_attributes
84+
85+
def __str__(self) -> str:
86+
payload = StringIO()
87+
payload.write(f"{self.name}={self.value}")
88+
89+
if self.path:
90+
payload.write(f"; Path={self.path}")
91+
92+
if self.domain:
93+
payload.write(f"; Domain={self.domain}")
94+
95+
if self.expires:
96+
payload.write(f"; Expires={_format_date(self.expires)}")
97+
98+
if self.max_age:
99+
if self.max_age > 0:
100+
payload.write(f"; MaxAge={self.max_age}")
101+
else:
102+
# negative or zero max-age should be set to 0
103+
payload.write("; MaxAge=0")
104+
105+
if self.http_only:
106+
payload.write("; HttpOnly")
107+
108+
if self.secure:
109+
payload.write("; Secure")
110+
111+
if self.same_site:
112+
payload.write(f"; SameSite={self.same_site.value}")
113+
114+
if self.custom_attributes:
115+
for attr in self.custom_attributes:
116+
payload.write(f"; {attr}")
117+
118+
return payload.getvalue()

aws_lambda_powertools/shared/headers_serializer.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
from collections import defaultdict
33
from typing import Any, Dict, List, Union
44

5+
from aws_lambda_powertools.shared.cookies import Cookie
6+
57

68
class BaseHeadersSerializer:
79
"""
810
Helper class to correctly serialize headers and cookies for Amazon API Gateway,
911
ALB and Lambda Function URL response payload.
1012
"""
1113

12-
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]:
14+
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[Cookie]) -> Dict[str, Any]:
1315
"""
1416
Serializes headers and cookies according to the request type.
1517
Returns a dict that can be merged with the response payload.
@@ -25,7 +27,7 @@ def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str
2527

2628

2729
class HttpApiHeadersSerializer(BaseHeadersSerializer):
28-
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]:
30+
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[Cookie]) -> Dict[str, Any]:
2931
"""
3032
When using HTTP APIs or LambdaFunctionURLs, everything is taken care automatically for us.
3133
We can directly assign a list of cookies and a dict of headers to the response payload, and the
@@ -44,11 +46,11 @@ def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str
4446
else:
4547
combined_headers[key] = ", ".join(values)
4648

47-
return {"headers": combined_headers, "cookies": cookies}
49+
return {"headers": combined_headers, "cookies": list(map(str, cookies))}
4850

4951

5052
class MultiValueHeadersSerializer(BaseHeadersSerializer):
51-
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]:
53+
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[Cookie]) -> Dict[str, Any]:
5254
"""
5355
When using REST APIs, headers can be encoded using the `multiValueHeaders` key on the response.
5456
This is also the case when using an ALB integration with the `multiValueHeaders` option enabled.
@@ -69,13 +71,13 @@ def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str
6971
if cookies:
7072
payload.setdefault("Set-Cookie", [])
7173
for cookie in cookies:
72-
payload["Set-Cookie"].append(cookie)
74+
payload["Set-Cookie"].append(str(cookie))
7375

7476
return {"multiValueHeaders": payload}
7577

7678

7779
class SingleValueHeadersSerializer(BaseHeadersSerializer):
78-
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str]) -> Dict[str, Any]:
80+
def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[Cookie]) -> Dict[str, Any]:
7981
"""
8082
The ALB integration has `multiValueHeaders` disabled by default.
8183
If we try to set multiple headers with the same key, or more than one cookie, print a warning.
@@ -93,7 +95,7 @@ def serialize(self, headers: Dict[str, Union[str, List[str]]], cookies: List[str
9395
)
9496

9597
# We can only send one cookie, send the last one
96-
payload["headers"]["Set-Cookie"] = cookies[-1]
98+
payload["headers"]["Set-Cookie"] = str(cookies[-1])
9799

98100
for key, values in headers.items():
99101
if isinstance(values, str):

aws_lambda_powertools/tracing/tracer.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ def capture_method(
355355
"""Decorator to create subsegment for arbitrary functions
356356
357357
It also captures both response and exceptions as metadata
358-
and creates a subsegment named `## <method_name>`
358+
and creates a subsegment named `## <method_module.method_qualifiedname>`
359+
# see here: [Qualified name for classes and functions](https://peps.python.org/pep-3155/)
359360
360361
When running [async functions concurrently](https://docs.python.org/3/library/asyncio-task.html#id6),
361362
methods may impact each others subsegment, and can trigger
@@ -509,7 +510,8 @@ async def async_tasks():
509510
functools.partial(self.capture_method, capture_response=capture_response, capture_error=capture_error),
510511
)
511512

512-
method_name = f"{method.__name__}"
513+
# Example: app.ClassA.get_all # noqa E800
514+
method_name = f"{method.__module__}.{method.__qualname__}"
513515

514516
capture_response = resolve_truthy_env_var_choice(
515517
env=os.getenv(constants.TRACER_CAPTURE_RESPONSE_ENV, "true"), choice=capture_response

aws_lambda_powertools/utilities/batch/__init__.py

-3
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@
1313
batch_processor,
1414
)
1515
from aws_lambda_powertools.utilities.batch.exceptions import ExceptionInfo
16-
from aws_lambda_powertools.utilities.batch.sqs import PartialSQSProcessor, sqs_batch_processor
1716

1817
__all__ = (
1918
"BatchProcessor",
2019
"BasePartialProcessor",
2120
"ExceptionInfo",
2221
"EventType",
2322
"FailureResponse",
24-
"PartialSQSProcessor",
2523
"SuccessResponse",
2624
"batch_processor",
27-
"sqs_batch_processor",
2825
)

aws_lambda_powertools/utilities/batch/base.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,19 @@ def batch_processor(
170170
Lambda's Context
171171
record_handler: Callable
172172
Callable to process each record from the batch
173-
processor: PartialSQSProcessor
173+
processor: BasePartialProcessor
174174
Batch Processor to handle partial failure cases
175175
176176
Examples
177177
--------
178-
**Processes Lambda's event with PartialSQSProcessor**
178+
**Processes Lambda's event with a BasePartialProcessor**
179179
180-
>>> from aws_lambda_powertools.utilities.batch import batch_processor, PartialSQSProcessor
180+
>>> from aws_lambda_powertools.utilities.batch import batch_processor, BatchProcessor
181181
>>>
182182
>>> def record_handler(record):
183183
>>> return record["body"]
184184
>>>
185-
>>> @batch_processor(record_handler=record_handler, processor=PartialSQSProcessor())
185+
>>> @batch_processor(record_handler=record_handler, processor=BatchProcessor())
186186
>>> def handler(event, context):
187187
>>> return {"StatusCode": 200}
188188

aws_lambda_powertools/utilities/batch/exceptions.py

-13
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,6 @@ def format_exceptions(self, parent_exception_str):
2424
return "\n".join(exception_list)
2525

2626

27-
class SQSBatchProcessingError(BaseBatchProcessingError):
28-
"""When at least one message within a batch could not be processed"""
29-
30-
def __init__(self, msg="", child_exceptions: Optional[List[ExceptionInfo]] = None):
31-
super().__init__(msg, child_exceptions)
32-
33-
# Overriding this method so we can output all child exception tracebacks when we raise this exception to prevent
34-
# errors being lost. See https://github.com/awslabs/aws-lambda-powertools-python/issues/275
35-
def __str__(self):
36-
parent_exception_str = super(SQSBatchProcessingError, self).__str__()
37-
return self.format_exceptions(parent_exception_str)
38-
39-
4027
class BatchProcessingError(BaseBatchProcessingError):
4128
"""When all batch records failed to be processed"""
4229

0 commit comments

Comments
 (0)