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