Python Friday #241: Filters for SQLAlchemy and FastAPI

Our filter for retrieving the tasks we are interested in still fetches all data from the database before applying the actual filtering. It is time to fix that and use a more flexible solution that works with the asynchronous SQLAlchemy that we introduced in the last post.

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

 

FastAPI Filter

There are multiple approaches to filter our data we can choose from. For this post I use FastAPI Filter, because it offers us a lot of flexibility. We can use everything it offers or, as I prefer, start small and add only the keywords we need.

By default, FastAPI Filter supports the following operators that we can append to our fields (field__[operator]):

  • neq – not equals
  • gt – greater than
  • gte – greater than or equal to
  • in – in a list of values
  • isnull – is NULL
  • lt – less than
  • lte – less than or equal to
  • not/ne – not or not equal to a specified value
  • not_in/nin – not in a list of values
  • like/ilike – like with case sensitive or case insensitive

 

Installation

We can install the fastapi-filter package with this command to support SQLAlchemy:

Keep in mind that you also need an asynchronous driver, for example aiosqlite, to connect to your database:

 

New tests for our data store

Even when we want to filter in FastAPI, we must make sure that we can use the filter with our database. Therefore, we start the implementation at the bottom and go upwards through our application.

Here are the tests we need to verify that the first filters work:

The tests currently fail, then we have no TaskFilter class and no filter() method on our data store.

 

Create the TaskFilter

The main component of FastAPI Filter is our filter object. We need to specify what fields we want to filter on and how we want to order the results.

If we specify our field as it is, we can filter for an exact match. If we want to use an operator from the list above, we can use our field name, add two _ and then use the operator as we did with due_date__lte.

The order_by uses the field name in ascending order if we do not overwrite it. That way we always get a sorted result.

The search field allows us to search for a part of the name. If we want to include other fields, we can add them to the list of search_model_fields.

As a final point, make sure that you set the default value to None. That way all the filters are optional. If you omit the None value, you turn them into required fields.

 

Extend the data store

In our data store, we add a new filter() method that uses the TaskFilter to expand the query with a WHERE and an ORDER BY clause:

I had to add an * in front of entry to convert it to our TaskOutput object. Otherwise, I got an error about a missing id attribute.

Our new tests for the data store should now work. If that is the case, we can go one layer up to FastAPI.

 

Change the endpoint tests

With our new FastAPI Filter package, the syntax to filter the tasks changes a bit. We can change the syntax of the existing tests that filter the tasks and add a few new tests to check that the order_by works as expected:

The endpoint tests that filter the tasks should all fail. To make them pass we need to wire-up the filters in our endpoint.

 

Change the /tasks endpoint

Our show_all_tasks() method needs to use our TaskFilter as a dependency and pass that filter to our data store. Our new Filter package comes with the FilterDepends keyword, that does all the magic to translate the query string to our TaskFilter object:

We can now run all our tests and they should pass.

 

Updated documentation

If we start our application and head to the /docs endpoint, we find the filter fields as part of the documentation:

Our filter fields are now part of the OpenAPI specification.

 

Next

With FastAPI Filter we can create our filter object and let the package write the correct query for us. We can focus on the filters we want and skip the rest for later. That offers us a lot of flexibility and still gives us a much better solution than the hand-written one we started with.

There is one downside of our change: our clients must adapt to the new way, or they can no longer use our service. Next week we look at ways to prevent this with an API endpoint versioning strategy.

1 thought on “Python Friday #241: Filters for SQLAlchemy and FastAPI”

Leave a Comment

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