mirror of https://github.com/tiangolo/fastapi.git
👷 Add CI for both Pydantic v1 and v2 (#9688)
* 👷 Test and install Pydantic v1 and v2 in CI * 💚 Tweak CI config for Pydantic v1 and v2 * 💚 Fix Pydantic v2 specification in CI * 🐛 Fix type annotations for compatibility with Python 3.7 * 💚 Install Pydantic v2 for lints * 🐛 Fix type annotations for Pydantic v2 * 💚 Re-use test cache for lint
This commit is contained in:
parent
5301cfff52
commit
c58e2b2d1e
|
|
@ -25,10 +25,12 @@ jobs:
|
|||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install -r requirements-tests.txt
|
||||
- name: Install Pydantic v2
|
||||
run: pip install --pre "pydantic>=2.0.0b2,<3.0.0"
|
||||
- name: Lint
|
||||
run: bash scripts/lint.sh
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
pydantic-version: ["pydantic-v1", "pydantic-v2"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
@ -51,10 +54,16 @@ jobs:
|
|||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install -r requirements-tests.txt
|
||||
- name: Install Pydantic v1
|
||||
if: matrix.pydantic-version == 'pydantic-v1'
|
||||
run: pip install "pydantic>=1.10.0,<2.0.0"
|
||||
- name: Install Pydantic v2
|
||||
if: matrix.pydantic-version == 'pydantic-v2'
|
||||
run: pip install --pre "pydantic>=2.0.0b2,<3.0.0"
|
||||
- run: mkdir coverage
|
||||
- name: Test
|
||||
run: bash scripts/test.sh
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ if PYDANTIC_V2:
|
|||
values: Dict[str, Any] = {}, # noqa: B006
|
||||
*,
|
||||
loc: Union[Tuple[Union[int, str], ...], str] = "",
|
||||
) -> tuple[Any, Union[List[ValidationError], None]]:
|
||||
) -> Tuple[Any, Union[List[ValidationError], None]]:
|
||||
try:
|
||||
return (
|
||||
self._type_adapter.validate_python(value, from_attributes=True),
|
||||
|
|
@ -415,13 +415,13 @@ def get_definitions(
|
|||
return get_model_definitions(flat_models=models, model_name_map=model_name_map)
|
||||
|
||||
|
||||
def _annotation_is_sequence(annotation: type[Any] | None) -> bool:
|
||||
def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
||||
if lenient_issubclass(annotation, (str, bytes)):
|
||||
return False
|
||||
return lenient_issubclass(annotation, sequence_types)
|
||||
|
||||
|
||||
def field_annotation_is_sequence(annotation: type[Any] | None) -> bool:
|
||||
def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
|
||||
return _annotation_is_sequence(annotation) or _annotation_is_sequence(
|
||||
get_origin(annotation)
|
||||
)
|
||||
|
|
@ -431,7 +431,7 @@ def value_is_sequence(value: Any) -> bool:
|
|||
return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def _annotation_is_complex(annotation: Type[Any] | None) -> bool:
|
||||
def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
||||
return (
|
||||
lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
|
||||
or _annotation_is_sequence(annotation)
|
||||
|
|
@ -439,7 +439,7 @@ def _annotation_is_complex(annotation: Type[Any] | None) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def field_annotation_is_complex(annotation: type[Any] | None) -> bool:
|
||||
def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
|
||||
origin = get_origin(annotation)
|
||||
if origin is Union or origin is UnionType:
|
||||
return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
|
||||
|
|
@ -461,7 +461,7 @@ def field_annotation_is_scalar(annotation: Any) -> bool:
|
|||
return annotation is Ellipsis or not field_annotation_is_complex(annotation)
|
||||
|
||||
|
||||
def field_annotation_is_scalar_sequence(annotation: type[Any] | None) -> bool:
|
||||
def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:
|
||||
origin = get_origin(annotation)
|
||||
if origin is Union or origin is UnionType:
|
||||
at_least_one_scalar_sequence = False
|
||||
|
|
@ -525,7 +525,7 @@ def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def is_bytes_sequence_annotation(annotation: type[Any] | None) -> bool:
|
||||
def is_bytes_sequence_annotation(annotation: Union[Type[Any], None]) -> bool:
|
||||
origin = get_origin(annotation)
|
||||
if origin is Union or origin is UnionType:
|
||||
at_least_one_bytes_sequence = False
|
||||
|
|
@ -540,7 +540,7 @@ def is_bytes_sequence_annotation(annotation: type[Any] | None) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def is_uploadfile_sequence_annotation(annotation: type[Any] | None) -> bool:
|
||||
def is_uploadfile_sequence_annotation(annotation: Union[Type[Any], None]) -> bool:
|
||||
origin = get_origin(annotation)
|
||||
if origin is Union or origin is UnionType:
|
||||
at_least_one_bytes_sequence = False
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -42,7 +44,7 @@ async def read_pet(pet_id: int):
|
|||
return pet
|
||||
|
||||
|
||||
@app.get("/pets/", response_model=list[PetOut])
|
||||
@app.get("/pets/", response_model=List[PetOut])
|
||||
async def read_pets():
|
||||
user = UserDB(
|
||||
email="johndoe@example.com",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -44,7 +46,7 @@ async def read_pet(pet_id: int):
|
|||
return pet
|
||||
|
||||
|
||||
@app.get("/pets/", response_model=list[PetOut])
|
||||
@app.get("/pets/", response_model=List[PetOut])
|
||||
async def read_pets():
|
||||
user = UserDB(
|
||||
email="johndoe@example.com",
|
||||
|
|
|
|||
Loading…
Reference in New Issue