Skip to content

feat(parser): add support for Pydantic v2 #2733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8daf90c
pydantic v2: initial tests
leandrodamascena Jul 8, 2023
de53dd6
pydantic v2: comment
leandrodamascena Jul 8, 2023
6f8c52b
pydantic v2: new workflow
leandrodamascena Jul 8, 2023
82b166c
pydantic v2: comment
leandrodamascena Jul 8, 2023
7295798
pydantic v2: mypy fix
leandrodamascena Jul 8, 2023
201d877
pydantic v2: fix v2 compability
leandrodamascena Jul 8, 2023
bf6b31a
pydantic v2: fix last things
leandrodamascena Jul 10, 2023
ef98e88
pydantic v2: improving comments
leandrodamascena Jul 10, 2023
f39ea89
pydantic v2: addressing Heitor's feedback
leandrodamascena Jul 10, 2023
15fab06
pydantic v2: creating pydantic v2 specific test
leandrodamascena Jul 10, 2023
e5d6318
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 10, 2023
b0f5fb3
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 10, 2023
6f30f08
pydantic v2: using fixture to clean the code
leandrodamascena Jul 11, 2023
cec6630
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 12, 2023
3d5c9b2
pydanticv2: reverting Optional fields
leandrodamascena Jul 12, 2023
3ee041b
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 12, 2023
7279137
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 13, 2023
07e483d
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 13, 2023
e6abd65
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 17, 2023
ce15df0
Removing the validators. Pydantic bug was fixed
Jul 17, 2023
d4f8171
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 17, 2023
f73a222
Adding pytest ignore messages for Pydantic v2
Jul 17, 2023
1774a1c
Adding pytest ignore messages for Pydantic v2
Jul 17, 2023
e7c1c34
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 18, 2023
f1bb815
Merge branch 'develop' into poc/pydanticv2
leandrodamascena Jul 20, 2023
3a5d26f
pydanticv2: removing duplicated workflow + disabling warning
leandrodamascena Jul 20, 2023
53e4e98
pydanticv2: adding documentation
leandrodamascena Jul 21, 2023
49561b2
Adding cache to disable pydantic warnings
Jul 21, 2023
eef0dc1
Adjusting workflow
Jul 21, 2023
f8470f5
Addressing Heitor's feedback
Jul 21, 2023
fa298d2
Removed codecov upload
Jul 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/quality_check_temp_pydanticv1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Code quality temp - Pydanticv1

# PROCESS
#
# 1. Install all dependencies and spin off containers for all supported Python versions
# 2. Run code formatters and linters (various checks) for code standard
# 3. Run static typing checker for potential bugs
# 4. Run entire test suite for regressions except end-to-end (unit, functional, performance)
# 5. Run static analysis (in addition to CodeQL) for common insecure code practices
# 6. Run complexity baseline to avoid error-prone bugs and keep maintenance lower
# 7. Collect and report on test coverage

# USAGE
#
# Always triggered on new PRs, PR changes and PR merge.


on:
pull_request:
paths:
- "aws_lambda_powertools/**"
- "tests/**"
- "pyproject.toml"
- "poetry.lock"
- "mypy.ini"
branches:
- poc/pydanticv2
push:
paths:
- "aws_lambda_powertools/**"
- "tests/**"
- "pyproject.toml"
- "poetry.lock"
- "mypy.ini"
branches:
- poc/pydanticv2

permissions:
contents: read

jobs:
quality_check:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
env:
PYTHON: "${{ matrix.python-version }}"
permissions:
contents: read # checkout code only
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Install poetry
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: make dev
- name: Formatting and Linting
run: make lint
- name: Static type checking
run: make mypy
- name: Test with pytest
run: make test
- name: Security baseline
run: make security-baseline
- name: Complexity baseline
run: make complexity-baseline
- name: Upload coverage to Codecov
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 3.1.4
with:
file: ./coverage.xml
env_vars: PYTHON
name: aws-lambda-powertools-python-codecov
82 changes: 82 additions & 0 deletions .github/workflows/quality_check_temp_pydanticv2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Code quality temp - Pydanticv2

# PROCESS
#
# 1. Install all dependencies and spin off containers for all supported Python versions
# 2. Run code formatters and linters (various checks) for code standard
# 3. Run static typing checker for potential bugs
# 4. Run entire test suite for regressions except end-to-end (unit, functional, performance)
# 5. Run static analysis (in addition to CodeQL) for common insecure code practices
# 6. Run complexity baseline to avoid error-prone bugs and keep maintenance lower
# 7. Collect and report on test coverage

# USAGE
#
# Always triggered on new PRs, PR changes and PR merge.


