diff --git a/.changes/unreleased/Dependencies-20231221-130418.yaml b/.changes/unreleased/Dependencies-20231221-130418.yaml new file mode 100644 index 00000000..e478c08d --- /dev/null +++ b/.changes/unreleased/Dependencies-20231221-130418.yaml @@ -0,0 +1,6 @@ +kind: Dependencies +body: Add support for pydantic 2 (and continue support for pydantic 1) +time: 2023-12-21T13:04:18.792592-08:00 +custom: + Author: QMalcolm bernardcooke53 esciara + PR: "134" diff --git a/.github/workflows/ci-pre-commit.yaml b/.github/workflows/ci-pre-commit.yaml index e59004fe..bfd95a99 100644 --- a/.github/workflows/ci-pre-commit.yaml +++ b/.github/workflows/ci-pre-commit.yaml @@ -16,5 +16,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-python-env + # This step is necessary so long as we're allowing Pydantic 1 and Pydantic 2 via shimming + - name: Force Pydantic 1 + run: hatch run dev-env:pip install "pydantic~=1.10" - name: Run Pre-commit Hooks run: hatch run dev-env:pre-commit run --show-diff-on-failure --color=always --all-files diff --git a/.github/workflows/ci-pytest.yaml b/.github/workflows/ci-pytest.yaml index 20c9bcaa..77c41fc7 100644 --- a/.github/workflows/ci-pytest.yaml +++ b/.github/workflows/ci-pytest.yaml @@ -11,11 +11,12 @@ on: jobs: pytest: - name: Run Tests / Python ${{ matrix.python-version }} + name: Run Tests / Python ${{ matrix.python-version }} / Pydantic ~= ${{ matrix.pydantic-version }} strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] + pydantic-version: ["1.10", "2.0"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -26,5 +27,7 @@ jobs: - name: Install Hatch shell: bash run: pip3 install hatch + - name: Set pydantic Version ~= ${{ matrix.pydantic-version }} + run: hatch run dev-env:pip install "pydantic~=${{ matrix.pydantic-version }}" - name: Run Python Tests run: hatch run dev-env:pytest tests diff --git a/dbt_semantic_interfaces/dataclass_serialization.py b/dbt_semantic_interfaces/dataclass_serialization.py index 44ada7b1..8207f4a3 100644 --- a/dbt_semantic_interfaces/dataclass_serialization.py +++ b/dbt_semantic_interfaces/dataclass_serialization.py @@ -19,11 +19,10 @@ get_type_hints, ) -import pydantic -from pydantic import BaseModel from typing_extensions import TypeAlias from dbt_semantic_interfaces.pretty_print import pformat_big_objects +from pydantic_shim import BaseModel, create_model logger = logging.getLogger(__name__) @@ -347,7 +346,7 @@ def _convert_dataclass_type_to_pydantic_type( logger.debug( f"Creating Pydantic model {class_name} with fields:\n{pformat_big_objects(fields_for_pydantic_model)}" ) - pydantic_model = pydantic.create_model(class_name, **fields_for_pydantic_model) # type: ignore + pydantic_model = create_model(class_name, **fields_for_pydantic_model) # type: ignore logger.debug(f"Finished creating Pydantic model {class_name}") logger.debug(f"Finished converting {dataclass_type.__name__} to a pydantic class") return pydantic_model diff --git a/dbt_semantic_interfaces/implementations/base.py b/dbt_semantic_interfaces/implementations/base.py index 31c9faeb..25195649 100644 --- a/dbt_semantic_interfaces/implementations/base.py +++ b/dbt_semantic_interfaces/implementations/base.py @@ -5,13 +5,12 @@ from abc import ABC, abstractmethod from typing import Any, Callable, ClassVar, Generator, Generic, Type, TypeVar -from pydantic import BaseModel, root_validator - from dbt_semantic_interfaces.errors import ParsingException from dbt_semantic_interfaces.parsing.yaml_loader import ( PARSING_CONTEXT_KEY, ParsingContext, ) +from pydantic_shim import BaseModel, root_validator # Type alias for the implicit "Any" type used as input and output for Pydantic's parsing API PydanticParseableValueType = Any # type: ignore[misc] diff --git a/dbt_semantic_interfaces/implementations/export.py b/dbt_semantic_interfaces/implementations/export.py index 7a06d1b0..c586c6e0 100644 --- a/dbt_semantic_interfaces/implementations/export.py +++ b/dbt_semantic_interfaces/implementations/export.py @@ -2,7 +2,6 @@ from typing import Optional -from pydantic import Field from typing_extensions import override from dbt_semantic_interfaces.implementations.base import HashableBaseModel @@ -11,6 +10,7 @@ from dbt_semantic_interfaces.type_enums.export_destination_type import ( ExportDestinationType, ) +from pydantic_shim import Field class PydanticExportConfig(HashableBaseModel, ProtocolHint[ExportConfig]): diff --git a/dbt_semantic_interfaces/implementations/metric.py b/dbt_semantic_interfaces/implementations/metric.py index 366648a4..1bedf40a 100644 --- a/dbt_semantic_interfaces/implementations/metric.py +++ b/dbt_semantic_interfaces/implementations/metric.py @@ -2,8 +2,6 @@ from typing import List, Optional, Sequence -from pydantic import Field - from dbt_semantic_interfaces.enum_extension import assert_values_exhausted from dbt_semantic_interfaces.errors import ParsingException from dbt_semantic_interfaces.implementations.base import ( @@ -22,6 +20,7 @@ MetricType, TimeGranularity, ) +from pydantic_shim import Field class PydanticMetricInputMeasure(PydanticCustomInputParser, HashableBaseModel): diff --git a/dbt_semantic_interfaces/implementations/project_configuration.py b/dbt_semantic_interfaces/implementations/project_configuration.py index 3e52fd91..93693502 100644 --- a/dbt_semantic_interfaces/implementations/project_configuration.py +++ b/dbt_semantic_interfaces/implementations/project_configuration.py @@ -3,7 +3,6 @@ from typing import List, Optional from importlib_metadata import version -from pydantic import validator from typing_extensions import override from dbt_semantic_interfaces.implementations.base import ( @@ -20,6 +19,7 @@ ) from dbt_semantic_interfaces.protocols import ProtocolHint from dbt_semantic_interfaces.protocols.project_configuration import ProjectConfiguration +from pydantic_shim import validator class PydanticProjectConfiguration(HashableBaseModel, ModelWithMetadataParsing, ProtocolHint[ProjectConfiguration]): diff --git a/dbt_semantic_interfaces/implementations/semantic_model.py b/dbt_semantic_interfaces/implementations/semantic_model.py index 45b90ae0..933acb55 100644 --- a/dbt_semantic_interfaces/implementations/semantic_model.py +++ b/dbt_semantic_interfaces/implementations/semantic_model.py @@ -2,7 +2,6 @@ from typing import Any, List, Optional, Sequence -from pydantic import validator from typing_extensions import override from dbt_semantic_interfaces.implementations.base import ( @@ -25,6 +24,7 @@ SemanticModelReference, TimeDimensionReference, ) +from pydantic_shim import validator class NodeRelation(HashableBaseModel): diff --git a/dbt_semantic_interfaces/validations/validator_helpers.py b/dbt_semantic_interfaces/validations/validator_helpers.py index 6ea60f48..1f816cc3 100644 --- a/dbt_semantic_interfaces/validations/validator_helpers.py +++ b/dbt_semantic_interfaces/validations/validator_helpers.py @@ -20,7 +20,6 @@ ) import click -from pydantic import BaseModel, Extra from dbt_semantic_interfaces.implementations.base import FrozenBaseModel from dbt_semantic_interfaces.protocols import Metadata, SemanticManifestT, SemanticModel @@ -30,6 +29,7 @@ SemanticModelReference, ) from dbt_semantic_interfaces.type_enums import DimensionType +from pydantic_shim import BaseModel, Extra VALIDATE_SAFELY_ERROR_STR_TMPLT = ". Issue occurred in method `{method_name}` called with {arguments_str}" ValidationContextJSON = Dict[str, Union[str, int, None]] diff --git a/pydantic_shim.py b/pydantic_shim.py new file mode 100644 index 00000000..3ea00720 --- /dev/null +++ b/pydantic_shim.py @@ -0,0 +1,26 @@ +from importlib.metadata import version + +pydantic_version = version("pydantic") +# Pydantic uses semantic versioning, i.e. .., and we need to know the major +pydantic_major = pydantic_version.split(".")[0] + +if pydantic_major == "1": + from pydantic import ( # type: ignore # noqa + BaseModel, + Extra, + Field, + create_model, + root_validator, + validator, + ) +elif pydantic_major == "2": + from pydantic.v1 import ( # type: ignore # noqa + BaseModel, + Extra, + Field, + create_model, + root_validator, + validator, + ) +else: + raise RuntimeError(f"Currently only pydantic 1 and 2 are supported, found pydantic {pydantic_version}") diff --git a/pyproject.toml b/pyproject.toml index a892f39d..aa6611c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "pydantic~=1.10", + "pydantic>=1.10,<3", "jsonschema~=4.0", "PyYAML~=6.0", "more-itertools>=8.0,<11.0",