Multiple filter/search functionality using ODMantic, FastAPI, and list unpacking

First story! Here we go…

Mike Tarpey
3 min readJan 29, 2021
Zoomed-in shot of a MongoDB Camelbak water bottle, flanked on the left by a Dr. Robotnik figure and on the right by a Link figure from Breath of the Wild.
Thanks to MongoDB for keeping me hydrated these days.

I’ll talk a little bit about where I’ve come from and where I plan to go as a programmer in future pieces. For now, I want to get straight to something that I just implemented on my website a few days ago (currently just an app playground for me to try new things in Python and JavaScript).

The “have you played X?” app was originally inspired by the concept at backloggery.com, which was huge motivation for me to play my existing backlog of video games in college, instead of continually buying new ones. Before I got my first big break as an actuary, I had the backlog down to near zero…now it’s back up over 100 games. (Insert “time is money” adage here.) At a certain point I found myself tracking the backlog in Excel instead of on the site, mostly because the site hadn’t received an update in a while and my Excel skills had grown to the point where it was faster and easier to maintain in a spreadsheet (the daily life of an actuary). But once I found my second wind in programming and began updating my website for the first time in years, it only made sense to transition my backlog back to the web in some form.

Originally, the backend architecture for searching my backlog was simply a CSV loaded through Pandas, but now it’s evolved to a MongoDB Atlas text search. This alone was a significant performance improvement, but I wanted to also be able to filter the backlog based on certain attributes:

  • whether or not the game is downloadable content (DLC),
  • whether or not I’m currently playing the game, and
  • what “status” the game is in (not started, started, beaten, completed, etc.)

My brain was getting stuck on how to dynamically create one “all-in” query, one that would work regardless of whether the user provides all or none of the acceptable query arguments. After some trial and error, I was able to add this functionality using QueryExpression objects from ODMantic, which has already proven to be an excellent and speedy Python library for working with MongoDB on an async, object-mapped basis (and as an added bonus, it integrated pretty much seamlessly with FastAPI, which I also recently switched the site to).

You’ll find the full gist for how I solved the problem below:

To summarize:

  • The initial_args dictionary in line 12 accepts input from the endpoint defined in lines 1–8 (or None if the user provides no input for any of the arguments)
  • Line 19 creates a final_args dictionary that filters out any arguments with None values
  • I was struggling with creating a list of Python equalities using == that would dynamically be the correct length and format for ODMantic’s QueryExpression objects…the list comprehension in line 25, generated using the final_args dictionary, solved this problem nicely!
  • Finally, the magic lightbulb moment was figuring out line 27. Python’s single star operator allows us to unpack the query_expression_list as separate arguments for the “query.and_” function from ODMantic. This results in a single QueryExpression object called “combined_query_expression”, which can be used in line 35 against the database to filter results exactly as we would expect!

Putting it all together, a set of query parameters like ?dlc=N&now_playing=Y&game_status=Beaten will return some JSON that satisfies the conditions in the URL.

Hoping to learn and share more simple solutions as I continue growing as a actuary-turning-developer. Thanks for stopping by!

--

--

Mike Tarpey

software engineer | former actuary | uconn | upenn | citizen of Earth | ars longa, vita brevis