I've got a need to "register" (?) arbitrary validators for custom classes. Right now I can achieve the functionality I want doing something like this:
from typing import Any
import pydantic.validators
from pydantic import BaseModel
class Foo:
pass
def foo_validator(v: Any) -> Foo:
if isinstance(v, Foo):
return v
if v == "Foo":
return Foo()
raise ValueError("Must be a Foo or the string 'Foo'")
pydantic.validators._VALIDATORS.append((Foo, [foo_validator]))
class Bar(BaseModel):
foo: Foo
bar = Bar(foo="Foo")
This feels a little hacky.
So:
1) Is there already a better way to do something like this that I'm not seeing?
2) Would it be possible to support something less hacky than this? e.g.
pydantic.register_custom_validator((Foo, [foo_validator]))
And then pydantic.validators.find_validators could do something like this:
for val_type, validators in _VALIDATORS + _CUSTOM_VALIDATORS:
try:
if issubclass(type_, val_type):
return validators
except TypeError as e:
raise RuntimeError(f'error checking inheritance of {type_!r} (type: {display_as_type(type_)})') from e
Happy to start working on PR if this seems feasible/reasonable/etc.
Best to use __get_validators__ on Foo like this.
Then you can setup Foo to validate as you wish.
Thanks!
For completeness, here's how to implement the above:
from typing import Any
from pydantic import BaseModel
class Foo:
@classmethod
def __get_validators__(cls):
def validator(v: Any) -> "Foo":
if isinstance(v, cls):
return v
if v == "Foo":
return cls()
raise ValueError("Must be a Foo or the string 'Foo'")
yield validator
class Bar(BaseModel):
foo: Foo
bar = Bar(foo="Foo")
that will work, but generally more readable if you put the validator method as a classmethod on the Foo class as we do on all the pydantic validator classes, eg. UrlStr.