Basic fastapi but sync model

master
Drew Bednar 3 years ago
parent a36d3b86f8
commit f0b7a90b40

@ -0,0 +1,2 @@
__pycache__
env

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Michael Herman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,62 @@
# FastAPI + SQLModel + Alembic
Following the https://testdriven.io/blog/fastapi-sqlmodel/ blog post
## Docker Containers
Bring it up
```
docker-compose up -d --build
```
Shut it down
```
docker-compose down -v
```
The -v removes names volumes declared in the `volumes` section of the compose
file and anonymous volumes attached to containers
### Logs
```
docker-compose logs web
```
You'll see what is logged to stdout. Should include your new models on startup.
or
```
docker-compose logs db
```
### PSQL for working in your DB
```
docker-compose exec db psql --username=postgres --dbname=foo
```
```
foo=# \dt
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | song | table | postgres
```
## Interactions with API
### Add a song
```
curl -d '{"name":"Midnight Fit", "artist":"Mogwai"}' \
-H "Content-Type: application/json" -X POST http://localhost:8004/songs
```
Response:
```
{"id":1,"artist":"Mogwai","name":"Midnight Fit"}%
```
## Sources Root in Pycharm
I marked "project" directory as the "Sources Root". PyCharm uses the source roots as the starting point for
resolving imports.

@ -0,0 +1,24 @@
version: '3.8'
services:
web:
build: ./project
command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000
volumes:
- ./project:/usr/src/app
ports:
- 8004:8000
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/foo
depends_on:
- db
db:
image: postgres:13.4
expose:
- 5432
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=foo

@ -0,0 +1,22 @@
# pull official base image
FROM python:3.9-slim-buster
# set working directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install system dependencies
RUN apt-get update \
&& apt-get -y install netcat gcc postgresql \
&& apt-get clean
# install python dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# add app
COPY . .

@ -0,0 +1,22 @@
import os
from sqlmodel import create_engine, SQLModel, Session
DATABASE_URL = os.environ.get("DATABASE_URL")
# The major differences between SQLModel's create_engine and SQLAlchemy's
# version is that the SQLModel version adds type annotations (for editor support)
# and enables the SQLAlchemy "2.0" style of engines and connections.
engine = create_engine(DATABASE_URL, echo=True) # Will want a "debug mode where it doesn't echo for prod
def init_db():
SQLModel.metadata.create_all(engine)
def get_session():
"""A coroutine function for getting a database session."""
with Session(engine) as session:
yield session

@ -0,0 +1,39 @@
from fastapi import Depends, FastAPI
from sqlalchemy import select
from sqlmodel import Session
from app.db import get_session, init_db
from app.models import Song, SongCreate
app = FastAPI()
@app.on_event("startup")
def on_startup():
"""
It's worth noting that from app.models import Song is required.
Without it, the song table will not be created. Kind of like
how I have to do my flask sql alchemy models
"""
init_db()
@app.get("/ping")
async def pong():
return {"ping": "pong!"}
@app.get("/songs", response_model=list[Song])
def get_songs(session: Session = Depends(get_session)):
result = session.execute(select(Song))
songs = result.scalars().all()
return [Song(name=song.name, artist=song.artist, id=song.id) for song in songs]
@app.post("/songs")
def add_song(song: SongCreate, session: Session = Depends(get_session)):
song = Song(name=song.name, artist=song.artist)
session.add(song)
session.commit()
session.refresh(song)
return song

@ -0,0 +1,28 @@
from sqlmodel import SQLModel, Field
class SongBase(SQLModel):
"""
SongBase is the base model that the others inherit from.
It has two properties, name and artist, both of which are strings.
This is a data-only model since it lacks table=True, which means
that it's only used as a pydantic model
"""
name: str
artist: str
class Song(SongBase, table=True):
"""
Song, meanwhile, adds an id property to the base model. It's a table model,
so it's a pydantic and SQLAlchemy model. It represents a database table.
"""
id: int = Field(default=None, primary_key=True)
class SongCreate(SongBase):
"""
SongCreate is a data-only, pydantic model
that will be used to create new song instances.
"""
pass

@ -0,0 +1,4 @@
fastapi==0.68.1
psycopg2-binary==2.9.1
sqlmodel==0.0.4
uvicorn==0.15.0
Loading…
Cancel
Save