Using PugSQL and FastAPI

Using PugSQL and FastAPI

Most of the tutorials/guides that I read about FastAPI and Python web frameworks in general use SQLAlchemy directly. While SQLAlchemy is great, I personally prefer writing raw SQL over using an ORM.

PugSQL is a really interesting alternative that allows you to write your own SQL files that look like this:

-- :name get_url :scalar
SELECT url FROM urls WHERE slug = :slug;

And then use that in your Python code like this:

queries = pugsql.module("path/to/sql/files")
queries.connect("sqlite:///db/database.sqlite3")
url = queries.get_url(slug="search_for_me")

Using this alongside FastAPI allows you to write very clean looking code, like this:

import pugsql
from fastapi import FastAPI, HTTPException, Depends
from starlette.responses import RedirectResponse

app = FastAPI()

queries = pugsql.module("path/to/sql/files")
queries.connect("sqlite:///db/database.sqlite3")


@app.get("/{slug}")
async def redirect_to_full_url(slug: str):
    url = queries.get_url(slug=slug)
    if url is None:
        raise HTTPException(status_code=404)
    return RedirectResponse(url)

What’s really fantastic about this is the separation of concerns. At an application level, I’m just looking at a function call with arguments. I can easily mock queries when I’m testing.

At a SQL file level, each SQL query is performing one specific action. I provide a name for the action, a return type, and all the parameters. I can use integration tests to make sure these queries are correct independently from my application, like so:

queries = pugsql.module("path/to/sql/files")
queries.connect("sqlite:///db/database.sqlite3")

queries.create_url(slug="example", url="https://www.google.com")
assert queries.get_url(slug="example") == "https://www.google.com"
assert queries.get_url(slug="EXAMPLE") is None

If you want to see a full example backend built with these technologies, including database migrations, check out this blog post.