tortoise-orm icon indicating copy to clipboard operation
tortoise-orm copied to clipboard

Unexpected call to `to_python_value` when creating new "Model"

Open AntsyLich opened this issue 2 years ago • 1 comments

Describe the bug Models needs to take in db value when creating model instead of pythonic value.

To Reproduce

import traceback
from typing import List

from tortoise import Model, Tortoise, run_async
from tortoise.fields import TextField


class TextListField(TextField):
    __SEPERATOR = ", "

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def to_db_value(self, value: List[str], instance) -> str:
        print(f"`to_db_value` provided value type is {type(value)}")
        return self.__SEPERATOR.join(value)

    def to_python_value(self, value: str) -> List[str]:
        print(f"`to_python_value` provided value type is {type(value)}")
        return value.split(self.__SEPERATOR)


class Test(Model):
    list = TextListField()


async def init():
    await Tortoise.init(
        db_url='sqlite://db.sqlite3',
        modules={'models': ['__main__']}
    )
    await Tortoise.generate_schemas()

    try:
        await Test.create(list=["a", "b"])
    except AttributeError:
        data = traceback.format_exc()
        print(data)
    await Test.create(list="a, b")


if __name__ == '__main__':
    run_async(init())

Output for first create:

`to_python_value` provided value type is <class 'list'>
Traceback (most recent call last):
  File "E:\Codes\main.py", line 36, in make_test_with_list
    await Test.create(list=["a", "b"])
  File "E:\Codes\venv\lib\site-packages\tortoise\models.py", line 1132, in create
    instance = cls(**kwargs)
  File "E:\Codes\venv\lib\site-packages\tortoise\models.py", line 671, in __init__
    for key in meta.fields.difference(self._set_kwargs(kwargs)):
  File "E:\Codes\venv\lib\site-packages\tortoise\models.py", line 698, in _set_kwargs
    setattr(self, key, field_object.to_python_value(value))
  File "E:\Codes\main.py", line 20, in to_python_value
    return value.split(self.__SEPERATOR)
AttributeError: 'list' object has no attribute 'split'

Output for second create:

`to_python_value` provided value type is <class 'str'>
`to_db_value` provided value type is <class 'list'>

Process finished with exit code 0

Expected behavior My expectation is that in the first create shouldn't fail

Additional context In my opinion the call to to_python_value isn't supposed to happen.

AntsyLich avatar Aug 14 '22 12:08 AntsyLich

Hi, @AntsyLich Use of Model.create(**data) or Model(**data) will cause Tortoise to call the Field.to_python_value ensuring each field model receives its value having the expected types. Case you want to allow different value types (str, list, and whatever), you can make your to_python_value a little more dynamic using singledispatchmethod.

isaquealves avatar Aug 19 '22 19:08 isaquealves