Skip to content

UnmappedInstanceError exception with (wrongly) unrefreshed model object #351

@orenl

Description

@orenl

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the SQLModel documentation, with the integrated search.
  • I already searched in Google "How to X in SQLModel" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to SQLModel but to Pydantic.
  • I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

import pytest

from pydantic import BaseModel
from typing import List, Optional
from sqlmodel import Session, SQLModel, Field, Relationship, create_engine
from sqlmodel.pool import StaticPool

# MODELS

class Ticket(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)

class Mascot(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)

class Groups(SQLModel):
    ticket: Ticket
    mascot: Mascot

# FIXTURES

@pytest.fixture(name='session')
def session_fixture():
    engine = create_engine(
        "sqlite://",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    SQLModel.metadata.create_all(engine)
    with Session(engine) as session:
        yield session

@pytest.fixture(name='ticket')
def ticket_fixture(session: Session):
    t = Ticket()
    session.add(t)
    session.commit()
    session.refresh(t)
    yield t

@pytest.fixture(name='mascot')
def mascot_fixture(session: Session):
    m = Mascot()
    session.add(m)
    session.commit()
    session.refresh(m)
    yield m

# TEST

def test_combine(
    session: Session,
    ticket: Ticket,
    mascot: Mascot,
):
    # Problem 1: @ticket is empty, @mascot is set (reverse their order would
    # reverses the outcome). Access to any field in @ticket fixes the problem.
    print(f'ticket = {ticket.__dict__}')           # <-- ticket is empty, no ticket.id value
    print(f'mascot = {mascot.__dict__}')           # <-- mascot has proper mascot.id as expected

    # this works (and when un-commented, makes problem 2 below disappear):
    # id = ticket.id if ticket else 0

    # Problem 2: this fails (but making class Groups not inherit SQLModel or
    # BaseModel, makes the problem disappear):
    group = Groups(ticket=ticket, mascot=mascot)
    id = group.ticket.id if group.ticket else 0    # <-- sqlalchemy.orm.exc.UnmappedInstanceError

Description

Use pytest to run this sample code.

The code defines two simple models/tables (Ticket, Mascot) and a third model (Group) that groups them. It uses pytest to populate the database for testing. The are two problems that occur in the function test_combine():

  1. The function takes three fixtures: session and the models ticket, mascot. The expected behavior is to have both properly initialized inside the function. However, in reality only the latter (mascot) holds data while the former (ticket) appears empty. If we reverse their order in the function arguments, the reverse happens. Also, any access to the content (e.g. to ticket.id) refreshes the object - so perhaps somehow the object became stale.

  2. The function then instantiates a Group object containing the two models, and then tries to access the (stale, from before) ticket. This time, the object does not get auto-refreshed, but instead an exception is raised. Like before, access to the content (e.g. to ticket.id) any time earlier makes the problem go away. Also, changing class Group to not inherit from SQLModel (or pydantic.BaseModel), also resolves the problem. So perhaps this is related to nested models (even though Group is not intended to be user/input facing so does not strictly need SQLModel/BaseModel capabilities).

Operating System

Linux

Operating System Details

Ubuntu 21.10
sqlalchemy 1.4.35
pydantic 1.9.1
pytest 7.1.2

SQLModel Version

0.0.6

Python Version

3.9

Additional Context

To run the test, run the command:

pytest -s reproduce.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions