tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.57k stars 378 forks source link

model.update_or_create KeyError #1583

Closed jiangying000 closed 3 months ago

jiangying000 commented 5 months ago

https://github.com/tortoise/tortoise-orm/blob/0c8efde78c4dae480b229e75790631297cab7fb3/tortoise/models.py#L1065

would cause KeyError for following code:

class Faqs(BaseModel):
    id = fields.UUIDField(pk=True)
    question = fields.TextField(null=True)

@pytest.mark.asyncio
async def test_tortoise_update_or_create():
    # suppose entry exists
    faq = await Faqs.get(id="6aadafff-e0b9-497b-aeb2-65498a35b6f5")
    # modify question
    faq.question = "q111234"
    # assign new id
    faq.id = uuid.uuid4()
    # update_or_create, KeyError
    new_faq = await Faqs.update_or_create(id=faq.id, defaults=dict(faq))

ERROR like:

    @classmethod
    async def get_or_create(
        cls: Type[MODEL],
        defaults: Optional[dict] = None,
        using_db: Optional[BaseDBAsyncClient] = None,
        **kwargs: Any,
    ) -> Tuple[MODEL, bool]:
        """
        Fetches the object if exists (filtering on the provided parameters),
        else creates an instance with any unspecified parameters as default values.

        :param defaults: Default values to be added to a created instance if it can't be fetched.
        :param using_db: Specific DB connection to use instead of default bound
        :param kwargs: Query parameters.
        :raises IntegrityError: If create failed
        :raises TransactionManagementError: If transaction error
        """
        if not defaults:
            defaults = {}
        db = using_db or cls._choose_db(True)
        async with in_transaction(connection_name=db.connection_name) as connection:
            try:
                return (
                    await cls.select_for_update()
                    .filter(**kwargs)
                    .using_db(connection)
                    .get(),
                    False,
                )
            except DoesNotExist:
                try:
                    return (
>                       await cls.create(using_db=connection, **defaults, **kwargs),
                        True,
                    )
E                   KeyError: 'id'

..\.venv\lib\site-packages\tortoise\models.py:1102: KeyError
waketzheng commented 5 months ago

I got different errors with the following code snippet:

import uuid

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

class Faqs(Model):
    id = fields.UUIDField(pk=True)
    question = fields.TextField(null=True)

async def run():
    await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]})
    await Tortoise.generate_schemas()

    faq, _ = await Faqs.get_or_create(id="6aadafff-e0b9-497b-aeb2-65498a35b6f5")
    # modify question
    faq.question = "q111234"
    # assign new id
    faq.id = uuid.uuid4()
    # update_or_create, KeyError
    new_faq, created = await Faqs.update_or_create(id=faq.id, defaults=dict(faq))
    print(dict(new_faq), created)

if __name__ == "__main__":
    run_async(run())

ERROR is:

Traceback (most recent call last):
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1079, in get_or_create
    await cls.select_for_update().filter(**kwargs).using_db(connection).get(),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wenping/github/tortoise-orm/tortoise/queryset.py", line 1074, in _execute
    raise DoesNotExist("Object does not exist")
tortoise.exceptions.DoesNotExist: Object does not exist

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/wenping/github/tortoise-orm/examples/fastapi/run.py", line 27, in <module>
    run_async(run())
  File "/home/wenping/github/tortoise-orm/tortoise/__init__.py", line 688, in run_async
    loop.run_until_complete(coro)
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 685, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/wenping/github/tortoise-orm/examples/fastapi/run.py", line 22, in run
    new_faq, created = await Faqs.update_or_create(id=faq.id, defaults=dict(faq))
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1129, in update_or_create
    return await cls.get_or_create(defaults, db, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1084, in get_or_create
    return await cls.create(using_db=connection, **defaults, **kwargs), True
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: tortoise.models.Model.create() got multiple values for keyword argument 'id'
waketzheng commented 5 months ago

After change the Python version to 3.10, got the KeyError:

Traceback (most recent call last):
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1079, in get_or_create
    await cls.select_for_update().filter(**kwargs).using_db(connection).get(),
  File "/home/wenping/github/tortoise-orm/tortoise/queryset.py", line 1074, in _execute
    raise DoesNotExist("Object does not exist")
tortoise.exceptions.DoesNotExist: Object does not exist

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/wenping/github/tortoise-orm/examples/fastapi/run.py", line 27, in <module>
    run_async(run())
  File "/home/wenping/github/tortoise-orm/tortoise/__init__.py", line 688, in run_async
    loop.run_until_complete(coro)
  File "/home/wenping/.pyenv/versions/3.10.2/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
    return future.result()
  File "/home/wenping/github/tortoise-orm/examples/fastapi/run.py", line 22, in run
    new_faq, created = await Faqs.update_or_create(id=faq.id, defaults=dict(faq))
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1129, in update_or_create
    return await cls.get_or_create(defaults, db, **kwargs)
  File "/home/wenping/github/tortoise-orm/tortoise/models.py", line 1084, in get_or_create
    return await cls.create(using_db=connection, **defaults, **kwargs), True
KeyError: 'id'
waketzheng commented 4 months ago

For why it raises KeyError instead of TypeError with Python<3.12, see this: https://stackoverflow.com/questions/78427983/why-does-dictid-1-id-2-sometimes-raise-keyerror-id-instead-of-a

waketzheng commented 3 months ago

@jiangying000 This issue can be closed as it fixed.