Skip to content

async - can't access parent.child item (relationship models) using async #74

@mnnweb

Description

@mnnweb

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 👆

Dependencies:
aiosqlite
https://pypi.org/project/aiosqlite/

Example Code

import asyncio
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
from typing import List, Optional
from sqlmodel import Field, Relationship, SQLModel, select


class Team(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    headquarters: str

    heroes: List["Hero"] = Relationship(back_populates="team")


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None

    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
    team: Optional[Team] = Relationship(back_populates="heroes")


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


sqlite_file_name = "database.db"
sqlite_url = f"sqlite+aiosqlite:///{sqlite_file_name}"

engine = create_async_engine(sqlite_url, echo=True)


async def create_db_and_tables():
    # SQLModel.metadata.create_all(engine)

    async with engine.begin() as conn:
        await conn.run_sync(SQLModel.metadata.drop_all)
        await conn.run_sync(SQLModel.metadata.create_all)


async def create_heroes():
    async with AsyncSession(engine) as session:
        team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
        hero_deadpond = Hero(
            name="Deadpond", secret_name="Dive Wilson", team=team_preventers
        )

        session.add(hero_deadpond)
        await session.commit()
        await session.refresh(hero_deadpond)

        print(hero_deadpond)


async def select_heroes():
    async with AsyncSession(engine) as session:
        statement = select(Team).where(Team.name == "Preventers")
        result = await session.execute(statement)
        team_preventers = result.scalar()
        print(f"Preventers team: {team_preventers}")
        print(f"Preventers heroes: {team_preventers.heroes}")


async def main():
    await create_db_and_tables()
    await create_heroes()
    await select_heroes()

if __name__ == "__main__":
    asyncio.run(main())

Operating System

macOS

Operating System Details

Using a MacBook with macOS running FastAPI with docker.

SQLModel Version

0.0.4

Python Version

3.8.6

Additional Context

We have two models with a one to many relationship (Team -> Hero).
Using async / await AsyncSession , we are trying to access the parent's child ( {team_preventers.heroes} ) but this is causing the following exception :

sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/14/xd2s)
sys:1: RuntimeWarning: coroutine 'Connection.cursor' was never awaited

Full stacktrace
Traceback (most recent call last):
  File "async_test.py", line 74, in <module>
    asyncio.run(main())
  File "/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "async_test.py", line 71, in main
    await select_heroes()
  File "async_test.py", line 65, in select_heroes
    print(f"Preventers heroes: {team_preventers.heroes}")
  File "/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 481, in __get__
    return self.impl.get(state, dict_)
  File "/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 926, in get
    value = self._fire_loader_callables(state, key, passive)
  File "/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 962, in _fire_loader_callables
    return self.callable_(state, passive)
  File "/lib/python3.9/site-packages/sqlalchemy/orm/strategies.py", line 892, in _load_for_state
    return self._emit_lazyload(
  File "/lib/python3.9/site-packages/sqlalchemy/orm/strategies.py", line 1028, in _emit_lazyload
    result = session.execute(
  File "/lib/python3.9/site-packages/sqlmodel/orm/session.py", line 101, in execute
    return super().execute(  # type: ignore
  File "/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 1689, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1583, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "/lib/python3.9/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1452, in _execute_clauseelement
    ret = self._execute_context(
  File "/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1814, in _execute_context
    self._handle_dbapi_exception(
  File "/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1999, in _handle_dbapi_exception
    util.raise_(exc_info[1], with_traceback=exc_info[2])
  File "/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 207, in raise_
    raise exception
  File "/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1771, in _execute_context
    self.dialect.do_execute(
  File "/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 717, in do_execute
    cursor.execute(statement, parameters)
  File "/lib/python3.9/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py", line 99, in execute
    self._adapt_connection._handle_exception(error)
  File "/lib/python3.9/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py", line 228, in _handle_exception
    raise error
  File "/lib/python3.9/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py", line 76, in execute
    _cursor = self.await_(self._connection.cursor())
  File "/lib/python3.9/site-packages/sqlalchemy/util/_concurrency_py3k.py", line 60, in await_only
    raise exc.MissingGreenlet(
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/14/xd2s)
sys:1: RuntimeWarning: coroutine 'Connection.cursor' was never awaited

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions