简体   繁体   中英

convert to pydantic model from tuple

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
]

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).

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)

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]
"""

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM