Skip to content
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

[MAINTENANCE] Pydantic 2.0 support #8604

Merged
merged 39 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f6071ee
Remove upper bound on requirements pin
NathanFarmer Aug 22, 2023
260913e
WIP fixing broken imports
NathanFarmer Aug 22, 2023
36ae081
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
NathanFarmer Sep 5, 2023
d8c9f3a
ban pydantic
NathanFarmer Sep 5, 2023
bf5d390
ruff fixes
NathanFarmer Sep 5, 2023
43ea3a9
fix or ignore linter warnings
NathanFarmer Sep 5, 2023
10f3433
fmt
NathanFarmer Sep 5, 2023
ab0eacf
Update more imports
NathanFarmer Sep 5, 2023
c8b57bf
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
Kilo59 Sep 6, 2023
d5bb4ee
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
Kilo59 Sep 6, 2023
16205b0
fix bad merge
Kilo59 Sep 6, 2023
1d3c8a8
v1 imports
Kilo59 Sep 6, 2023
f860670
add `pydantic-v1` test
Kilo59 Sep 6, 2023
0ceaf9a
`fields`, `networks`
Kilo59 Sep 6, 2023
0bd3cdc
update packaging tests
Kilo59 Sep 6, 2023
080019c
type ignores
Kilo59 Sep 7, 2023
e8812be
no-redef
Kilo59 Sep 7, 2023
06f473f
re-order type-check to before marker coverage
Kilo59 Sep 7, 2023
f0e9f84
swap mypy ruff ignore comment order
Kilo59 Sep 7, 2023
9767e1b
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
Kilo59 Sep 7, 2023
f006a0f
remove contrib upperbound
Kilo59 Sep 7, 2023
329139e
remove unused type ignore
Kilo59 Sep 7, 2023
c80ce4b
typing import
Kilo59 Sep 7, 2023
b32cf30
ignore `render_configuration.py` warning
Kilo59 Sep 7, 2023
c41687b
update spark type ignores
Kilo59 Sep 7, 2023
fe07ce5
remove type ignore
Kilo59 Sep 7, 2023
46519ee
ignore for spark dbfs tests
Kilo59 Sep 7, 2023
f778423
remove unused test ignores
Kilo59 Sep 7, 2023
c0b8cfa
comment
Kilo59 Sep 7, 2023
ec2e6f7
ignore for `GxAgentEnvVars()`
Kilo59 Sep 7, 2023
0bd7462
unused
Kilo59 Sep 7, 2023
f4d776e
ignore `GxAgentEnvVar` warning
Kilo59 Sep 7, 2023
9bb8a5e
update source file ignores
Kilo59 Sep 7, 2023
8fe9a31
cleanup
Kilo59 Sep 7, 2023
ee24f78
update min pydantic version
Kilo59 Sep 7, 2023
4d80172
update min version and add notes
Kilo59 Sep 7, 2023
80eab95
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
Kilo59 Sep 7, 2023
0670e34
remove type ignore
Kilo59 Sep 7, 2023
35f4025
Merge branch 'develop' into mod-coding-pydantic-v2-compatibility
Kilo59 Sep 7, 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
25 changes: 22 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,12 @@ jobs:
run: pip install -r requirements-types.txt -r reqs/requirements-dev-contrib.txt
- run: invoke lint --no-fmt
- run: invoke fmt --check
- name: Marker-coverage-check
run: |
invoke marker-coverage
- name: Type-check
run: |
invoke type-check --ci --pretty
invoke type-check --ci --pretty --check-stub-sources
- name: Marker-coverage-check
run: invoke marker-coverage
Comment on lines 59 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swapped the order of these to get faster feedback on typing issues


docs-changes:
# check whether docs were changed
Expand Down Expand Up @@ -405,6 +404,24 @@ jobs:
- name: Run the tests
run: invoke ci-tests -m unit --xdist --slowest=10 --timeout=2.0

pydantic-v1:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for our min version of pydantic now that the other tests will be installing v2

needs: [unit-tests, static-analysis]
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.8"
cache: "pip"
cache-dependency-path: reqs/requirements-dev-test.txt
- name: Install dependencies
run: pip install . -c ci/constraints-test/pydantic-v1-install.txt -r reqs/requirements-dev-test.txt
- name: Run the tests
run: invoke ci-tests -m unit --xdist --slowest=10 --timeout=2.0

