tortoise / tortoise/tortoise-orm

save(force_update=True) error in specific cases

Open
#1,212 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.6k
Forks
516
Avg merge
2d 21h
Merged PRs (30d)
9

Description

Describe the bug
Use save(force_update=True) after getting an instance will cause an unexpected error

To Reproduce

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

class User(Model):
    id: int = fields.IntField(pk=True)
    name: str = fields.CharField(max_length=255)

async def main():
    await Tortoise.init(db_url="mysql://test@localhost/test", modules={"models": ["__main__"]})
    await Tortoise.generate_schemas()

    # create a user, not important here
    await User.create(name='111')

    user = await User.get(id=1)
    user.update_from_dict(dict(name='222'))
    await user.save(force_update=True)  # raise exception

if __name__ == '__main__':
    run_async(main())
  File "/app/tortoise-bug/.venv/lib/python3.10/site-packages/tortoise/models.py", line 948, in save
    raise IntegrityError(f"Can't update object that doesn't exist. PK: {self.pk}")
tortoise.exceptions.IntegrityError: Can't update object that doesn't exist. PK: 1

Expected behavior
Should working

Additional context

# my test environment
python version: 3.10
tortoise-orm version: 0.18.1 and 0.19.2
mysql: mariadb 10.8.3 and mysql 5.8 (GCP)
  1. DB engine uses the sample sqlite without error
--- await Tortoise.init(db_url="mysql://test@localhost/test", modules={"models": ["__main__"]})
+++ await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]})
  1. The instance is not from get will without error
--- await User.create(name='111')
--- user = await User.get(id=1)
+++ user = await User.create(name='111')
  1. save method does not use the force_update parameter without error
--- await user.save(force_update=True)  # raise exception
+++ await user.save()

in "/home/shonen/tortoise-bug/.venv/lib/python3.10/site-packages/tortoise/models.py", line 948

    elif force_update:
        rows = await executor.execute_update(self, update_fields)

        # my test
        assert rows==1, f'{rows=} {type(rows)=}'

        if rows == 0:
            raise IntegrityError(f"Can't update object that doesn't exist. PK: {self.pk}")
        created = False
AssertionError: rows=0 type(rows)=<class 'int'>

In addition, an unbelievable result occurred in my company's environment (with FastAPI)
(but not successfully reproduced in the minimal example)

    elif force_update:
        rows = await executor.execute_update(self, update_fields)

        # my test
        assert rows==1, f'{rows=} {type(rows)=}'
        raise Exception(f'{rows=} {type(rows)=}')

        if rows == 0:
            raise IntegrityError(f"Can't update object that doesn't exist. PK: {self.pk}")
        created = False
File "/app/tortoise-bug/.venv/lib/python3.10/site-packages/tortoise/models.py", line 944, in save
    raise Exception(f'{rows=} {type(rows)=}')
Exception: rows=1 type(rows)=<class 'int'>

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in tortoise/models.py around the save method at line 948, then follow execute_update for the force_update path. Reproduce the example with MySQL or MariaDB and compare it with the provided SQLite case and the differing rows values. Done means saving an instance loaded with User.get(id=1) succeeds with force_update=True when the database row exists.

Written by the indexing model from the issue text.

Assessment

Tech stack
mysql, python
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.