sqlmodel
sqlmodel copied to clipboard
Add an AwaitableField for accessing sqlmodel field asynchronously
I'm not that good at English. So, if there's anything you don't understand, please feel free to reply.
Currently, accessing some fields asynchronously is hard. I often encounter the MissingGreenlet error while programming asynchronously, which happens because attempting IO without using await keyword when accessing lazy loading or expired fields. (others seem to have this problem too #868 #74)
While SQLAlchemy provides the AsyncAttr Mixin, I found it not suitable for sqlmodel because the completion wasn't good enough.
So, I propose an AwaitableField, making access to other fields awaitable like below:
Usage
Create a AsyncSQLModel and add an awaitable field
You can easliy create a AsyncSQLModel Using the same interface as a sqlmodel. and an AwaitableField yields an awaitable field for the field specified in the argument.
from typing import Optional, Awaitable
from sqlmodel import Field
from sqlmodel.ext.asyncio.async_model import AsyncSQLModel, AwaitableField
class Hero(AsyncSQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
awt_name: Awaitable[str] = AwaitableField(field="name")
This allows fields which may be subject to lazy loading or deferred / unexpiry loading to be accessed like this:
hero = Hero(name="Rusty-Man")
async_session.add(hero)
await async_session.commit()
# the fields of "hero" have expired.
# Therefore, accessing them will raise MissingGreenlet error
print(hero.name)
# E sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called;
# can't call await_only() here. Was IO attempted in an unexpected place?
# (Background on this error at: https://sqlalche.me/e/20/xd2s)
# it works!
print(await hero.awt_name) # Rusty-Man
Access a Relationship Field using an AwaitableField
Using an AwaitableField with Relationship fields can resolve the problem encountered during lazy loading
from typing import Optional, Awaitable
from sqlmodel import Field, select
from sqlmodel.ext.asyncio.async_model import AsyncSQLModel, AwaitableField
class Team(AsyncSQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
heroes: List["Hero"] = Relationship()
awt_heroes: Awaitable[List["Hero"]] = AwaitableField(field="heroes")
class Hero(AsyncSQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship(back_populates="heroes")
awt_team: Awaitable[Optional[Team]] = AwaitableField(field="team")
...
hero = (
await session.exec(select(Hero).where(Hero.id == hero_rusty_man.id))
).one()
# loading lazy loading attribute will raise MissingGreenlet error
team = hero.team
# E sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called;
# can't call await_only() here. Was IO attempted in an unexpected place?
# (Background on this error at: https://sqlalche.me/e/20/xd2s)
# it works!
team = await hero.awt_team
📝 Docs preview for commit 2d9af2f8e3fc04172120ef062e8c8378e0bca708 at: https://bfab4bb5.sqlmodel.pages.dev
📝 Docs preview for commit 548f0050b3df31e0206d9157b2715912ff7ca643 at: https://d4117a75.sqlmodel.pages.dev
📝 Docs preview for commit 1d7f73e03ab0440d81719b7ce0a1398296acc1ab at: https://b18eab40.sqlmodel.pages.dev
@tiangolo this is very useful. My workaround now is a ton of lazy loads
any news on this issue?