airflow220-min-versions:
needs: [unit-tests, static-analysis]
runs-on: ubuntu-latest
Expand Down Expand Up @@ -481,6 +498,7 @@ jobs:
py39-min-versions,
py310-min-versions,
py311-min-versions,
pydantic-v1,
airflow220-min-versions,
import_gx,
usage_stats_integration
Expand Down Expand Up @@ -528,6 +546,7 @@ jobs:
py39-min-versions,
py310-min-versions,
py311-min-versions,
pydantic-v1,
airflow220-min-versions,
import_gx,
usage_stats_integration,
Expand Down
3 changes: 3 additions & 0 deletions ci/constraints-test/pydantic-v1-install.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# this is higher than our min version but is the earliest version that actually works
# TODO: update our min version or fix gx for our current min version
pydantic==1.10.8
Comment on lines +1 to +3
Copy link
Contributor

@Kilo59 Kilo59 Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our min version of pydantic in our requirements file is 1.9 but gx actually breaks at import time on this version, and it looks like we were never testing against this version in our CI.
This does not appear related to moving to pydantic v2.

I will create followup tickets for taking the action here.

2 changes: 1 addition & 1 deletion contrib/cli/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ black[jupyter]==23.3.0 # Linting / code style
Click>=7.1.2 # CLI tooling
cookiecutter==2.1.1 # Project templating
mypy==1.5.1 # Type checker
pydantic>=1.0,<2.0 # Needed for mypy plugin
pydantic>=1.0 # Needed for mypy plugin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should make me 1.10.8?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't understand why we have this entry in cli/requirements at all.
It being in our core requirements should be enough.

pytest>=5.3.5 # Test framework
ruff==0.0.284 # Linting / code style
twine==3.7.1 # Packaging
Expand Down
3 changes: 1 addition & 2 deletions great_expectations/agent/actions/agent_action.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from abc import abstractmethod
from typing import Generic, Sequence, TypeVar

from pydantic import BaseModel

from great_expectations.agent.models import CreatedResource, Event
from great_expectations.compatibility.pydantic import BaseModel
from great_expectations.data_context import CloudDataContext


Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from uuid import UUID

import pydantic

from great_expectations.agent.actions import ActionResult, AgentAction
from great_expectations.agent.config import GxAgentEnvVars
from great_expectations.agent.models import DraftDatasourceConfigEvent
from great_expectations.compatibility import pydantic
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core.http import create_session

Expand Down Expand Up @@ -33,7 +32,7 @@ def run(self, event: DraftDatasourceConfigEvent, id: str) -> ActionResult:

def get_draft_config(self, config_id: UUID) -> dict:
try:
config = GxAgentEnvVars()
config = GxAgentEnvVars() # type: ignore[call-arg] # args pulled from env vars
except pydantic.ValidationError as validation_err:
raise RuntimeError(
f"Missing or badly formed environment variable\n{validation_err.errors()}"
Expand Down
5 changes: 2 additions & 3 deletions great_expectations/agent/actions/list_table_names.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import TYPE_CHECKING, List

import pydantic

from great_expectations.agent.actions.agent_action import (
ActionResult,
AgentAction,
Expand All @@ -10,6 +8,7 @@
from great_expectations.agent.models import (
ListTableNamesEvent,
)
from great_expectations.compatibility import pydantic
from great_expectations.compatibility.sqlalchemy import inspect
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core.http import create_session
Expand Down Expand Up @@ -47,7 +46,7 @@ def _add_or_update_table_names_list(
self, datasource_id: str, table_names: List[str]
) -> None:
try:
cloud_config = GxAgentEnvVars()
cloud_config = GxAgentEnvVars() # type: ignore[call-arg] # args pulled from env vars
except pydantic.ValidationError as validation_err:
raise RuntimeError(
f"Missing or badly formed environment variable\n{validation_err.errors()}"
Expand Down
7 changes: 3 additions & 4 deletions great_expectations/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
from functools import partial
from typing import TYPE_CHECKING, Dict, Optional

import pydantic
from pydantic import AmqpDsn, AnyUrl

from great_expectations import get_context
from great_expectations.agent.actions.agent_action import ActionResult
from great_expectations.agent.config import GxAgentEnvVars
Expand All @@ -32,6 +29,8 @@
JobStatus,
UnknownEvent,
)
from great_expectations.compatibility import pydantic
from great_expectations.compatibility.pydantic import AmqpDsn, AnyUrl
from great_expectations.core.http import create_session
from great_expectations.data_context.cloud_constants import CLOUD_DEFAULT_BASE_URL

Expand Down Expand Up @@ -222,7 +221,7 @@ def _get_config(cls) -> GXAgentConfig:
# ensure we have all required env variables, and provide a useful error if not

try:
env_vars = GxAgentEnvVars()
env_vars = GxAgentEnvVars() # type: ignore[call-arg] # args pulled from env vars
except pydantic.ValidationError as validation_err:
raise GXAgentError(
f"Missing or badly formed environment variable\n{validation_err.errors()}"
Expand Down
5 changes: 2 additions & 3 deletions great_expectations/agent/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pydantic
from pydantic import AnyUrl

from great_expectations.compatibility import pydantic
from great_expectations.compatibility.pydantic import AnyUrl
from great_expectations.data_context.cloud_constants import CLOUD_DEFAULT_BASE_URL


Expand Down
2 changes: 1 addition & 1 deletion great_expectations/agent/message_service/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from json import JSONDecodeError
from typing import Callable, Coroutine, Union

import pydantic
from pika.exceptions import AMQPError, ChannelError

from great_expectations.agent.message_service.asyncio_rabbit_mq_client import (
AsyncRabbitMQClient,
OnMessagePayload,
)
from great_expectations.agent.models import Event, UnknownEvent
from great_expectations.compatibility import pydantic


@dataclass(frozen=True)
Expand Down
3 changes: 2 additions & 1 deletion great_expectations/agent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from typing import Literal, Sequence, Union
from uuid import UUID

from pydantic import BaseModel, Extra, Field
from typing_extensions import Annotated

from great_expectations.compatibility.pydantic import BaseModel, Extra, Field


class AgentBaseModel(BaseModel):
class Config:
Expand Down
56 changes: 56 additions & 0 deletions great_expectations/compatibility/pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pydantic

from great_expectations.compatibility.not_imported import (
is_version_greater_or_equal,
)

if is_version_greater_or_equal(version=pydantic.VERSION, compare_version="2.0.0"):
# TODO: don't use star imports
from pydantic.v1 import * # noqa: F403
from pydantic.v1 import (
AnyUrl,
UrlError,
error_wrappers,
errors,
fields,
generics,
json,
networks,
schema,
typing,
)
Comment on lines +10 to +21
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it work if I'm creating a V2 Base Model and I want to use one of these classes? I guess we can cross that bridge when we get there.

Copy link
Contributor

@Kilo59 Kilo59 Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't be possible yet, without some conditional imports.

We would need some kind of pydantic_v2 compatibility module that works like our other optional dependencies (sqlalchemy).
Unless we just decide to drop pydantic v1, but I think we need to let the ecosystem catch up before we can do that.

from pydantic.v1.generics import GenericModel
from pydantic.v1.main import ModelMetaclass

else:
# TODO: don't use star imports
from pydantic import * # type: ignore[assignment,no-redef] # noqa: F403
from pydantic import ( # type: ignore[no-redef]
AnyUrl,
UrlError,
error_wrappers,
errors,
fields,
generics,
json,
networks,
schema,
typing,
)
from pydantic.generics import GenericModel # type: ignore[no-redef]
from pydantic.main import ModelMetaclass # type: ignore[no-redef]

__all__ = [
"AnyUrl",
"error_wrappers",
"errors",
"fields",
"GenericModel",
"generics",
"json",
"ModelMetaclass",
"networks",
"schema",
"typing",
"UrlError",
]
NathanFarmer marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 1 addition & 2 deletions great_expectations/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@
import dateutil.parser
import numpy as np
import pandas as pd
import pydantic
from IPython import get_ipython

from great_expectations import exceptions as gx_exceptions
from great_expectations.compatibility import pyspark, sqlalchemy
from great_expectations.compatibility import pydantic, pyspark, sqlalchemy
from great_expectations.compatibility.sqlalchemy import (
SQLALCHEMY_NOT_IMPORTED,
LegacyRow,
Expand Down
3 changes: 1 addition & 2 deletions great_expectations/datasource/data_connector/batch_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import logging
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Sequence, Union

from pydantic import StrictInt, StrictStr

import great_expectations.exceptions as gx_exceptions
from great_expectations.compatibility.pydantic import StrictInt, StrictStr
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core.id_dict import IDDict

Expand Down
21 changes: 12 additions & 9 deletions great_expectations/datasource/fluent/batch_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
Union,
)

import pydantic
from pydantic import StrictStr
from pydantic.json import pydantic_encoder
from pydantic.schema import default_ref_template
from great_expectations.compatibility import pydantic
from great_expectations.compatibility.pydantic import Field, StrictStr
from great_expectations.compatibility.pydantic import json as pydantic_json
from great_expectations.compatibility.pydantic import (
schema as pydantic_schema,
)

# default_ref_template
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core._docs_decorators import public_api
from great_expectations.datasource.data_connector.batch_filter import (
Expand Down Expand Up @@ -59,17 +62,17 @@ class BatchRequest(pydantic.BaseModel):
BatchRequest
"""

datasource_name: StrictStr = pydantic.Field(
datasource_name: StrictStr = Field(
...,
allow_mutation=False,
description="The name of the Datasource used to connect to the data.",
)
data_asset_name: StrictStr = pydantic.Field(
data_asset_name: StrictStr = Field(
...,
allow_mutation=False,
description="The name of the Data Asset used to connect to the data.",
)
options: BatchRequestOptions = pydantic.Field(
options: BatchRequestOptions = Field(
default_factory=dict,
allow_mutation=True,
description=(
Expand Down Expand Up @@ -215,7 +218,7 @@ def schema_json(
cls,
*,
by_alias: bool = True,
ref_template: str = default_ref_template,
ref_template: str = pydantic_schema.default_ref_template,
**dumps_kwargs: Any,
) -> str:
# batch_slice is only a property/pydantic setter, so we need to add a field
Expand All @@ -230,7 +233,7 @@ def schema_json(
)
result = cls.__config__.json_dumps(
cls.schema(by_alias=by_alias, ref_template=ref_template),
default=pydantic_encoder,
default=pydantic_json.pydantic_encoder,
**dumps_kwargs,
)
# revert model changes
Expand Down
4 changes: 2 additions & 2 deletions great_expectations/datasource/fluent/batch_request.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, Dict, Optional

import pydantic
from pydantic import StrictStr
from typing_extensions import TypeAlias

from great_expectations.compatibility import pydantic
from great_expectations.compatibility.pydantic import StrictStr
from great_expectations.datasource.data_connector.batch_filter import BatchSlice

BatchRequestOptions: TypeAlias = Dict[StrictStr, Any]
Expand Down
7 changes: 4 additions & 3 deletions great_expectations/datasource/fluent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
overload,
)

from pydantic import Extra, Field, validator
from ruamel.yaml import YAML

from great_expectations.compatibility.pydantic import Extra, Field, validator
from great_expectations.compatibility.sqlalchemy import TextClause
from great_expectations.compatibility.typing_extensions import override
from great_expectations.datasource.fluent.constants import (
Expand All @@ -40,8 +40,9 @@
)

if TYPE_CHECKING:
from pydantic.error_wrappers import ErrorDict as PydanticErrorDict

from great_expectations.compatibility.pydantic.error_wrappers import (
ErrorDict as PydanticErrorDict,
)
from great_expectations.datasource.fluent.fluent_base_model import (
AbstractSetIntStr,
MappingIntStrAny,
Expand Down
3 changes: 1 addition & 2 deletions great_expectations/datasource/fluent/config_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import warnings
from typing import TYPE_CHECKING, Mapping

from pydantic import SecretStr

from great_expectations.compatibility.pydantic import SecretStr
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core.config_substitutor import TEMPLATE_STR_REGEX

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import re
from typing import TYPE_CHECKING, Callable, ClassVar, List, Optional, Type

import pydantic

from great_expectations.compatibility import pydantic
from great_expectations.compatibility.typing_extensions import override
from great_expectations.core.batch_spec import AzureBatchSpec, PathBatchSpec
from great_expectations.datasource.data_connector.util import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import re
from typing import TYPE_CHECKING, Callable, ClassVar, List, Optional, Type

import pydantic

from great_expectations.compatibility import pydantic
from great_expectations.compatibility.typing_extensions import override
from great_expectations.datasource.data_connector.util import (
get_filesystem_one_level_directory_glob_path_list,
Expand Down
Loading