Search code examples
pythoncelerypydanticfunctools

Is it possible to use custom or other libraries' decorators on a custom method of a pydantic model class


I'm using Pydantic's BaseModel for creating my working class. I also have custom methods other than the methods with validators. I need to use singledispatchmethod decorator from functools with the custom function. For example,

class Foo(pydantic.BaseModel):
    name: str
    bar: int
    baz: int

    @functools.singledispatchmethod
    def handle(self, command: Command) -> CommandResult:
        pass

    @celery.task
    def add(a: int, b: int):
        time.sleep(10)
        print("Sum is: ", a+b)

However, it's throwing the following error for using the singledispatchmethod

File "/models.py", line 56, in <module>
   class Foo(pydantic.BaseModel):
File "pydantic/main.py", line 323, in pydantic.main.ModelMetaclass.__new__
File "pydantic/fields.py", line 411, in pydantic.fields.ModelField.infer
File "pydantic/fields.py", line 342, in pydantic.fields.ModelField.__init__
File "pydantic/fields.py", line 456, in pydantic.fields.ModelField.prepare
File "pydantic/fields.py", line 670, in pydantic.fields.ModelField.populate_validators
File "pydantic/validators.py", line 715, in find_validators
RuntimeError: no validator found for <class 'functools.singledispatchmethod'>, see `arbitrary_types_allowed` in Config

In the same fashion I also want to use Celery's task decorator on one such custom methods.


Solution

  • Not every decorator will result in this behavior. In this case, the singledispatchmethod is a descriptor class that is not processed by the pydantic by default.

    But you can use keep_untouched model config settings. Excerpt from the documentation:

    keep_untouched

    a tuple of types (e.g. descriptors) for a model's default values that should not be changed during model creation and will not be included in the model schemas. Note: this means that attributes on the model with defaults of this type, not annotations of this type, will be left alone.

    class Foo(BaseModel):
        name: str
        bar: int
    
        @functools.singledispatchmethod
        def handle(self, command: str) -> str:
            pass
    
        class Config:
            keep_untouched = (functools.singledispatchmethod,)