Skip to content

Commit 1f45b41

Browse files
committed
init
0 parents  commit 1f45b41

File tree

18 files changed

+413
-0
lines changed

18 files changed

+413
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
env

LICENSE

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

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Developing and Testing an Asynchronous API with FastAPI and Pytest
2+
3+
## Want to learn how to build this?
4+
5+
Check out the [post](https://testdriven.io/blog/fastapi-crud).
6+
7+
## Want to use this project?
8+
9+
Build the images and run the containers:
10+
11+
```sh
12+
$ docker-compose up -d --build
13+
```
14+
15+
Test out the following routes:
16+
17+
1. [http://localhost:8002/ping](http://localhost:8002/ping)
18+
1. [http://localhost:8002/docs](http://localhost:8002/docs)
19+
1. [http://localhost:8002/notes](http://localhost:8002/notes)

docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: '3.7'
2+
3+
services:
4+
web:
5+
build: ./src
6+
command: uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000
7+
volumes:
8+
- ./src/:/usr/src/app/
9+
ports:
10+
- 8002:8000
11+
environment:
12+
- DATABASE_URL=postgresql://hello_fastapi:hello_fastapi@db/hello_fastapi_dev
13+
db:
14+
image: postgres:12.1-alpine
15+
volumes:
16+
- postgres_data:/var/lib/postgresql/data/
17+
environment:
18+
- POSTGRES_USER=hello_fastapi
19+
- POSTGRES_PASSWORD=hello_fastapi
20+
- POSTGRES_DB=hello_fastapi_dev
21+
22+
volumes:
23+
postgres_data:

src/Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# pull official base image
2+
FROM python:3.8.1-alpine
3+
4+
# set work directory
5+
WORKDIR /usr/src/app
6+
7+
# set environment variables
8+
ENV PYTHONDONTWRITEBYTECODE 1
9+
ENV PYTHONUNBUFFERED 1
10+
11+
# copy requirements file
12+
COPY ./requirements.txt /usr/src/app/requirements.txt
13+
14+
# install dependencies
15+
RUN set -eux \
16+
&& apk add --no-cache --virtual .build-deps build-base \
17+
libressl-dev libffi-dev gcc musl-dev python3-dev \
18+
postgresql-dev \
19+
&& pip install --upgrade pip setuptools wheel \
20+
&& pip install -r /usr/src/app/requirements.txt \
21+
&& rm -rf /root/.cache/pip
22+
23+
# copy project
24+
COPY . /usr/src/app/

src/app/__init__.py

Whitespace-only changes.

src/app/api/__init__.py

Whitespace-only changes.

src/app/api/crud.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from app.api.models import NoteSchema
2+
from app.db import notes, database
3+
4+
5+
async def post(payload: NoteSchema):
6+
query = notes.insert().values(title=payload.title, description=payload.description)
7+
return await database.execute(query=query)
8+
9+
10+
async def get(id: int):
11+
query = notes.select().where(id == notes.c.id)
12+
return await database.fetch_one(query=query)
13+
14+
15+
async def get_all():
16+
query = notes.select()
17+
return await database.fetch_all(query=query)
18+
19+
20+
async def put(id: int, payload: NoteSchema):
21+
query = (
22+
notes
23+
.update()
24+
.where(id == notes.c.id)
25+
.values(title=payload.title, description=payload.description)
26+
)
27+
return await database.execute(query=query)
28+
29+
30+
async def delete(id: int):
31+
query = notes.delete().where(id == notes.c.id)
32+
return await database.execute(query=query)

src/app/api/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydantic import BaseModel, Field
2+
3+
4+
class NoteSchema(BaseModel):
5+
title: str = Field(..., min_length=3, max_length=50)
6+
description: str = Field(..., min_length=3, max_length=50)
7+
8+
9+
class NoteDB(NoteSchema):
10+
id: int

src/app/api/notes.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from typing import List
2+
3+
from app.api import crud
4+
from app.api.models import NoteDB, NoteSchema
5+
from fastapi import APIRouter, HTTPException, Path
6+
7+
router = APIRouter()
8+
9+
10+
@router.post("/", response_model=NoteDB, status_code=201)
11+
async def create_note(payload: NoteSchema):
12+
note_id = await crud.post(payload)
13+
14+
response_object = {
15+
"id": note_id,
16+
"title": payload.title,
17+
"description": payload.description,
18+
}
19+
return response_object
20+
21+
22+
@router.get("/{id}/", response_model=NoteDB)
23+
async def read_note(id: int = Path(..., gt=0),):
24+
note = await crud.get(id)
25+
if not note:
26+
raise HTTPException(status_code=404, detail="Note not found")
27+
return note
28+
29+
30+
@router.get("/", response_model=List[NoteDB])
31+
async def read_all_notes():
32+
return await crud.get_all()
33+
34+
35+
@router.put("/{id}/", response_model=NoteDB)
36+
async def update_note(payload: NoteSchema, id: int = Path(..., gt=0),):
37+
note = await crud.get(id)
38+
if not note:
39+
raise HTTPException(status_code=404, detail="Note not found")
40+
41+
note_id = await crud.put(id, payload)
42+
43+
response_object = {
44+
"id": note_id,
45+
"title": payload.title,
46+
"description": payload.description,
47+
}
48+
return response_object
49+
50+
51+
@router.delete("/{id}/", response_model=NoteDB)
52+
async def delete_note(id: int = Path(..., gt=0)):
53+
note = await crud.get(id)
54+
if not note:
55+
raise HTTPException(status_code=404, detail="Note not found")
56+
57+
await crud.delete(id)
58+
59+
return note

0 commit comments

Comments
 (0)