Reputation: 103
I want to map a tuple(list) to a pydantic model.
Is there a best practice to map tuple indexes to attributes in the following cases?
from pydantic import BaseModel
class Ohlc(BaseModel):
close_time: float
open_time: float
high_price: float
low_price: float
close_price: float
volume: float
quote_volume: float
data = [
1495324800,
232660,
242460,
231962,
242460,
231.863,
0
]
Upvotes: 9
Views: 11283
Reputation: 4976
This is usually how I do it:
from pydantic import BaseModel
class CustomBaseModel(BaseModel):
@classmethod
def from_list(cls, tpl):
return cls(**{k: v for k, v in zip(cls.__fields__.keys(), tpl)})
You just need to inherit your pydantic class from this new one:
class Ohlc(CustomBaseModel):
...
Now you can use it like this:
obj = Ohlc.from_list(data)
Upvotes: 2
Reputation: 61
Similar to the answer of Or Y but I prefer to use zip to avoid dealing with indices altogether.
Ohlc(**{k: v for k, v in zip(Ohlc.__fields__.keys(), data)})
Upvotes: 3
Reputation: 9
In my case, I implemented the ListModel
class for Point(x,y)
as follows.
I am sharing an example that did not process the tuple
separately assuming that only the list
is input.
I hope it will be helpful.
from typing import List, Optional, Any, Dict, Union
from pydantic import BaseModel, PrivateAttr, root_validator, validator
class ListModel(BaseModel):
_list: List = PrivateAttr()
def __init__(self, obj: Optional[Union[List, BaseModel]] = None, **kwargs):
super().__init__(**self.parse_args(obj, kwargs))
# set private attribute
self._list = obj or list(kwargs.values())
def __iter__(self):
return iter(self._list)
def __getitem__(self, item):
return self._list[item]
def parse_args(self, obj: Optional[Union[List, BaseModel]], kwargs) -> Dict:
self.validate_init(obj, kwargs)
if isinstance(obj, list):
self.validate_list(obj)
return self.parse_list(obj)
elif isinstance(obj, BaseModel):
return obj.dict()
elif isinstance(obj, dict):
return obj
else:
return kwargs
def validate_init(self, obj: Optional[Union[List, BaseModel]], kwargs: Dict) -> None:
if all([any(obj), any(kwargs)]):
raise TypeError(
f"Allow input type exclusively either list or dict."
)
def validate_list(self, list_: List) -> bool:
# Check that the number of elements in the list (or tuple)
# matches the number of fields (attributes) in the Base Model
if len(list_) != len(self.__class__.__fields__.keys()):
raise ValueError(
f"List does not match the field of the {self.__class__.__name__}"
)
def parse_list(self, list_: List) -> Dict:
# Assign each element to each field
return {
field.name: field.type_(value)
for field, value in zip(self.__class__.__fields__.values(), list_)
}
class Point(ListModel):
x: int
y: int
Point([1, 2])
# x=1 y=2
class Box(ListModel):
left_top: Point
right_top: Point
right_bottom: Point
left_bottom: Point
Box([[1, 2], [1, 6], [4, 6], [4, 2]])
"""
left_top=Point(x=1, y=2) right_top=Point(x=1, y=6) right_bottom=Point(x=4, y=6) left_bottom=Point(x=4, y=2)
"""
box.left_top
# x=1 y=2
for point in box:
print(point)
"""
[1, 2]
[1, 6]
[4, 6]
[4, 2]
"""
Upvotes: 0
Reputation: 370
I faced a simular problem and realized it can be solved using named tuples and pydantic. Modified solution below
from pydantic import BaseModel
import typing as t
data = [
1495324800,
232660,
242460,
231962,
242460,
231.863,
0
]
class OhlcEntry(t.NamedTuple):
close_time: float
open_time: float
high_price: float
low_price: float
close_price: float
volume: float
quote_volume: float
class Ohlc(BaseModel):
data: OhlcEntry
Ohlc(data=data)
If you work with lists of mixed types, then the same approach can be extended to parse the data in the list:
from pydantic import BaseModel
import datetime as dt
import typing as t
js_data= """[
[43.88,-32.24,"2021-12-20T00:01:59Z"],
[43.95,-32.21,"2021-12-20T00:14:46Z"]
]"""
class Point(t.NamedTuple):
longitude: float
latitude: float
ts: dt.datetime
class Track(BaseModel):
__root__: t.List[Point]
Track.parse_raw(js_data)
Upvotes: 0
Reputation: 2118
Assuming that data
's length is always equal to the number of fields in your model, you can use __fields__
to achieve that.
Ohlc(**{key: data[i] for i, key in enumerate(Ohlc.__fields__.keys())})
(There used to be fields
which required you to use construct()
first but now it is deprecated and now they tell you to use __fields__
instead).
Upvotes: 8