on:
pull_request:
paths:
- "aws_lambda_powertools/**"
- "tests/**"
- "pyproject.toml"
- "poetry.lock"
- "mypy.ini"
branches:
- poc/pydanticv2
push:
paths:
- "aws_lambda_powertools/**"
- "tests/**"
- "pyproject.toml"
- "poetry.lock"
- "mypy.ini"
branches:
- poc/pydanticv2

permissions:
contents: read

jobs:
quality_check:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
env:
PYTHON: "${{ matrix.python-version }}"
permissions:
contents: read # checkout code only
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Install poetry
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0 # v4.6.1
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Removing cfn-lint
run: poetry remove cfn-lint
- name: Replacing Pydantic v1 with v2
run: poetry add "pydantic>=2.0"
- name: Install dependencies
run: make dev
- name: Formatting and Linting
run: make lint
- name: Static type checking
run: make mypy
- name: Test with pytest
run: make test
- name: Security baseline
run: make security-baseline
- name: Complexity baseline
run: make complexity-baseline
- name: Upload coverage to Codecov
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 3.1.4
with:
file: ./coverage.xml
env_vars: PYTHON
name: aws-lambda-powertools-python-codecov
30 changes: 30 additions & 0 deletions aws_lambda_powertools/shared/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,33 @@ def extract_event_from_common_models(data: Any) -> Dict | Any:

# Is it a Dataclass? If not return as is
return dataclasses.asdict(data) if dataclasses.is_dataclass(data) else data


def disable_pydantic_v2_warning():
"""
Disables the Pydantic version 2 warning by filtering out the related warnings.

This function checks the version of Pydantic currently installed and if it is version 2,
it filters out the PydanticDeprecationWarning and PydanticDeprecatedSince20 warnings
to suppress them.

Note: This function assumes that Pydantic is already imported.

Usage:
disable_pydantic_v2_warning()
"""
try:
from pydantic import __version__

version = __version__.split(".")

if int(version[0]) == 2:
import warnings

from pydantic import PydanticDeprecatedSince20, PydanticDeprecationWarning

warnings.filterwarnings("ignore", category=PydanticDeprecationWarning)
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)

except ImportError:
pass
6 changes: 3 additions & 3 deletions aws_lambda_powertools/shared/user_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def register_feature_to_session(session, feature):
def register_feature_to_botocore_session(botocore_session, feature):
"""
Register the given feature string to the event system of the provided botocore session

Please notice this function is for patching botocore session and is different from
previous one which is for patching boto3 session

Expand All @@ -127,7 +127,7 @@ def register_feature_to_botocore_session(botocore_session, feature):
------
AttributeError
If the provided session does not have an event system.

Examples
--------
**register data-masking user-agent to botocore session**
Expand All @@ -139,7 +139,7 @@ def register_feature_to_botocore_session(botocore_session, feature):
>>> session = botocore.session.Session()
>>> register_feature_to_botocore_session(botocore_session=session, feature="data-masking")
>>> key_provider = StrictAwsKmsMasterKeyProvider(key_ids=self.keys, botocore_session=session)

"""
try:
botocore_session.register(TARGET_SDK_EVENT, _create_feature_function(feature))
Expand Down
23 changes: 19 additions & 4 deletions aws_lambda_powertools/utilities/batch/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ def _to_batch_type(self, record: dict, event_type: EventType) -> EventSourceData

def _to_batch_type(self, record: dict, event_type: EventType, model: Optional["BatchTypeModels"] = None):
if model is not None:
# If a model is provided, we assume Pydantic is installed and we need to disable v2 warnings
from aws_lambda_powertools.shared.functions import disable_pydantic_v2_warning

disable_pydantic_v2_warning()

return model.parse_obj(record)
return self._DATA_CLASS_MAPPING[event_type](record)

Expand Down Expand Up @@ -500,8 +505,13 @@ def _process_record(self, record: dict) -> Union[SuccessResponse, FailureRespons
# we need to handle that exception differently.
# We check for a public attr in validation errors coming from Pydantic exceptions (subclass or not)
# and we compare if it's coming from the same model that trigger the exception in the first place
model = getattr(exc, "model", None)
if model == self.model:

# Pydantic v1 raises a ValidationError with ErrorWrappers and store the model instance in a class variable.
# Pydantic v2 simplifies this by adding a title variable to store the model name directly.
model = getattr(exc, "model", None) or getattr(exc, "title", None)
model_name = getattr(self.model, "__name__", None)

if model == self.model or model == model_name:
return self._register_model_validation_error_record(record)

return self.failure_handler(record=data, exception=sys.exc_info())
Expand Down Expand Up @@ -644,8 +654,13 @@ async def _async_process_record(self, record: dict) -> Union[SuccessResponse, Fa
# we need to handle that exception differently.
# We check for a public attr in validation errors coming from Pydantic exceptions (subclass or not)
# and we compare if it's coming from the same model that trigger the exception in the first place
model = getattr(exc, "model", None)
if model == self.model:

# Pydantic v1 raises a ValidationError with ErrorWrappers and store the model instance in a class variable.
# Pydantic v2 simplifies this by adding a title variable to store the model name directly.
model = getattr(exc, "model", None) or getattr(exc, "title", None)
model_name = getattr(self.model, "__name__", None)

if model == self.model or model == model_name:
return self._register_model_validation_error_record(record)

return self.failure_handler(record=data, exception=sys.exc_info())
42 changes: 23 additions & 19 deletions aws_lambda_powertools/utilities/parser/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from .alb import AlbModel, AlbRequestContext, AlbRequestContextData
from .apigw import (
from aws_lambda_powertools.shared.functions import disable_pydantic_v2_warning

disable_pydantic_v2_warning()

from .alb import AlbModel, AlbRequestContext, AlbRequestContextData # noqa: E402
from .apigw import ( # noqa: E402
APIGatewayEventAuthorizer,
APIGatewayEventIdentity,
APIGatewayEventRequestContext,
APIGatewayProxyEventModel,
)
from .apigwv2 import (
from .apigwv2 import ( # noqa: E402
APIGatewayProxyEventV2Model,
RequestContextV2,
RequestContextV2Authorizer,
Expand All @@ -14,54 +18,54 @@
RequestContextV2AuthorizerJwt,
RequestContextV2Http,
)
from .cloudformation_custom_resource import (
from .cloudformation_custom_resource import ( # noqa: E402
CloudFormationCustomResourceBaseModel,
CloudFormationCustomResourceCreateModel,
CloudFormationCustomResourceDeleteModel,
CloudFormationCustomResourceUpdateModel,
)
from .cloudwatch import (
from .cloudwatch import ( # noqa: E402
CloudWatchLogsData,
CloudWatchLogsDecode,
CloudWatchLogsLogEvent,
CloudWatchLogsModel,
)
from .dynamodb import (
from .dynamodb import ( # noqa: E402
DynamoDBStreamChangedRecordModel,
DynamoDBStreamModel,
DynamoDBStreamRecordModel,
)
from .event_bridge import EventBridgeModel
from .kafka import (
from .event_bridge import EventBridgeModel # noqa: E402
from .kafka import ( # noqa: E402
KafkaBaseEventModel,
KafkaMskEventModel,
KafkaRecordModel,
KafkaSelfManagedEventModel,
)
from .kinesis import (
from .kinesis import ( # noqa: E402
KinesisDataStreamModel,
KinesisDataStreamRecord,
KinesisDataStreamRecordPayload,
)
from .kinesis_firehose import (
from .kinesis_firehose import ( # noqa: E402
KinesisFirehoseModel,
KinesisFirehoseRecord,
KinesisFirehoseRecordMetadata,
)
from .kinesis_firehose_sqs import KinesisFirehoseSqsModel, KinesisFirehoseSqsRecord
from .lambda_function_url import LambdaFunctionUrlModel
from .s3 import (
from .kinesis_firehose_sqs import KinesisFirehoseSqsModel, KinesisFirehoseSqsRecord # noqa: E402
from .lambda_function_url import LambdaFunctionUrlModel # noqa: E402
from .s3 import ( # noqa: E402
S3EventNotificationEventBridgeDetailModel,
S3EventNotificationEventBridgeModel,
S3EventNotificationObjectModel,
S3Model,
S3RecordModel,
)
from .s3_event_notification import (
from .s3_event_notification import ( # noqa: E402
S3SqsEventNotificationModel,
S3SqsEventNotificationRecordModel,
)
from .s3_object_event import (
from .s3_object_event import ( # noqa: E402
S3ObjectConfiguration,
S3ObjectContext,
S3ObjectLambdaEvent,
Expand All @@ -71,7 +75,7 @@
S3ObjectUserIdentity,
S3ObjectUserRequest,
)
from .ses import (
from .ses import ( # noqa: E402
SesMail,
SesMailCommonHeaders,
SesMailHeaders,
Expand All @@ -82,9 +86,9 @@
SesReceiptVerdict,
SesRecordModel,
)
from .sns import SnsModel, SnsNotificationModel, SnsRecordModel
from .sqs import SqsAttributesModel, SqsModel, SqsMsgAttributeModel, SqsRecordModel
from .vpc_lattice import VpcLatticeModel
from .sns import SnsModel, SnsNotificationModel, SnsRecordModel # noqa: E402
from .sqs import SqsAttributesModel, SqsModel, SqsMsgAttributeModel, SqsRecordModel # noqa: E402
from .vpc_lattice import VpcLatticeModel # noqa: E402

__all__ = [
"APIGatewayProxyEventV2Model",
Expand Down
Loading