Python Friday #240: Asynchronous SQLAlchemy With FastAPI

Last week we got pytest to run asynchronous test methods. That was the preparation step for this post where we switch to asynchronous SQLAlchemy for our to-do application. As it turns out, switching to asynchronous methods for SQLAlchemy takes a lot of work. Let us get through the different changes we need to make.

This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.

 

Install the asynchronous SQLite driver

The default driver for SQLite only works with synchronous commands. To access SQLite with the asynchronous engine, we need to install this package:

 

Update the data store tests

We start our change by turning our tests for the data store into asynchronous methods. As we learned last week, this consists of these 4 main steps:

  1. Add the async keyword in front of our test methods.
  2. Add the async decorator to our test methods.
  3. Add an await in front of all method calls to our data store.
  4. Add the async fixture decorator to our fixture.

In our fixture we call the function create_async_session_factory() to get the asynchronous session factory. While we could work with the session in our data store in the synchronous world, we cannot do the same with the asynchronous engine. If we try, we get an endless list of errors about closed transactions and other problems. Therefore, we will put the factory inside our data store.

 

Create the asynchronous session factory

In our data/database.py file, we can add the new method to create an asynchronous session factory:

We now use our new driver to access the SQLite database, but otherwise the method works as before.

 

Change the DataStoreDb

For our DataStoreDb class, we need to make these adjustments:

  1. Use AsyncIterator[AsyncSession] instead of the session in the constructor.
  2. Add the async keyword in front of all public methods.
  3. Wrap an async with block around everything that uses the database.
  4. Rewrite the queries to run asynchronously.
  5. In the statistics method, we turn the result cursor into a list to prevent a cursor error.

With all those changes, our DataStoreDb class now looks like this:

We can now run our tests for the datastore, and they all should pass. However, the tests for FastAPI application will fail. Let us fix that.

 

Fix the endpoint tests

The tests for our FastAPI endpoints need an adjustment for the override_get_db() function. All the methods need the await keyword and the marker, and we need to await the call to the prepare_task() function:

 

Fix the dependencies.py code

We need to switch to the create_async_session_factory() function and change how we initialise our data store:

 

Fix the FastAPI endpoints

In all our endpoints we need to await the call to the db.* methods, the rest of the files can stay the same.

Our main.py file has one place to await the call to the db.get_statistics() method:

In the routers/todo.py file, we need an await in every method:

We can now run all tests and they should pass. Without those tests we would need to spend an awful amount of time to run the application by hand. Therefore, make sure that you have a good test coverage for your code before you start such a massive change.

 

Next

With all these changes we can now run our to-do application with asynchronous SQLAlchemy. As you can see in this post, even for a small application that is a massive change. It took me a few rounds to get everything back into a working state and I would not do it without a reliable test suite.

Next week we explore a way to get rid of our hand-written filter and replace it with something more powerful.

2 thoughts on “Python Friday #240: Asynchronous SQLAlchemy With FastAPI”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.