-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
[BUGFIX] Ensure that Checkpoint
deserializes proper action subclass
#9701
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import json | ||
import logging | ||
from typing import Type, Union | ||
from typing import Type | ||
from unittest import mock | ||
|
||
import pytest | ||
|
@@ -23,7 +23,7 @@ | |
) | ||
from great_expectations.checkpoint.util import smtplib | ||
from great_expectations.checkpoint.v1_checkpoint import Checkpoint, CheckpointResult | ||
from great_expectations.compatibility.pydantic import BaseModel, Field, ValidationError | ||
from great_expectations.compatibility.pydantic import ValidationError | ||
from great_expectations.core.expectation_validation_result import ( | ||
ExpectationSuiteValidationResult, | ||
) | ||
|
@@ -37,7 +37,6 @@ | |
ExpectationSuiteIdentifier, | ||
ValidationResultIdentifier, | ||
) | ||
from great_expectations.render.renderer.renderer import Renderer | ||
from great_expectations.util import is_library_loadable | ||
|
||
logger = logging.getLogger(__name__) | ||
|
@@ -764,52 +763,6 @@ def test_action_deserialization( | |
actual = action_class.parse_obj(serialized_action) | ||
assert isinstance(actual, action_class) | ||
|
||
@pytest.mark.parametrize( | ||
"action_class, init_params", | ||
[(k, v) for k, v in ACTION_INIT_PARAMS.items()], | ||
ids=[k.__name__ for k in ACTION_INIT_PARAMS], | ||
) | ||
@pytest.mark.unit | ||
def test_action_deserialization_within_parent_class( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we can test this with Checkpoint, I'd like to remove this test |
||
self, action_class: ValidationAction, init_params: dict | ||
): | ||
""" | ||
The matter of deserializing an Action into the relevant subclass is the responsibility of | ||
the Checkpoint in production code. | ||
|
||
In order to test Actions alone, we utilize a dummy class with a single field to ensure | ||
properly implementation of Pydantic discriminated unions. | ||
|
||
Ref: https://docs.pydantic.dev/1.10/usage/types/#discriminated-unions-aka-tagged-unions | ||
""" | ||
|
||
# This somewhat feels like we're testing Pydantic code but it's the only way to ensure that | ||
# we've properly implemented the Pydantic discriminated union feature. | ||
class DummyClassWithActionChild(BaseModel): | ||
class Config: | ||
# Due to limitations of Pydantic V1, we need to specify the json_encoders at every level of the hierarchy # noqa: E501 | ||
json_encoders = {Renderer: lambda r: r.serialize()} | ||
|
||
action: Union[ | ||
APINotificationAction, | ||
EmailAction, | ||
MicrosoftTeamsNotificationAction, | ||
OpsgenieAlertAction, | ||
PagerdutyAlertAction, | ||
SlackNotificationAction, | ||
SNSNotificationAction, | ||
StoreValidationResultAction, | ||
UpdateDataDocsAction, | ||
] = Field(..., discriminator="type") | ||
|
||
action = action_class(**init_params) | ||
instance = DummyClassWithActionChild(action=action) | ||
|
||
json_dict = instance.json() | ||
parsed_action = DummyClassWithActionChild.parse_raw(json_dict) | ||
|
||
assert isinstance(parsed_action.action, action_class) | ||
|
||
|
||
class TestV1ActionRun: | ||
@pytest.fixture | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
from great_expectations.checkpoint.actions import ( | ||
MicrosoftTeamsNotificationAction, | ||
SlackNotificationAction, | ||
UpdateDataDocsAction, | ||
ValidationAction, | ||
) | ||
from great_expectations.checkpoint.v1_checkpoint import Checkpoint, CheckpointResult | ||
|
@@ -367,6 +368,37 @@ def test_checkpoint_deserialization_failure( | |
|
||
assert expected_error in str(e.value) | ||
|
||
@pytest.mark.unit | ||
def test_checkpoint_deserialization_with_actions(self, mocker: MockerFixture): | ||
# Arrange | ||
context = mocker.Mock(spec=AbstractDataContext) | ||
context.validation_definition_store.get.return_value = mocker.Mock( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deserializing a checkpoint hits the validation definition store? |
||
spec=ValidationDefinition | ||
) | ||
set_context(context) | ||
|
||
# Act | ||
serialized_checkpoint = { | ||
"actions": [ | ||
{"site_names": [], "type": "update_data_docs"}, | ||
{"slack_webhook": "test", "type": "slack"}, | ||
{"teams_webhook": "test", "type": "microsoft"}, | ||
], | ||
"id": "e7d1f462-821b-429c-8086-cca80eeea5e9", | ||
"name": "my_checkpoint", | ||
"result_format": "SUMMARY", | ||
"validation_definitions": [ | ||
{"id": "3fb9ce09-a8fb-44d6-8abd-7d699443f6a1", "name": "my_validation_def"} | ||
], | ||
} | ||
checkpoint = Checkpoint.parse_obj(serialized_checkpoint) | ||
|
||
# Assert | ||
assert len(checkpoint.actions) == 3 | ||
assert isinstance(checkpoint.actions[0], UpdateDataDocsAction) | ||
assert isinstance(checkpoint.actions[1], SlackNotificationAction) | ||
assert isinstance(checkpoint.actions[2], MicrosoftTeamsNotificationAction) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably add another test here to verify that (pseudocode) serialized_checkpoint == serialize(deserialize(checkpoint)) |
||
|
||
|
||
class TestCheckpointResult: | ||
suite_name: str = "my_suite" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than ignoring all these warnings I would add these (or their immediate parent) here.
great_expectations/pyproject.toml
Lines 381 to 387 in 7993a68
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually sorry, you need
Checkpoint
here, orpydantic.v1.BaseModel
not these actions.