0

I have a FastApi item that I am trying to initialize using python tuples,

from pydantic import BaseModel

class Item(BaseModel):
     name: str = ""
     surname: str = ""

data = ("jhon", "dhon")
Item(*data)

Output the following error

TypeError: __init__() takes 1 positional argument but 3 were given

Is there a way to initialize a BaseModel from a tuple ?

farch
  • 315
  • 4
  • 14

4 Answers4

1

No, Pydantic models can only be initialized with keyword arguments. If you absolutely must initialize it from positional args, you can look at the schema:

>>> Item(**dict(zip(Item.schema()["properties"], data)))
Item(name='jhon', surname='dhon')
L3viathan
  • 26,748
  • 2
  • 58
  • 81
0

I wrote a helper function that can load data from the tuple but

def fill_model(model: BaseModel, columns: List, row: Tuple) -> BaseModel:

    base_model = model()
    model_keys = base_model.dict().keys()
    fields_count = len(model_keys)

    if fields_count != len(columns):
        raise ValueError("Columns length doesn't match fields count")

    if not set(columns).issubset(model_keys):
        raise ValueError("Columns doesn't match model fields")

    if fields_count != len(row):
        raise ValueError("Data length doesn't match fields count")

    return model(**{k: v for k, v in zip(columns, row)})
farch
  • 315
  • 4
  • 14
  • Be aware that this will/can break in mysterious ways if your model ever changes. At least you should give an `index` => `field` mapping. – MatsLindh May 17 '22 at 13:22
  • Yeah you are right, but I have assumption on my tuple that's always in correct order. – farch May 17 '22 at 13:58
  • I'm talking about the model - if you introduce a new field (and I'm not sure if the order of fields in the `dict()` method for pydantic is _guaranteed_) in your model - in particular between your existing fields, for example by adding an id field or something similar), the ordering inside of the `keys` view will change. If you at least supply a tuple of field names that map to the index in the data tuple, you'll get far less error prone code (so `(model, data, mapping)` where mapping would be something like `(key1, key2, key3)`. Then the same tuple will at least always match the correct fields – MatsLindh May 17 '22 at 17:26
  • 1
    Thank you for you hint, I did a change to the helper function – farch May 18 '22 at 12:08
0

You can also use pydantics BaseModel parse_obj functions: Item.parse_obj(some_dict). However, you would need to write a wrapper function/ use the keys from the class.

from pydantic import BaseModel

class Item(BaseModel):
     name: str = ""
     surname: str = ""    
data = ("jhon", "dhon")
fields = Item.__fields__.keys()
zipped_dict = dict(zip(fields, data))
item = Item.parse_obj(zipped_dict)

The nice part about this is, given that your tuples always contain the right data, having more entries in the Item class is pretty easy to handle.

This solution zips the properties of the Item class with the entries in the data tuple. Converting this to a dict, pydantics parse_obj function can be used.

Tabea
  • 39
  • 1
  • 7
0

One option is just to override the __init__, set the positional arguments and pass them as keyword arguments to the BaseModel's init:

from pydantic import BaseModel

class Item(BaseModel):
    name: str = ""
    surname: str = ""

    def __init__(self, name, surname):
        super().__init__(name=name, surname=surname)

data = ("jhon", "dhon")
Item(*data)

Outputs:

Item(name='jhon', surname='dhon')

If you have more fields and wish to have a more dynamic version of this approach, I added more examples to an answer for a similar question: https://stackoverflow.com/a/72657947/13696660

miksus
  • 2,426
  • 1
  • 18
  • 34