From 5c27305505a79569e17c3d975f94229984f728b3 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 17 Jul 2023 22:09:37 -0400 Subject: [PATCH] Support Pydantic v2 with v1 backport (#326) Simpler migration to pydantic v2 --- Makefile | 3 ++- pyproject.toml | 4 +-- setup.py | 4 +-- spectree/_pydantic.py | 37 ++++++++++++++++++++++++++++ spectree/_types.py | 3 ++- spectree/config.py | 3 +-- spectree/models.py | 2 +- spectree/plugins/falcon_plugin.py | 2 +- spectree/plugins/flask_plugin.py | 2 +- spectree/plugins/quart_plugin.py | 2 +- spectree/plugins/starlette_plugin.py | 2 +- spectree/response.py | 3 +-- spectree/utils.py | 3 +-- tests/common.py | 3 +-- tests/test_config.py | 2 +- tests/test_response.py | 2 +- tests/test_spec.py | 2 +- 17 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 spectree/_pydantic.py diff --git a/Makefile b/Makefile index bbd6c91d..3c763099 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .DEFAULT_GOAL:=install SOURCE_FILES=spectree tests examples +MYPY_SOURCE_FILES=spectree tests # temporary install: pip install -U -e .[email,quart,flask,falcon,starlette,dev] @@ -43,6 +44,6 @@ lint: isort --check --diff --project=spectree ${SOURCE_FILES} black --check --diff ${SOURCE_FILES} flake8 ${SOURCE_FILES} --count --show-source --statistics --ignore=D203,E203,W503 --max-line-length=88 --max-complexity=15 - mypy --install-types --non-interactive ${SOURCE_FILES} + mypy --install-types --non-interactive ${MYPY_SOURCE_FILES} .PHONY: test doc diff --git a/pyproject.toml b/pyproject.toml index b258af2a..fcfb1cc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "pydantic>=1.2,<2", + "pydantic>=1.2,<3", ] [project.optional-dependencies] @@ -37,7 +37,7 @@ dev = [ "syrupy>=4.0", ] email = [ - "pydantic[email]>=1.2,<2", + "pydantic[email]>=1.2,<3", ] falcon = [ "falcon>=3.0.0", diff --git a/setup.py b/setup.py index f42a2b44..de1217cf 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,10 @@ setup( name="spectree", install_requires=[ - "pydantic>=1.2,<2", + "pydantic>=1.2,<3", ], extras_require={ - "email": ["pydantic[email]>=1.2,<2"], + "email": ["pydantic[email]>=1.2,<3"], "flask": ["flask"], "quart": ["quart"], "falcon": ["falcon>=3.0.0"], diff --git a/spectree/_pydantic.py b/spectree/_pydantic.py new file mode 100644 index 00000000..70559fe7 --- /dev/null +++ b/spectree/_pydantic.py @@ -0,0 +1,37 @@ +from pydantic.version import VERSION as PYDANTIC_VERSION + +PYDANTIC2 = PYDANTIC_VERSION.startswith("2") + +__all__ = [ + "BaseModel", + "ValidationError", + "Field", + "root_validator", + "AnyUrl", + "BaseSettings", + "EmailStr", + "validator", +] + +if PYDANTIC2: + from pydantic.v1 import ( + AnyUrl, + BaseModel, + BaseSettings, + EmailStr, + Field, + ValidationError, + root_validator, + validator, + ) +else: + from pydantic import ( # type: ignore[no-redef,assignment] + AnyUrl, + BaseModel, + BaseSettings, + EmailStr, + Field, + ValidationError, + root_validator, + validator, + ) diff --git a/spectree/_types.py b/spectree/_types.py index 79e93208..b572fad5 100644 --- a/spectree/_types.py +++ b/spectree/_types.py @@ -11,9 +11,10 @@ Union, ) -from pydantic import BaseModel from typing_extensions import Protocol +from ._pydantic import BaseModel + ModelType = Type[BaseModel] OptionalModelType = Optional[ModelType] NamingStrategy = Callable[[ModelType], str] diff --git a/spectree/config.py b/spectree/config.py index 0e0bfd06..2a9a680c 100644 --- a/spectree/config.py +++ b/spectree/config.py @@ -2,8 +2,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union -from pydantic import AnyUrl, BaseModel, BaseSettings, EmailStr, root_validator - +from ._pydantic import AnyUrl, BaseModel, BaseSettings, EmailStr, root_validator from .models import SecurityScheme, Server from .page import DEFAULT_PAGE_TEMPLATES diff --git a/spectree/models.py b/spectree/models.py index e49dae5a..1b07ef9e 100644 --- a/spectree/models.py +++ b/spectree/models.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Dict, Optional, Sequence -from pydantic import BaseModel, Field, root_validator, validator +from ._pydantic import BaseModel, Field, root_validator, validator # OpenAPI names validation regexp OpenAPI_NAME_RE = re.compile(r"^[A-Za-z0-9-._]+") diff --git a/spectree/plugins/falcon_plugin.py b/spectree/plugins/falcon_plugin.py index 892661eb..07b5d13f 100644 --- a/spectree/plugins/falcon_plugin.py +++ b/spectree/plugins/falcon_plugin.py @@ -5,8 +5,8 @@ from falcon import HTTP_400, HTTP_415, HTTPError from falcon.routing.compiled import _FIELD_PATTERN as FALCON_FIELD_PATTERN -from pydantic import ValidationError +from .._pydantic import ValidationError from .._types import ModelType from ..response import Response from .base import BasePlugin diff --git a/spectree/plugins/flask_plugin.py b/spectree/plugins/flask_plugin.py index 95a9ca88..0604a5ab 100644 --- a/spectree/plugins/flask_plugin.py +++ b/spectree/plugins/flask_plugin.py @@ -1,9 +1,9 @@ from typing import Any, Callable, Mapping, Optional, Tuple, get_type_hints from flask import Blueprint, abort, current_app, jsonify, make_response, request -from pydantic import BaseModel, ValidationError from werkzeug.routing import parse_converter_args +from .._pydantic import BaseModel, ValidationError from .._types import ModelType from ..response import Response from ..utils import get_multidict_items, werkzeug_parse_rule diff --git a/spectree/plugins/quart_plugin.py b/spectree/plugins/quart_plugin.py index 28c9fb16..de891c2e 100644 --- a/spectree/plugins/quart_plugin.py +++ b/spectree/plugins/quart_plugin.py @@ -1,10 +1,10 @@ import inspect from typing import Any, Callable, Mapping, Optional, Tuple, get_type_hints -from pydantic import BaseModel, ValidationError from quart import Blueprint, abort, current_app, jsonify, make_response, request from werkzeug.routing import parse_converter_args +from .._pydantic import BaseModel, ValidationError from .._types import ModelType from ..response import Response from ..utils import get_multidict_items, werkzeug_parse_rule diff --git a/spectree/plugins/starlette_plugin.py b/spectree/plugins/starlette_plugin.py index 5dabdc26..938bad75 100644 --- a/spectree/plugins/starlette_plugin.py +++ b/spectree/plugins/starlette_plugin.py @@ -4,12 +4,12 @@ from json import JSONDecodeError from typing import Any, Callable, Optional, get_type_hints -from pydantic import ValidationError from starlette.convertors import CONVERTOR_TYPES from starlette.requests import Request from starlette.responses import HTMLResponse, JSONResponse from starlette.routing import compile_path +from .._pydantic import ValidationError from .._types import ModelType from ..response import Response from .base import BasePlugin, Context diff --git a/spectree/response.py b/spectree/response.py index 15716b2a..6b8e69e5 100644 --- a/spectree/response.py +++ b/spectree/response.py @@ -1,8 +1,7 @@ from http import HTTPStatus from typing import Any, Dict, Iterable, List, Optional, Tuple, Union -from pydantic import BaseModel - +from ._pydantic import BaseModel from ._types import ModelType, NamingStrategy, OptionalModelType from .utils import gen_list_model, get_model_key, parse_code diff --git a/spectree/utils.py b/spectree/utils.py index 52af7c74..cc803f05 100644 --- a/spectree/utils.py +++ b/spectree/utils.py @@ -16,8 +16,7 @@ Union, ) -from pydantic import BaseModel, ValidationError - +from ._pydantic import BaseModel, ValidationError from ._types import ModelType, MultiDict, NamingStrategy, NestedNamingStrategy # parse HTTP status code to get the code diff --git a/tests/common.py b/tests/common.py index 7af87452..ec375a3e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,9 +1,8 @@ from enum import Enum, IntEnum from typing import Dict, List -from pydantic import BaseModel, Field, root_validator - from spectree import BaseFile, ExternalDocs, SecurityScheme, SecuritySchemeData, Tag +from spectree._pydantic import BaseModel, Field, root_validator from spectree.utils import hash_module_path api_tag = Tag( diff --git a/tests/test_config.py b/tests/test_config.py index c4716542..bc1f4e8e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,9 +2,9 @@ from typing import Type import pytest -from pydantic import ValidationError from spectree import SecurityScheme +from spectree._pydantic import ValidationError from spectree.config import Configuration, EmailFieldType from .common import SECURITY_SCHEMAS, WRONG_SECURITY_SCHEMAS_DATA diff --git a/tests/test_response.py b/tests/test_response.py index 103ee3f0..1ba509c2 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,8 +1,8 @@ from typing import List, get_type_hints import pytest -from pydantic import BaseModel +from spectree._pydantic import BaseModel from spectree.models import ValidationError from spectree.response import DEFAULT_CODE_DESC, Response from spectree.utils import gen_list_model diff --git a/tests/test_spec.py b/tests/test_spec.py index 98ebace2..3e802a0b 100644 --- a/tests/test_spec.py +++ b/tests/test_spec.py @@ -1,10 +1,10 @@ import pytest from falcon import App as FalconApp from flask import Flask -from pydantic import BaseModel from starlette.applications import Starlette from spectree import Response +from spectree._pydantic import BaseModel from spectree.config import Configuration from spectree.models import Server, ValidationError from spectree.plugins.flask_plugin import FlaskPlugin