pydantic-partial icon indicating copy to clipboard operation
pydantic-partial copied to clipboard

Feature Request: Support custom field validators

Open alexdrydew opened this issue 1 year ago • 5 comments

Hi! First of all, thank you for your work, the library provides a really handy way to reduce boilerplate for partial models.

Unfortunately, I faced a blocker for using this library in my case: currently pydantic partial discards metadata from FieldInfo which contains custom validation logic. It would be great if the pydantic-partial library introduced a way to call custom field validators if the value is present.

Here is an example of a possible usage case:

from datetime import timedelta
from typing import Annotated, Any

from pydantic import BaseModel, PlainSerializer, WrapValidator
from pydantic.functional_validators import ModelWrapValidatorHandler
from pydantic_partial import create_partial_model


def timedelta_validator(value: Any, handler: ModelWrapValidatorHandler[timedelta]) -> timedelta:
    if isinstance(value, (timedelta, str)):
        return handler(value)
    elif isinstance(value, (float, int)):
        return timedelta(seconds=value)
    else:
        raise ValueError


TimedeltaSeconds = Annotated[timedelta, PlainSerializer(lambda td: td.total_seconds()), WrapValidator(timedelta_validator)]


class MyModel(BaseModel):
    t: TimedeltaSeconds


MyModelPartial = create_partial_model(MyModel)
assert MyModel(t=timedelta(seconds=10)).model_dump()['t'] == 10.  # ok
assert MyModelPartial(t=timedelta(seconds=10)).model_dump()['t'] == 10.  # error

Thank you!

Version info

pydantic-partial==0.5.2

pydantic version: 2.4.2 pydantic-core version: 2.10.1 pydantic-core build: profile=release pgo=false python version: 3.11.4 (main, Aug 28 2023, 23:10:28) [Clang 14.0.3 (clang-1403.0.22.14.1)] platform: macOS-14.0-arm64-arm-64bit related packages: mypy-1.6.0 typing_extensions-4.8.0 pydantic-settings-2.0.3 fastapi-0.101.1

alexdrydew avatar Nov 07 '23 13:11 alexdrydew

@alexdrydew Sorry, I had no time to look into this - but I will do that. I don't think it's that simple...

Currently we want to skip validators, because those might just break when the type changes due to what pydantic-partial does. Your code for example should also accept None in the partial model version (like MyModelPartial(t=None)), which I think (haven't tested this yet) the validator would prevent by triggering a ValueError. So this might (!) introduce strange side effects. 😉

ddanier avatar Nov 11 '23 11:11 ddanier

Hi @ddanier, thank you for your answer.

Skipping validators makes the partial models a bit useless in my case :( So, I added PR with this feature. Could you please take a look? PR

ADR-007 avatar Feb 26 '24 12:02 ADR-007

@alexdrydew Sorry, I had no time to look into this - but I will do that. I don't think it's that simple...

Currently we want to skip validators, because those might just break when the type changes due to what pydantic-partial does. Your code for example should also accept None in the partial model version (like MyModelPartial(t=None)), which I think (haven't tested this yet) the validator would prevent by triggering a ValueError. So this might (!) introduce strange side effects. 😉

I guess that we should trigger field validation only if not None (and was not Optional originally)

AlmogBaku avatar May 21 '24 21:05 AlmogBaku

I guess that we should trigger field validation only if not None (and was not Optional originally)

@AlmogBaku I hope you understand that this sounds like a very bad developer experience. Sometimes the validators are executed, sometimes not. You might have a validator das explicitly wants to handle None values for example. This would then break.

I didn't come up with a good solution so far, sorry!

ddanier avatar May 22 '24 12:05 ddanier

@AlmogBaku if you want to have the validation always be enabled, you may want to try my version of this library that I did for that case: pydantic-strict-partial.

ADR-007 avatar May 22 '24 13:05 ADR-007