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 asynchronous sqlalchemy example #2331

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ArcLightSlavik
Copy link
Contributor

@ArcLightSlavik ArcLightSlavik commented Nov 9, 2020

Recently sqlalchemy released the beta for the 1.4 release, which includes support for asynchronous calls.
This PR adds an example based on (https://fastapi.tiangolo.com/tutorial/sql-databases/) but with asynchronous calls.

@codecov
Copy link

codecov bot commented Nov 9, 2020

Codecov Report

Attention: 148 lines in your changes are missing coverage. Please review.

Comparison is base (cf73051) 100.00% compared to head (982da54) 98.57%.
Report is 1078 commits behind head on master.

❗ Current head 982da54 differs from pull request most recent head 9c2a553. Consider uploading reports for the commit 9c2a553 to get more accurate results

Files Patch % Lines
docs_src/async_sql_databases/sql_app/main.py 0.00% 39 Missing ⚠️
docs_src/async_sql_databases/sql_app/crud.py 0.00% 35 Missing ⚠️
.../async_sql_databases/sql_app/tests/test_sql_app.py 0.00% 30 Missing ⚠️
docs_src/async_sql_databases/sql_app/schemas.py 0.00% 22 Missing ⚠️
docs_src/async_sql_databases/sql_app/models.py 0.00% 17 Missing ⚠️
docs_src/async_sql_databases/sql_app/database.py 0.00% 5 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##            master    #2331      +/-   ##
===========================================
- Coverage   100.00%   98.57%   -1.43%     
===========================================
  Files          540      415     -125     
  Lines        13969    10412    -3557     
===========================================
- Hits         13969    10264    -3705     
- Misses           0      148     +148     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ArcLightSlavik
Copy link
Contributor Author

ArcLightSlavik commented Nov 14, 2020

Added an issue to pydantic for the response_model: pydantic/pydantic#2124

The test added here fails because of this issue.
Also includes a workaround if anyone is interested.

@github-actions
Copy link
Contributor

📝 Docs preview for commit 0eb012e at: https://5fb02820cf2b1c96da914104--fastapi.netlify.app

@Kludex
Copy link
Member

Kludex commented Nov 14, 2020

Can you push the failing commit so I can debug? 🥺

@Mause
Copy link
Contributor

Mause commented Nov 15, 2020

@ArcLightSlavik you need to use .scalars().all() instead of just .all() to fix your pydantic validation problem

@github-actions
Copy link
Contributor

📝 Docs preview for commit db9b2cf at: https://5fb0d245fcd4be0010bc0424--fastapi.netlify.app

@github-actions
Copy link
Contributor

📝 Docs preview for commit fe9b662 at: https://5fb0d37199973915dc505d90--fastapi.netlify.app

@github-actions
Copy link
Contributor

📝 Docs preview for commit 0811c04 at: https://5fb2ba7646867616716f246c--fastapi.netlify.app

Copy link

@yenchenLiu yenchenLiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a problem, it comes from Sqlalchemy. And the problem will appear in PostgreSQL.
When the crud is operated for the second time, the first time object will become unavailable.
Because sqlalchemy need to query again, but it is not await at this time.

@ArcLightSlavik
Copy link
Contributor Author

@yenchenLiu Haven't written about this issue here, but it's been known for weeks now. I am not sure which dependancy is at fault for it so I haven't made an issue. I assume it will be fixed in beta/release version of async alchemy.

@ArcLightSlavik
Copy link
Contributor Author

SQLAlchemy released a 2nd beta and from my testing everything seems to be working.

@yenchenLiu Could you install SQLAlchemy==1.4.0b2 and check if the error happens, if yes could you link the code? Can't replicate it as of right now.

@jokull
Copy link

jokull commented Mar 16, 2021

Did a quick test but SQLAlchemy complained that the default sqlite driver was not async. Does there even exist an async sqlite driver?

@gerazenobi
Copy link

gerazenobi commented Mar 16, 2021

FYI: SQLAlchemy 1.4 has just been released.

@jokull from the release docs I think not:

Complete support for Python asyncio for both Core and ORM. This new system leverages the greenlet context-switching library to adapt the entirety of SQLAlchemy to be usable behind a very thin asyncio front-end while operating directly against asyncio database drivers within the asyncio event loop, with no need for thread executors. Support for asyncpg and aiomysql is included so far with more drivers in development.

@kurtrottmann
Copy link

Hi @ArcLightSlavik,

I tried this with PostgreSQL in the past (1.4.0b1) and with SQLAlchemy 1.4.1 today, and it doesn't work properly as @yenchenLiu mentions.

A workaround I found is to use lazy='subquery' or lazy='selectin' in items relationship. Example:

items = relationship("Item", back_populates="owner", lazy='subquery')

Also is necessary to remove "check_same_thread" parameter from create_async_engine when PostgreSQL is used.

@gerazenobi
Copy link

@kurtrottmann indeed. Relationships don't work quite the same in async mode as stated in the async sqlalchemy documentation: here

@Kludex
Copy link
Member

Kludex commented Mar 19, 2021

Maybe adding an update (put) method?

@ycd ycd mentioned this pull request Mar 22, 2021
9 tasks
@aaronstephenson
Copy link

Thank you so much @ArcLightSlavik for writing this! It was instrumental in my attempt to get the async ORM working in my app. I hope this makes it to the official docs soon so others can benefit from it.

Also thanks to @kurtrottmann for your tips, they likely saved me a lot of time. I have several levels of nested relationships in my app, so I ended up declaring my relationships using backref and lazy=joined. Example:

items = relationship("Item", backref="owner", lazy='joined')

I did that because putting lazy in both sides of the back_populates relationship slowed the query down enormously. Maybe that's documented somewhere, but I didn't see it after doing a lot of reading, so I discovered the hard way that lazy should only be included on one side of the relationship. Declaring the relationship once with backref instead of twice with back_populates makes this more straightforward to me and my team (no need to question why the two sides of a relationship declaration have differing numbers of parameters, and a lot fewer lines of code to maintain across our many models).

@jcf-dev
Copy link

jcf-dev commented Mar 26, 2021

1.4.3 was released today with aiosqlite support.

@grillazz
Copy link

grillazz commented Mar 28, 2021

@ArcLightSlavik i have ready to use here solution: https://github.com/grillazz/fastapi-sqlalchemy-asyncpg
if you dont mind i can polish this PR and add missing parts

@github-actions
Copy link
Contributor

📝 Docs preview for commit de5eab2 at: https://6060f61a45aa8bf9b4eecf14--fastapi.netlify.app

@ArcLightSlavik
Copy link
Contributor Author

Don't have time to add the Update method, so if anyone wants to do that I would be very happy 🙂
We do need to synchronize solutions with #2665 (async here / non async there). I've took the liberty to implement the delete method similar to https://github.com/tiangolo/full-stack-fastapi-postgresql but update would be more complex with password.

@grillazz
Copy link

grillazz commented Mar 30, 2021

Don't have time to add the Update method, so if anyone wants to do that I would be very happy 🙂
We do need to synchronize solutions with #2665 (async here / non async there). I've took the liberty to implement the delete method similar to https://github.com/tiangolo/full-stack-fastapi-postgresql but update would be more complex with password.

@ArcLightSlavik is below model method a solution for pass update for you:
@password.setter def password(self, psw):
?

@aakashnand
Copy link
Contributor

@grillazz Did you tagged correct person in above comment? I am bit out of context here 😄

@grillazz
Copy link

@grillazz Did you tagged correct person in above comment? I am bit out of context here 😄

sorry my bad

@ArcLightSlavik
Copy link
Contributor Author

@grillazz Not really, I want the main insparation taken from https://github.com/tiangolo/full-stack-fastapi-postgresql/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/app/crud/crud_user.py#L27, but without the usage of classes.
Aka:

async def update_user(db: AsyncSession, .......................):
    .... do stuff

@jcf-dev
Copy link

jcf-dev commented Apr 18, 2021

The get_db dependency causes a weird
TypeError: <async_generator object get_db at 0x7ff6d9d9aa60> is not a callable object
issue on my project.

Here's my code:

db.py

from typing import Generator
from .db.session import SessionLocal

async def get_db() -> Generator:
    try:
        db = SessionLocal()
        yield db
    finally:
        await db.close()

session.py

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from .core.config import settings

engine = create_async_engine(
    settings.SQLALCHEMY_DATABASE_URI,
    pool_pre_ping=True
)
SessionLocal = AsyncSession(
    autocommit=False,
    autoflush=False,
    bind=engine
)

@grillazz
Copy link

grillazz commented Apr 18, 2021

@joweenflores below is working well for me:

from typing import AsyncGenerator
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

from the_app import config

global_settings = config.get_settings()
url = global_settings.asyncpg_url

engine = create_async_engine(
    url,
    echo=True,
)

# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


# Dependency
async def get_db() -> AsyncGenerator:
    session = async_session()
    try:
        yield session
        await session.commit()
    except SQLAlchemyError as ex:
        await session.rollback()
        raise ex
    finally:
        await session.close()

@ArcLightSlavik
Copy link
Contributor Author

Already in #3096 but fix was using Depends(get_db) not Depends(get_db())

@github-actions
Copy link
Contributor

📝 Docs preview for commit a6ce99c at: https://6081db5166760a00dedd6827--fastapi.netlify.app

@musicinmybrain
Copy link
Contributor

Currently, sqlalchemy 1.4 is not allowed by pyproject.toml. I suppose that restriction would need to be loosened as well.

@ArcLightSlavik
Copy link
Contributor Author

@musicinmybrain That would be a separate PR, it doesn't affect end users only the tests in this project.

@Kludex
Copy link
Member

Kludex commented Jul 9, 2021

Actually, @musicinmybrain is right.
The thing is that everything on the docs_src should be added on the tests as well, otherwise the coverage will drop.
Unfortunately, this is only true if #1904 gets merged. 😅

Well, in summary, the version needs to change. 😗

@ArcLightSlavik
Copy link
Contributor Author

Eh fair I guess, although the sound of tests for documentation scares me 😄

@Kludex
Copy link
Member

Kludex commented Jul 9, 2021

In the case of this PR, I guess you just need to reimport the test function, you can check my PR above or click here.

@ArcLightSlavik
Copy link
Contributor Author

If someone could do it I would be happy, don't have time to look into it for a while 😢

@jacobhjkim
Copy link

Any updates on this PR?

@github-actions
Copy link
Contributor

github-actions bot commented Oct 8, 2021

📝 Docs preview for commit 982da54 at: https://61604430f990703b9c0e6678--fastapi.netlify.app

@github-actions
Copy link
Contributor

github-actions bot commented May 1, 2022

📝 Docs preview for commit ce89c33 at: https://626eec6527fc40141e35602a--fastapi.netlify.app

@tiangolo tiangolo added the docs Documentation about how to use FastAPI label Oct 2, 2023
@alejsdev alejsdev added the p4 label Jan 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation about how to use FastAPI p4
Projects
None yet
Development

Successfully merging this pull request may close these issues.