'How to initialize a Pydantic object from field values given by position instead of name?
I am not able to find a simple way how to initialize a Pydantic object from field values given by position (for example in a list instead of a dictionary) so I have written class method positional_fields()
to create the required dictionary from an iterable:
from typing import Optional, Iterable, Any, Dict
from pydantic import BaseModel
class StaticRoute(BaseModel):
if_name: str
dest_ip: str
mask: str
gateway_ip: str
distance: Optional[int]
@classmethod
def positional_fields(cls, values: Iterable) -> Dict[str, Any]:
return dict(zip(cls.__fields__, values))
input_lines = """
route ab 10.0.0.0 255.0.0.0 10.220.196.23 1
route gh 10.0.2.61 255.255.255.255 10.220.198.38 1
""".splitlines()
for line in input_lines:
words = line.split()
if words and words[0] == 'route':
sroute = StaticRoute(**StaticRoute.positional_fields(words[1:]))
print(sroute)
if_name='ab' dest_ip='10.0.0.0' mask='255.0.0.0' gateway_ip='10.220.196.23' distance=1
if_name='gh' dest_ip='10.0.2.61' mask='255.255.255.255' gateway_ip='10.220.198.38' distance=1
Is there a more straightforward way of achieving this?
My method expects the __fields__
dictionary to have keys in the order the fields were defined in the class. I am not sure if this is guaranteed (assuming Python 3.6+).
Solution 1:[1]
How about using dataclasses instead? Something like:
from typing import Optional
from pydantic.dataclasses import dataclass
@dataclass
class StaticRoute:
if_name: str
dest_ip: str
mask: str
gateway_ip: str
distance: Optional[int]
words = "route if_name dest_ip mask gateway_ip 10".split()
print(StaticRoute(*words[1:])
# StaticRoute(if_name='if_name', dest_ip='dest_ip', mask='mask', gateway_ip='gateway_ip', distance=10)
Solution 2:[2]
The class method BaseModel.parse_obj()
returns an object instance initialized by a dictionary. We can create a similar class method parse_iterable()
which accepts an iterable instead.
from typing import Optional, Iterable, Any, Dict
from pydantic import BaseModel
class BaseModelExt(BaseModel):
@classmethod
def parse_iterable(cls, values: Iterable):
return cls.parse_obj(dict(zip(cls.__fields__, values)))
class StaticRoute(BaseModelExt):
if_name: str
dest_ip: str
mask: str
gateway_ip: str
distance: Optional[int]
input_lines = """
route ab 10.0.0.0 255.0.0.0 10.220.196.23 1
route gh 10.0.2.61 255.255.255.255 10.220.198.38 1
""".splitlines()
for line in input_lines:
words = line.split()
if words and words[0] == 'route':
sroute = StaticRoute.parse_iterable(words[1:])
print(sroute)
Note: We are still missing a confirmation if the order of BaseModel.__fields__
is guaranteed.
Solution 3:[3]
You can use a combination of a custom RootType and NamedTuple, like so:
from pydantic import BaseModel
from typing import NamedTuple, Optional
class StaticRouteTuple(NamedTuple):
if_name: str
dest_ip: str
mask: str
gateway_ip: str
distance: Optional[int]
class StaticRoute(BaseModel):
__root__: StaticRouteTuple
@property
def route(self) -> StaticRouteTuple:
return self.__root__
input_lines = """
route ab 10.0.0.0 255.0.0.0 10.220.196.23 1
route gh 10.0.2.61 255.255.255.255 10.220.198.38 1
""".splitlines()
for line in input_lines:
words = line.split()
if words and words[0] == "route":
sroute = StaticRoute.parse_obj(words[1:]).route
print(sroute)
If you don't want to use a custom RootType you can use instead pyandic.parse_obj_as
, example:
from pydantic import parse_obj_as
sroute = parse_obj_as(StaticRouteTuple, words[1:])
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | funnydman |
Solution 2 | |
Solution 3 |