Basic fastapi but sync model
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,2 @@
|
|||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
@ -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…
Reference in New Issue