Skip to content

Commit a1672ef

Browse files
Merge branch 'develop' into dependabot/github_actions/docker/setup-buildx-action-3.11.1
2 parents dfde84d + a5482c8 commit a1672ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2133
-451
lines changed

CHANGELOG.md

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,65 @@
44
<a name="unreleased"></a>
55
# Unreleased
66

7+
## Maintenance
8+
9+
* **deps:** bump redis from 5.3.0 to 6.2.0 ([#6827](https://github.com/aws-powertools/powertools-lambda-python/issues/6827))
10+
11+
12+
<a name="v3.15.1"></a>
13+
## [v3.15.1] - 2025-06-20
14+
## Features
15+
16+
* **kafka:** add logic to handle protobuf deserialization ([#6841](https://github.com/aws-powertools/powertools-lambda-python/issues/6841))
17+
18+
## Maintenance
19+
20+
* version bump
21+
* **ci:** new pre-release 3.15.1a0 ([#6839](https://github.com/aws-powertools/powertools-lambda-python/issues/6839))
22+
23+
24+
<a name="v3.15.0"></a>
25+
## [v3.15.0] - 2025-06-19
726
## Bug Fixes
827

928
* **bedrock_agent:** fix querystring field resolution ([#6777](https://github.com/aws-powertools/powertools-lambda-python/issues/6777))
1029

1130
## Documentation
1231

32+
* **kafka:** add kafka documentation ([#6834](https://github.com/aws-powertools/powertools-lambda-python/issues/6834))
1333
* **public_reference:** add Instil as a public reference ([#6763](https://github.com/aws-powertools/powertools-lambda-python/issues/6763))
1434

35+
## Features
36+
37+
* **kafka:** add support for Confluence Producers ([#6833](https://github.com/aws-powertools/powertools-lambda-python/issues/6833))
38+
* **kafka:** New Kafka utility ([#6821](https://github.com/aws-powertools/powertools-lambda-python/issues/6821))
39+
1540
## Maintenance
1641

42+
* version bump
43+
* **ci:** new pre-release 3.14.1a6 ([#6830](https://github.com/aws-powertools/powertools-lambda-python/issues/6830))
44+
* **ci:** new pre-release 3.14.1a5 ([#6820](https://github.com/aws-powertools/powertools-lambda-python/issues/6820))
45+
* **ci:** new pre-release 3.14.1a0 ([#6773](https://github.com/aws-powertools/powertools-lambda-python/issues/6773))
1746
* **ci:** new pre-release 3.14.1a4 ([#6812](https://github.com/aws-powertools/powertools-lambda-python/issues/6812))
1847
* **ci:** new pre-release 3.14.1a3 ([#6797](https://github.com/aws-powertools/powertools-lambda-python/issues/6797))
1948
* **ci:** new pre-release 3.14.1a1 ([#6778](https://github.com/aws-powertools/powertools-lambda-python/issues/6778))
2049
* **ci:** new pre-release 3.14.1a2 ([#6788](https://github.com/aws-powertools/powertools-lambda-python/issues/6788))
21-
* **ci:** new pre-release 3.14.1a0 ([#6773](https://github.com/aws-powertools/powertools-lambda-python/issues/6773))
22-
* **deps:** bump mkdocstrings-python from 1.16.11 to 1.16.12 ([#6765](https://github.com/aws-powertools/powertools-lambda-python/issues/6765))
2350
* **deps:** bump mkdocstrings-python from 1.16.11 to 1.16.12 in /docs ([#6768](https://github.com/aws-powertools/powertools-lambda-python/issues/6768))
51+
* **deps:** bump mkdocstrings-python from 1.16.11 to 1.16.12 ([#6765](https://github.com/aws-powertools/powertools-lambda-python/issues/6765))
2452
* **deps:** bump protobuf from 6.31.0 to 6.31.1 ([#6815](https://github.com/aws-powertools/powertools-lambda-python/issues/6815))
53+
* **deps-dev:** bump boto3-stubs from 1.38.29 to 1.38.30 ([#6772](https://github.com/aws-powertools/powertools-lambda-python/issues/6772))
2554
* **deps-dev:** bump aws-cdk from 2.1017.1 to 2.1018.0 ([#6775](https://github.com/aws-powertools/powertools-lambda-python/issues/6775))
55+
* **deps-dev:** bump boto3-stubs from 1.38.33 to 1.38.35 ([#6796](https://github.com/aws-powertools/powertools-lambda-python/issues/6796))
56+
* **deps-dev:** bump aws-cdk from 2.1018.0 to 2.1018.1 ([#6803](https://github.com/aws-powertools/powertools-lambda-python/issues/6803))
57+
* **deps-dev:** bump boto3-stubs from 1.38.30 to 1.38.31 ([#6776](https://github.com/aws-powertools/powertools-lambda-python/issues/6776))
58+
* **deps-dev:** bump requests from 2.32.3 to 2.32.4 ([#6789](https://github.com/aws-powertools/powertools-lambda-python/issues/6789))
59+
* **deps-dev:** bump boto3-stubs from 1.38.28 to 1.38.29 ([#6764](https://github.com/aws-powertools/powertools-lambda-python/issues/6764))
2660
* **deps-dev:** bump ruff from 0.11.12 to 0.11.13 ([#6780](https://github.com/aws-powertools/powertools-lambda-python/issues/6780))
27-
* **deps-dev:** bump requests from 2.32.3 to 2.32.4 ([#6787](https://github.com/aws-powertools/powertools-lambda-python/issues/6787))
28-
* **deps-dev:** bump boto3-stubs from 1.38.29 to 1.38.30 ([#6772](https://github.com/aws-powertools/powertools-lambda-python/issues/6772))
2961
* **deps-dev:** bump boto3-stubs from 1.38.31 to 1.38.33 ([#6786](https://github.com/aws-powertools/powertools-lambda-python/issues/6786))
30-
* **deps-dev:** bump boto3-stubs from 1.38.28 to 1.38.29 ([#6764](https://github.com/aws-powertools/powertools-lambda-python/issues/6764))
31-
* **deps-dev:** bump requests from 2.32.3 to 2.32.4 ([#6789](https://github.com/aws-powertools/powertools-lambda-python/issues/6789))
32-
* **deps-dev:** bump boto3-stubs from 1.38.33 to 1.38.35 ([#6796](https://github.com/aws-powertools/powertools-lambda-python/issues/6796))
3362
* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.200.0a0 to 2.200.1a0 ([#6766](https://github.com/aws-powertools/powertools-lambda-python/issues/6766))
3463
* **deps-dev:** bump aws-cdk-lib from 2.200.0 to 2.200.1 ([#6767](https://github.com/aws-powertools/powertools-lambda-python/issues/6767))
35-
* **deps-dev:** bump aws-cdk from 2.1018.0 to 2.1018.1 ([#6803](https://github.com/aws-powertools/powertools-lambda-python/issues/6803))
36-
* **deps-dev:** bump boto3-stubs from 1.38.30 to 1.38.31 ([#6776](https://github.com/aws-powertools/powertools-lambda-python/issues/6776))
64+
* **deps-dev:** bump pytest-cov from 6.1.1 to 6.2.1 ([#6800](https://github.com/aws-powertools/powertools-lambda-python/issues/6800))
65+
* **deps-dev:** bump requests from 2.32.3 to 2.32.4 ([#6787](https://github.com/aws-powertools/powertools-lambda-python/issues/6787))
3766

3867

3968
<a name="v3.14.0"></a>
@@ -6648,7 +6677,9 @@
66486677
* Merge pull request [#5](https://github.com/aws-powertools/powertools-lambda-python/issues/5) from jfuss/feat/python38
66496678

66506679

6651-
[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.14.0...HEAD
6680+
[Unreleased]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.15.1...HEAD
6681+
[v3.15.1]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.15.0...v3.15.1
6682+
[v3.15.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.14.0...v3.15.0
66526683
[v3.14.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.13.0...v3.14.0
66536684
[v3.13.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.12.0...v3.13.0
66546685
[v3.12.0]: https://github.com/aws-powertools/powertools-lambda-python/compare/v3.11.0...v3.12.0
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.14.1a5"
3+
VERSION = "3.15.1"

aws_lambda_powertools/utilities/kafka/consumer_records.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import logging
34
from functools import cached_property
45
from typing import TYPE_CHECKING, Any
56

@@ -13,6 +14,8 @@
1314

1415
from aws_lambda_powertools.utilities.kafka.schema_config import SchemaConfig
1516

17+
logger = logging.getLogger(__name__)
18+
1619

1720
class ConsumerRecordRecords(KafkaEventRecordBase):
1821
"""
@@ -31,18 +34,24 @@ def key(self) -> Any:
3134
if not key:
3235
return None
3336

37+
logger.debug("Deserializing key field")
38+
3439
# Determine schema type and schema string
3540
schema_type = None
36-
schema_str = None
41+
schema_value = None
3742
output_serializer = None
3843

3944
if self.schema_config and self.schema_config.key_schema_type:
4045
schema_type = self.schema_config.key_schema_type
41-
schema_str = self.schema_config.key_schema
46+
schema_value = self.schema_config.key_schema
4247
output_serializer = self.schema_config.key_output_serializer
4348

4449
# Always use get_deserializer if None it will default to DEFAULT
45-
deserializer = get_deserializer(schema_type, schema_str)
50+
deserializer = get_deserializer(
51+
schema_type=schema_type,
52+
schema_value=schema_value,
53+
field_metadata=self.key_schema_metadata,
54+
)
4655
deserialized_value = deserializer.deserialize(key)
4756

4857
# Apply output serializer if specified
@@ -57,16 +66,22 @@ def value(self) -> Any:
5766

5867
# Determine schema type and schema string
5968
schema_type = None
60-
schema_str = None
69+
schema_value = None
6170
output_serializer = None
6271

72+
logger.debug("Deserializing value field")
73+
6374
if self.schema_config and self.schema_config.value_schema_type:
6475
schema_type = self.schema_config.value_schema_type
65-
schema_str = self.schema_config.value_schema
76+
schema_value = self.schema_config.value_schema
6677
output_serializer = self.schema_config.value_output_serializer
6778

6879
# Always use get_deserializer if None it will default to DEFAULT
69-
deserializer = get_deserializer(schema_type, schema_str)
80+
deserializer = get_deserializer(
81+
schema_type=schema_type,
82+
schema_value=schema_value,
83+
field_metadata=self.value_schema_metadata,
84+
)
7085
deserialized_value = deserializer.deserialize(value)
7186

7287
# Apply output serializer if specified

aws_lambda_powertools/utilities/kafka/deserializer/avro.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
import io
4+
import logging
5+
from typing import Any
46

57
from avro.io import BinaryDecoder, DatumReader
68
from avro.schema import parse as parse_schema
@@ -9,8 +11,11 @@
911
from aws_lambda_powertools.utilities.kafka.exceptions import (
1012
KafkaConsumerAvroSchemaParserError,
1113
KafkaConsumerDeserializationError,
14+
KafkaConsumerDeserializationFormatMismatch,
1215
)
1316

17+
logger = logging.getLogger(__name__)
18+
1419

1520
class AvroDeserializer(DeserializerBase):
1621
"""
@@ -20,10 +25,11 @@ class AvroDeserializer(DeserializerBase):
2025
a provided Avro schema definition.
2126
"""
2227

23-
def __init__(self, schema_str: str):
28+
def __init__(self, schema_str: str, field_metadata: dict[str, Any] | None = None):
2429
try:
2530
self.parsed_schema = parse_schema(schema_str)
2631
self.reader = DatumReader(self.parsed_schema)
32+
self.field_metatada = field_metadata
2733
except Exception as e:
2834
raise KafkaConsumerAvroSchemaParserError(
2935
f"Invalid Avro schema. Please ensure the provided avro schema is valid: {type(e).__name__}: {str(e)}",
@@ -60,6 +66,13 @@ def deserialize(self, data: bytes | str) -> object:
6066
... except KafkaConsumerDeserializationError as e:
6167
... print(f"Failed to deserialize: {e}")
6268
"""
69+
data_format = self.field_metatada.get("dataFormat") if self.field_metatada else None
70+
71+
if data_format and data_format != "AVRO":
72+
raise KafkaConsumerDeserializationFormatMismatch(f"Expected data is AVRO but you sent {data_format}")
73+
74+
logger.debug("Deserializing data with AVRO format")
75+
6376
try:
6477
value = self._decode_input(data)
6578
bytes_reader = io.BytesIO(value)

aws_lambda_powertools/utilities/kafka/deserializer/default.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from __future__ import annotations
22

33
import base64
4+
import logging
45

56
from aws_lambda_powertools.utilities.kafka.deserializer.base import DeserializerBase
67

8+
logger = logging.getLogger(__name__)
9+
710

811
class DefaultDeserializer(DeserializerBase):
912
"""
@@ -43,4 +46,5 @@ def deserialize(self, data: bytes | str) -> str:
4346
>>> result = deserializer.deserialize(bytes_data)
4447
>>> print(result == bytes_data) # Output: True
4548
"""
49+
logger.debug("Deserializing data with primitives types")
4650
return base64.b64decode(data).decode("utf-8")

aws_lambda_powertools/utilities/kafka/deserializer/deserializer.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,27 @@
1313
_deserializer_cache: dict[str, DeserializerBase] = {}
1414

1515

16-
def _get_cache_key(schema_type: str | object, schema_value: Any) -> str:
16+
def _get_cache_key(schema_type: str | object, schema_value: Any, field_metadata: dict[str, Any]) -> str:
17+
schema_metadata = None
18+
19+
if field_metadata:
20+
schema_metadata = field_metadata.get("schemaId")
21+
1722
if schema_value is None:
18-
return str(schema_type)
23+
schema_hash = f"{str(schema_type)}_{schema_metadata}"
1924

2025
if isinstance(schema_value, str):
26+
hashable_value = f"{schema_value}_{schema_metadata}"
2127
# For string schemas like Avro, hash the content
22-
schema_hash = hashlib.md5(schema_value.encode("utf-8"), usedforsecurity=False).hexdigest()
28+
schema_hash = hashlib.md5(hashable_value.encode("utf-8"), usedforsecurity=False).hexdigest()
2329
else:
2430
# For objects like Protobuf, use the object id
25-
schema_hash = str(id(schema_value))
31+
schema_hash = f"{str(id(schema_value))}_{schema_metadata}"
2632

2733
return f"{schema_type}_{schema_hash}"
2834

2935

30-
def get_deserializer(schema_type: str | object, schema_value: Any) -> DeserializerBase:
36+
def get_deserializer(schema_type: str | object, schema_value: Any, field_metadata: Any) -> DeserializerBase:
3137
"""
3238
Factory function to get the appropriate deserializer based on schema type.
3339
@@ -75,7 +81,7 @@ def get_deserializer(schema_type: str | object, schema_value: Any) -> Deserializ
7581
"""
7682

7783
# Generate a cache key based on schema type and value
78-
cache_key = _get_cache_key(schema_type, schema_value)
84+
cache_key = _get_cache_key(schema_type, schema_value, field_metadata)
7985

8086
# Check if we already have this deserializer in cache
8187
if cache_key in _deserializer_cache:
@@ -87,14 +93,14 @@ def get_deserializer(schema_type: str | object, schema_value: Any) -> Deserializ
8793
# Import here to avoid dependency if not used
8894
from aws_lambda_powertools.utilities.kafka.deserializer.avro import AvroDeserializer
8995

90-
deserializer = AvroDeserializer(schema_value)
96+
deserializer = AvroDeserializer(schema_str=schema_value, field_metadata=field_metadata)
9197
elif schema_type == "PROTOBUF":
9298
# Import here to avoid dependency if not used
9399
from aws_lambda_powertools.utilities.kafka.deserializer.protobuf import ProtobufDeserializer
94100

95-
deserializer = ProtobufDeserializer(schema_value)
101+
deserializer = ProtobufDeserializer(message_class=schema_value, field_metadata=field_metadata)
96102
elif schema_type == "JSON":
97-
deserializer = JsonDeserializer()
103+
deserializer = JsonDeserializer(field_metadata=field_metadata)
98104

99105
else:
100106
# Default to no-op deserializer

aws_lambda_powertools/utilities/kafka/deserializer/json.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22

33
import base64
44
import json
5+
import logging
6+
from typing import Any
57

68
from aws_lambda_powertools.utilities.kafka.deserializer.base import DeserializerBase
7-
from aws_lambda_powertools.utilities.kafka.exceptions import KafkaConsumerDeserializationError
9+
from aws_lambda_powertools.utilities.kafka.exceptions import (
10+
KafkaConsumerDeserializationError,
11+
KafkaConsumerDeserializationFormatMismatch,
12+
)
13+
14+
logger = logging.getLogger(__name__)
815

916

1017
class JsonDeserializer(DeserializerBase):
@@ -15,6 +22,9 @@ class JsonDeserializer(DeserializerBase):
1522
into Python dictionaries.
1623
"""
1724

25+
def __init__(self, field_metadata: dict[str, Any] | None = None):
26+
self.field_metatada = field_metadata
27+
1828
def deserialize(self, data: bytes | str) -> dict:
1929
"""
2030
Deserialize JSON data to a Python dictionary.
@@ -45,6 +55,14 @@ def deserialize(self, data: bytes | str) -> dict:
4555
... except KafkaConsumerDeserializationError as e:
4656
... print(f"Failed to deserialize: {e}")
4757
"""
58+
59+
data_format = self.field_metatada.get("dataFormat") if self.field_metatada else None
60+
61+
if data_format and data_format != "JSON":
62+
raise KafkaConsumerDeserializationFormatMismatch(f"Expected data is JSON but you sent {data_format}")
63+
64+
logger.debug("Deserializing data with JSON format")
65+
4866
try:
4967
return json.loads(base64.b64decode(data).decode("utf-8"))
5068
except Exception as e:

0 commit comments

Comments
 (0)