inherit choices validators and class validators for fields in generated pydantic models

This commit is contained in:
collerek
2021-10-11 16:22:50 +02:00
parent f6458be157
commit 9559c0f7f6
6 changed files with 46 additions and 25 deletions

View File

@ -50,9 +50,10 @@ def get_pydantic_field(field_name: str, model: Type["Model"]) -> "ModelField":
:return: newly created pydantic field :return: newly created pydantic field
:rtype: pydantic.ModelField :rtype: pydantic.ModelField
""" """
type_ = model.Meta.model_fields[field_name].__type__
return ModelField( return ModelField(
name=field_name, name=field_name,
type_=model.Meta.model_fields[field_name].__type__, # type: ignore type_=type_, # type: ignore
model_config=model.__config__, model_config=model.__config__,
required=not model.Meta.model_fields[field_name].nullable, required=not model.Meta.model_fields[field_name].nullable,
class_validators={}, class_validators={},

View File

@ -101,6 +101,7 @@ def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
:type model_field: relation Field :type model_field: relation Field
""" """
related_name = model_field.get_related_name() related_name = model_field.get_related_name()
# TODO: Reverse relations does not register pydantic fields?
if model_field.is_multi: if model_field.is_multi:
model_field.to.Meta.model_fields[related_name] = ManyToMany( # type: ignore model_field.to.Meta.model_fields[related_name] = ManyToMany( # type: ignore
model_field.owner, model_field.owner,

View File

@ -1,3 +1,4 @@
import copy
import string import string
from random import choices from random import choices
from typing import ( from typing import (
@ -82,7 +83,9 @@ class PydanticMixin(RelationMixin):
(pydantic.BaseModel,), (pydantic.BaseModel,),
{"__annotations__": fields_dict, **defaults}, {"__annotations__": fields_dict, **defaults},
) )
return cast(Type[pydantic.BaseModel], model) model = cast(Type[pydantic.BaseModel], model)
cls._copy_field_validators(model=model)
return model
@classmethod @classmethod
def _determine_pydantic_field_type( def _determine_pydantic_field_type(
@ -111,3 +114,33 @@ class PydanticMixin(RelationMixin):
if target is not None and field.nullable: if target is not None and field.nullable:
target = Optional[target] target = Optional[target]
return target return target
@classmethod
def _copy_field_validators(cls, model: Type[pydantic.BaseModel]) -> None:
"""
Copy field validators from ormar model to generated pydantic model.
"""
for field_name, field in model.__fields__.items():
if (
field_name not in cls.__fields__
or cls.Meta.model_fields[field_name].is_relation
):
continue
validators = cls.__fields__[field_name].validators
already_attached = [
validator.__wrapped__ for validator in field.validators # type: ignore
]
validators_to_copy = [
validator
for validator in validators
if validator.__wrapped__ not in already_attached # type: ignore
]
field.validators.extend(copy.deepcopy(validators_to_copy))
class_validators = cls.__fields__[field_name].class_validators
field.class_validators.update(copy.deepcopy(class_validators))
field.pre_validators = copy.deepcopy(
cls.__fields__[field_name].pre_validators
)
field.post_validators = copy.deepcopy(
cls.__fields__[field_name].post_validators
)

View File

@ -4,11 +4,8 @@ from fastapi import FastAPI
from starlette.testclient import TestClient from starlette.testclient import TestClient
from tests.settings import DATABASE_URL from tests.settings import DATABASE_URL
from tests.test_inheritance_and_pydantic_generation.test_geting_the_pydantic_models import ( from tests.test_inheritance_and_pydantic_generation.test_geting_pydantic_models import (
Category, Category,
Item,
MutualA,
MutualB,
SelfRef, SelfRef,
database, database,
metadata, metadata,
@ -53,7 +50,9 @@ app.post("/categories/", response_model=Category)(create_category)
response_model=SelfRef.get_pydantic(exclude={"parent", "children__name"}), response_model=SelfRef.get_pydantic(exclude={"parent", "children__name"}),
) )
async def create_selfref( async def create_selfref(
selfref: SelfRef.get_pydantic(exclude={"children__name"}), # type: ignore selfref: SelfRef.get_pydantic( # type: ignore
exclude={"children__name"} # noqa: F821
),
): ):
selfr = SelfRef(**selfref.dict()) selfr = SelfRef(**selfref.dict())
await selfr.save() await selfr.save()

View File

@ -5,7 +5,6 @@ import pydantic
import pytest import pytest
import sqlalchemy import sqlalchemy
from pydantic import ValidationError from pydantic import ValidationError
from pydantic.class_validators import make_generic_validator
import ormar import ormar
@ -44,26 +43,14 @@ class ModelExample(ormar.Model):
raise ValueError("must contain a space") raise ValueError("must contain a space")
return v return v
@pydantic.validator("str_field")
def validate_str_field(cls, v): def validate_str_field2(cls, v):
if " " not in v: if " " not in v:
raise ValueError("must contain a space") raise ValueError("must contain a space")
return v return v
def validate_choices(cls, v):
if v not in list(EnumExample):
raise ValueError(f"{v} is not in allowed choices: {list(EnumExample)}")
return v
ModelExampleCreate = ModelExample.get_pydantic(exclude={"id"}) ModelExampleCreate = ModelExample.get_pydantic(exclude={"id"})
ModelExampleCreate.__fields__["str_field"].validators.append(
make_generic_validator(validate_str_field)
)
ModelExampleCreate.__fields__["enum_field"].validators.append(
make_generic_validator(validate_choices)
)
def test_ormar_validator(): def test_ormar_validator():