Issue
I have the following model
class Window(BaseModel):
size: tuple[int, int]
and I would like to instantiate it like this:
fields = {'size': '1920x1080'}
window = Window(**fields)
Of course this fails since the value of 'size'
is not of the correct type. However, I would like to add logic so that the value is split at x
, i.e.:
def transform(raw: str) -> tuple[int, int]:
x, y = raw.split('x')
return int(x), int(y)
Does Pydantic support this?
Solution
Pydantic 2.x (edit)
Pydantic 2.0 introduced the field_validator
decorator which lets you implement such a behaviour in a very simple way. Given the original parsing function:
from pydantic import BaseModel, field_validator
class Window(BaseModel):
size: tuple[int, int]
@field_validator("size", mode="before")
def transform(cls, raw: str) -> tuple[int, int]:
x, y = raw.split("x")
return int(x), int(y)
Note:
- The validator method is a class method, as denoted by the
cls
first argument. Implementing it as an instance method (withself
) will raise an error. - The
mode="before"
in the decorator is critical here, as expected this is what makes the method run before checking "size" is a tuple.
Pydantic 1.x (original answer)
You can implement such a behaviour with pydantic's validator
. Given your predefined function:
def transform(raw: str) -> tuple[int, int]:
x, y = raw.split('x')
return int(x), int(y)
You can implement it in your class like this:
from pydantic import BaseModel, validator
class Window(BaseModel):
size: tuple[int, int]
_extract_size = validator('size', pre=True, allow_reuse=True)(transform)
Note the pre=True
argument passed to the validator. It means that it will be run before the default validator that checks if size
is a tuple.
Now:
fields = {'size': '1920x1080'}
window = Window(**fields)
print(window)
# output: size=(1920, 1080)
Note that after that, you won't be able to instantiate your Window
with a tuple for size.
fields2 = {'size': (800, 600)}
window2 = Window(**fields2)
# AttributeError: 'tuple' object has no attribute 'split'
In order to overcome that, you could simply bypass the function if a tuple is passed by altering slightly your code:
Pydantic 2.x
class Window(BaseModel):
size: tuple[int, int]
@field_validator("size", mode="before")
def transform(cls, raw: str | tuple[int, int]) -> tuple[int, int]:
if isinstance(raw, tuple):
return raw
x, y = raw.split("x")
return int(x), int(y)
Pydantic 1.x
def transform(raw: str | tuple[int, int]) -> tuple[int, int]:
if isinstance(raw, tuple):
return raw
x, y = raw.split('x')
return int(x), int(y)
class Window(BaseModel):
size: tuple[int, int]
_extract_size = validator('size', pre=True, allow_reuse=True)(transform)
Which should give:
fields2 = {'size': (800, 600)}
window2 = Window(**fields2)
print(window2)
# output: size:(800, 600)
Answered By - ye olde noobe
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.