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
|
||||
:rtype: pydantic.ModelField
|
||||
"""
|
||||
type_ = model.Meta.model_fields[field_name].__type__
|
||||
return ModelField(
|
||||
name=field_name,
|
||||
type_=model.Meta.model_fields[field_name].__type__, # type: ignore
|
||||
type_=type_, # type: ignore
|
||||
model_config=model.__config__,
|
||||
required=not model.Meta.model_fields[field_name].nullable,
|
||||
class_validators={},
|
||||
|
||||
@ -101,6 +101,7 @@ def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
|
||||
:type model_field: relation Field
|
||||
"""
|
||||
related_name = model_field.get_related_name()
|
||||
# TODO: Reverse relations does not register pydantic fields?
|
||||
if model_field.is_multi:
|
||||
model_field.to.Meta.model_fields[related_name] = ManyToMany( # type: ignore
|
||||
model_field.owner,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import copy
|
||||
import string
|
||||
from random import choices
|
||||
from typing import (
|
||||
@ -82,7 +83,9 @@ class PydanticMixin(RelationMixin):
|
||||
(pydantic.BaseModel,),
|
||||
{"__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
|
||||
def _determine_pydantic_field_type(
|
||||
@ -111,3 +114,33 @@ class PydanticMixin(RelationMixin):
|
||||
if target is not None and field.nullable:
|
||||
target = Optional[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 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,
|
||||
Item,
|
||||
MutualA,
|
||||
MutualB,
|
||||
SelfRef,
|
||||
database,
|
||||
metadata,
|
||||
@ -53,7 +50,9 @@ app.post("/categories/", response_model=Category)(create_category)
|
||||
response_model=SelfRef.get_pydantic(exclude={"parent", "children__name"}),
|
||||
)
|
||||
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())
|
||||
await selfr.save()
|
||||
|
||||
@ -5,7 +5,6 @@ import pydantic
|
||||
import pytest
|
||||
import sqlalchemy
|
||||
from pydantic import ValidationError
|
||||
from pydantic.class_validators import make_generic_validator
|
||||
|
||||
|
||||
import ormar
|
||||
@ -44,26 +43,14 @@ class ModelExample(ormar.Model):
|
||||
raise ValueError("must contain a space")
|
||||
return v
|
||||
|
||||
|
||||
def validate_str_field(cls, v):
|
||||
if " " not in v:
|
||||
raise ValueError("must contain a space")
|
||||
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
|
||||
@pydantic.validator("str_field")
|
||||
def validate_str_field2(cls, v):
|
||||
if " " not in v:
|
||||
raise ValueError("must contain a space")
|
||||
return v
|
||||
|
||||
|
||||
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():
|
||||
|
||||
Reference in New Issue
Block a user