inherit choices validators and class validators for fields in generated pydantic models
This commit is contained in:
@ -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={},
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
)
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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():
|
||||||
|
|||||||
Reference in New Issue
Block a user