Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an AwaitableField for accessing sqlmodel field asynchronously #872

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

2jun0
Copy link

@2jun0 2jun0 commented Apr 1, 2024

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

Copy link

github-actions bot commented Apr 1, 2024

📝 Docs preview for commit 2d9af2f at: https://bfab4bb5.sqlmodel.pages.dev

Copy link

github-actions bot commented Apr 1, 2024

📝 Docs preview for commit 548f005 at: https://d4117a75.sqlmodel.pages.dev

Copy link

github-actions bot commented Apr 1, 2024

📝 Docs preview for commit 1d7f73e at: https://b18eab40.sqlmodel.pages.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants