Skip to content

Instantly share code, notes, and snippets.

@petrilli
Last active December 28, 2024 13:14
Show Gist options
  • Save petrilli/81511edd88db935d17af0ec271ed950b to your computer and use it in GitHub Desktop.
Save petrilli/81511edd88db935d17af0ec271ed950b to your computer and use it in GitHub Desktop.
Example combination of FastAPI and Pydantic with aiosql and aiosqlite
# -*- coding: utf-8 -*-
"""Example combination of FastAPI and Pydantic with aiosql and aiosqlite.
This module demonstrates the minimum viable integration approach for putting
together a few components:
- FastAPI. This provides a very high-performance and type-driving approach to
building APIs in Python
- Pydantic. A powerful data validation library.
- aiosql. Inspired by annosql and originally Clojure's yeSql, a way to programatically,
and safely, drive SQL databases with their own DSL: SQL.
- aiosqlite. An asyncio compatible version of the sqlite database adapter.
What's Shown:
1. How to load queries for aiosql to use.
2. Creating a dependency in FastAPI that can be used to inject the database connection
wherever you need it in an API endpoint.
3. Using differeny Pydantic models for the database and the API, and allowing FastAPI
to automaticaly convert between them.
How to Use:
To use this, you will need to create a sqlite database called `testdb.sqlite`. You can
do this with the following script run in the same directory as this module:
$ sqlite3 testdb.sqlite3 <<EOM
CREATE TABLE greetings (
greeting_id INT PRIMARY KEY,
greeting TEXT NOT NULL,
note TEXT
);
INSERT INTO greetings (greeting, note) VALUES ("Test greeting", "Sample Note");
EOM
Once that's created, you'll also need to install the correct dependencies. I use
Pipenv, but you can use anything that manages a virtualenv. Just use a virtualenv.
You'll need to install the following packages:
fastapi
aiosql
aiosqlite
uvicorn
Once those are installed, you can run the script using something like this:
$ uvicorn main:app
Enjoy.
"""
from typing import List, Union
import aiosql
import aiosqlite
from fastapi import Depends, FastAPI
from pydantic import BaseModel
DB_FILE = "testdb.sqlite3"
SQL_QUERIES = """
-- name: get-all-greetings
-- Get all the greetings from the database
SELECT greeting_id, greeting, note FROM greetings;
"""
app = FastAPI()
queries = aiosql.from_str(SQL_QUERIES, "aiosqlite")
#############################################################################
# Pydantic Models
# This is used to provide strong typing on the data going into and out of the
# application. Note that normally, these would be stored in their own file,
# but to keep this simple, it's being kept in the same file.
class Greeting(BaseModel):
"""A greeting that can be used to the public.
Attributes:
greeting_id (int): The primary auto-generated key.
greeting (str): The human-readable greeting.
"""
greeting_id: int
greeting: str
class GreetingDB(Greeting):
"""The database representation of the greeting.
Attributes:
note (str): An internal-only notation for the greeting.
"""
note: Union[str, None]
#############################################################################
# Dependency
# Provide a re-usable way to inject a database connection into the individual
# endpoints.
async def get_db():
"""Return a database connection for use as a dependency.
This connection has the Row row factory automatically attached."""
db = await aiosqlite.connect(DB_FILE)
# Provide a smarter version of the results. This keeps from having to unpack
# tuples manually.
db.row_factory = aiosqlite.Row
try:
yield db
finally:
await db.close()
#############################################################################
# Database Queries
# Leverage the `aiosql` library to query the database. This is a thinner
# approach than an ORM, and also allows you to be very specific about the
# query witho fewer surprises.
async def get_greetings(conn: aiosqlite.Connection) -> List[GreetingDB]:
"""Get all the greetings in the database."""
greetings = await queries.get_all_greetings(conn)
# parse_obj allows us to use the Row returned to populate the model by
# name, rather than having to unpack the tuples.
return [GreetingDB.parse_obj(greeting) for greeting in greetings]
#############################################################################
# FastAPI Endpoints
# Finally, we create a FastAPI endpoint to be called. Note the response model
# that is used is different from the one returned by the database call. FastAPI
# will automatically convert them.
@app.get("/", response_model=List[Greeting])
async def test_get(db_connection: aiosqlite.Connection = Depends(get_db)):
greetings = await get_greetings(db_connection)
return greetings
@juanchavez4096
Copy link

Right now you can just:

async def get_greetings(conn: aiosqlite.Connection) -> List[GreetingDB]:
    """Get all the greetings in the database."""

    greetings = await queries.get_all_greetings(conn)

    # parse_obj allows us to use the Row returned to populate the model by
    # name, rather than having to unpack the tuples.
    return [GreetingDB(**dict(greeting)) for greeting in greetings]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment