pythonsubclassbuilt-inpydantic

Pydantic - How to subclass a builtin type


I'm trying to make a subclass of timedelta that expects to receive milliseconds instead of seconds, but it's not currently working.

Am I going against the grain? Is there a "right" way to achieve this with Pydantic? Or do I somehow need to tell Pydantic that MillisecondTimedelta is just a timedelta..

from datetime import timedelta

from pydantic import BaseModel


class MillisecondTimedelta(timedelta):
    @classmethod
    def __get_validators__(cls):
        # timedelta expects seconds
        yield lambda v: v / 1000
        yield cls


class MyModel(BaseModel):
    td: MillisecondTimedelta

data = {
    "td": 7598040,
}

print(MyModel(**data))

Results in:

Traceback (most recent call last):
  File "main.py", line 14, in <module>
    class MyModel(BaseModel):
  File "pydantic/main.py", line 262, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/fields.py", line 315, in pydantic.fields.ModelField.infer
  File "pydantic/fields.py", line 284, in pydantic.fields.ModelField.__init__
  File "pydantic/fields.py", line 362, in pydantic.fields.ModelField.prepare
  File "pydantic/fields.py", line 541, in pydantic.fields.ModelField.populate_validators
  File "pydantic/class_validators.py", line 255, in pydantic.class_validators.prep_validators
  File "pydantic/class_validators.py", line 238, in pydantic.class_validators.make_generic_validator
  File "/usr/lib/python3.8/inspect.py", line 3105, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
  File "/usr/lib/python3.8/inspect.py", line 2854, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
  File "/usr/lib/python3.8/inspect.py", line 2384, in _signature_from_callable
    raise ValueError(
ValueError: no signature found for builtin type <class '__main__.MillisecondTimedelta'>

Solution

  • Aha, turns out I needed to change 2 things.

    1. Actually return a timedelta from the validator, I was returning cls which was my custom subclass.

    2. Don't subclass timedelta

    from datetime import timedelta
    
    from pydantic import BaseModel
    
    
    class MillisecondTimedelta:
    
        @classmethod
        def __get_validators__(cls):
            yield lambda v: timedelta(milliseconds=v)
    
    
    class MyModel(BaseModel):
        td: MillisecondTimedelta
    
    
    data = {
        "td": 7598040,
    }
    
    print(repr(MyModel(**data)))
    
    MyModel(td=datetime.timedelta(seconds=7598, microseconds=40000))