What’s the right way to use PonyOrm with FastApi?

Viewed 2037

For a personal project I am using PonyOrm with FastApi ; is there a classy way to keep a db_session through the whole async lifecycle call of an endpoint ?

The documentation of PonyOrm talks about using the decorator and yield; but it didn't work for me so after looking on other Github projects, I found this workaround which is working fine.

But I don't really know what's happening behind the scenes and why the documentation of Pony isn't accurate about the async topic.

def _enter_session():
    session = db_session(sql_debug=True)
    Request.pony_session = session
    session.__enter__()


def _exit_session():
    session = getattr(Request, 'pony_session', None)
    if session is not None:
        session.__exit__()


@app.middleware("http")
async def add_pony(request: Request, call_next):
    _enter_session()
    response = await call_next(request)
    _exit_session()
    return response

and then in a dependency for example :

async def current_user(
        username: str = Depends(current_user_from_token)) -> User:

    with Request.pony_session:
        # db actions 

and in an endpoint call :

@router.post("/token", response_model=Token)
async def login_for_access_token(
        request: Request,
        user_agent: Optional[str] = Header(None),
        form_data: OAuth2PasswordRequestForm = Depends()):

    status: bool = authenticate_user(
        form_data.username,
        form_data.password,
        request.client.host,
        user_agent)


@db_session
def authenticate_user(
        username: str,
        password: str,
        client_ip: str = 'Undefined',
        client_app: str = 'Undefined'):
    user: User = User.get(email=username)

If you guys have a better way or a good explanation, I would love to hear about it :)

2 Answers

I'm a kinda PonyORM developer and FastAPI user.

The problem with the async and Pony is that Pony uses transactions which in our understanding are atomic. Also we use thread local cache that can be used in another session if context will switch to another coroutine. I agree that we should add information about it in documentation.

To be sure everything will be okay you should use db_session as the context manager and be sure that you don't have async calls inside this block of code.

If your endpoints are not asynchronous you can also use db_session decorator for them.

In Pony we agree that using ContextVar instead of Local should help with some cases.

The answer in one sentence is: Use little shortliving sessions and don't interrupt them with async.

Try using a standard fastapi dependency:

from fastapi import Depends

async def get_pony():
    with db_session(sql_debug=True) as session:
        yield session


async def current_user(
    username: str = Depends(current_user_from_token),
    pony_session = Depends(get_pony)) -> User:

    with pony_session:
        # db actions 
Related