tortoise / tortoise/tortoise-orm

[BUG] get_or_create function raises "Object does not exist"

Open
#1,634 1 comment 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
While writing a discord bot for a project of a friend of mine i noticed that the get_or_create function does not seem to work as expected.

I can also fully confirm that every value has something in it.
I did a print before the model creation call which confirms that every parameter should receive a proper value.

The following error occurs: (The last one is obviously a discord error)

Traceback (most recent call last):
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/models.py", line 1075, in get_or_create
    return await cls.filter(**kwargs).using_db(db).get(), False
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/queryset.py", line 1073, 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/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/fields/base.py", line 284, in validate
    v(value)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/validators.py", line 44, in __call__
    raise ValidationError("Value must not be None")
tortoise.exceptions.ValidationError: Value must not be None

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/discord/app_commands/commands.py", line 827, in _do_call
    return await self._callback(self.binding, interaction, **params)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dominik/Projects/pirate-era-bot/pirate_era_bot/plugins/rule_book/rule_book.py", line 39, in designate_rule_channel
    rule_message_tuple = await RuleMessage(
                         ^^^^^^^^^^^^^^^^^^
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/models.py", line 1079, in get_or_create
    return await cls.create(using_db=connection, **defaults, **kwargs), True
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/models.py", line 1154, in create
    await instance.save(using_db=db, force_create=True)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/models.py", line 962, in save
    await executor.execute_insert(self)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/backends/base/executor.py", line 224, in execute_insert
    self.column_map[field_name](getattr(instance, field_name), instance)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/fields/base.py", line 256, in to_db_value
    self.validate(value)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/tortoise/fields/base.py", line 286, in validate
    raise ValidationError(f"{self.model_field_name}: {exc}")
tortoise.exceptions.ValidationError: title: Value must not be None

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/discord/app_commands/tree.py", line 1248, in _call
    await command._invoke_with_namespace(interaction, namespace)
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/discord/app_commands/commands.py", line 853, in _invoke_with_namespace
    return await self._do_call(interaction, transformed_values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dominik/.cache/pypoetry/virtualenvs/pirate-era-bot-doMltmxG-py3.12/lib/python3.12/site-packages/discord/app_commands/commands.py", line 846, in _do_call
    raise CommandInvokeError(self, e) from e
discord.app_commands.errors.CommandInvokeError: Command 'create-or-edit-rule-message' raised an exception: ValidationError: title: Value must not be None

To Reproduce
Using this code it will crash with the mentioned error.

    @app_commands.command(
        name="create-or-edit-rule-message",
        description="Create or edit rule explanation message",
    )
    @app_commands.describe(
        channel="Channel to designate as rule channel",
        title="Title of the rule message",
        description="Description of the rule message",
        colour="Colour of the embed. Must be in hexadecimal format. Example: #FF0000",
        footer="Footer of the rule message",
    )
    async def designate_rule_channel(
        self,
        interaction: discord.Interaction,
        channel: discord.TextChannel,
        title: str,
        description: str,
        colour: Optional[str],
        footer: Optional[str],
    ):
        print(f"Title: {title}")
        rule_message_tuple = await RuleMessage(
            id=channel.id,
            title=title,
            description=description,
            colour=colour,
            footer=footer,
        ).get_or_create()

        rule_message: RuleMessage = rule_message_tuple[0]

        embed = discord.Embed(
            title=rule_message.title,
            description=rule_message.description,
            colour=discord.Color.from_str(rule_message.colour),
        ).set_footer(text=rule_message.footer)

        assert not isinstance(interaction.channel, discord.ForumChannel)
        assert not isinstance(interaction.channel, discord.CategoryChannel)
        assert not isinstance(interaction.channel, discord.GroupChannel)

        if rule_message.message_id and isinstance(rule_message.message_id, int):
            created_message = channel.get_partial_message(rule_message.message_id)
            await created_message.edit(embed=embed)
            await interaction.response.send_message(
                f"Rule message {rule_message.title} edited with ID {rule_message.id}",
                ephemeral=True,
            )
        else:
            rule_message = await channel.send(embed=embed)
            rule_message = await RuleMessage.filter(id=interaction.channel_id).update(
                message_id=rule_message.id
            )
            await interaction.response.send_message(
                f"Rule message {rule_message.title} created with ID {rule_message.id}",
                ephemeral=True,
            )

RuleMessage looks like this:

from tortoise import fields
from tortoise.models import Model


class RuleMessage(Model):
    id = fields.BigIntField(primary_key=True)
    title = fields.CharField(256)  # Discord limitations
    description = fields.CharField(4096)  # Discord limitations
    colour = fields.CharField(7, null=True)  # Hexadecimal colour
    footer = fields.CharField(2048, null=True)  # Discord limitations
    message_id = fields.BigIntField(null=True)

    def __str__(self) -> str:
        return f"title={self.title} description={self.description} colour={self.colour} footer={self.footer}"

Expected behavior
The model is either written to the database or retrieved from the database.

Additional context
Using .save() on it saves it to the database and works as expected.
But this is rather cumbersome.

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 with tortoise/models.py around get_or_create, then compare it with the supplied RuleMessage model and reproduction in the Discord command. Check how the call handles the shown lookup and creation arguments, and verify the expected behavior with a focused reproduction or existing ORM tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
database
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.