Skip to content

Commit 0f025f6

Browse files
committed
Connected MySQL using Peeweee
1 parent 221b6fc commit 0f025f6

9 files changed

+237
-10
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ WORKDIR /code
1010

1111
# Install dependencies
1212
COPY ./requirements.txt /code/requirements.txt
13-
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
13+
RUN pip install --upgrade -r /code/requirements.txt
1414

1515
# Copy project
1616
COPY . /code/

docker-compose.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ services:
88
- .:/code
99
ports:
1010
- "8000:8000"
11+
1112
depends_on:
1213
- db
1314

@@ -16,20 +17,26 @@ services:
1617
restart: always
1718
tty: true
1819
environment:
19-
MYSQL_DATABASE: mysql_db
20-
MYSQL_USER: mysql
21-
MYSQL_PASSWORD: mysql
22-
MYSQL_ROOT_PASSWORD: mysql
20+
- MYSQL_DATABASE=mysql_db
21+
- MYSQL_USER=mysql
22+
- MYSQL_PASSWORD=mysql
23+
- MYSQL_ROOT_PASSWORD=mysql
24+
- MYSQL_HOST=db
25+
- MYSQL_PORT=3307
2326
ports:
2427
- "3307:3306"
28+
2529
volumes:
2630
- mysql_data:/var/lib/mysql/
2731

28-
adminer:
29-
image: adminer:latest
32+
phpmyadmin:
33+
image: phpmyadmin:latest
3034
restart: always
3135
ports:
32-
- "8080:8080"
36+
- "8080:80"
37+
environment:
38+
- PMA_ARBITRARY=1
3339

3440
volumes:
3541
mysql_data:
42+

main.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
1+
import os, time
2+
from typing import List
3+
14
import uvicorn
2-
from fastapi import FastAPI
5+
from fastapi import Depends, FastAPI, HTTPException
6+
7+
from sql_app import crud, schemas, database, models
8+
from sql_app.database import db_state_default
9+
10+
database.db.connect()
11+
database.db.create_tables([models.User, models.Item])
12+
database.db.close()
13+
314

415
app = FastAPI()
16+
sleep_time = 10
17+
518

19+
async def reset_db_state():
20+
database.db._state._state.set(db_state_default.copy())
21+
database.db._state.reset()
622

23+
24+
def get_db(db_state=Depends(reset_db_state)):
25+
try:
26+
database.db.connect()
27+
yield
28+
finally:
29+
if not database.db.is_closed():
30+
database.db.close()
31+
32+
33+
# Endpoints
734
@app.get("/")
835
async def root():
936
return {"message": "Hello World"}
@@ -14,5 +41,56 @@ async def say_hello(name: str):
1441
return {"message": f"Hello {name}"}
1542

1643

44+
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
45+
def create_user(user: schemas.UserCreate):
46+
db_user = crud.get_user_by_email(email=user.email)
47+
if db_user:
48+
raise HTTPException(status_code=400, detail="Email already registered")
49+
return crud.create_user(user=user)
50+
51+
52+
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
53+
def read_users(skip: int = 0, limit: int = 100):
54+
users = crud.get_users(skip=skip, limit=limit)
55+
return users
56+
57+
58+
@app.get(
59+
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
60+
)
61+
def read_user(user_id: int):
62+
db_user = crud.get_user(user_id=user_id)
63+
if db_user is None:
64+
raise HTTPException(status_code=404, detail="User not found")
65+
return db_user
66+
67+
68+
@app.post(
69+
"/users/{user_id}/items/",
70+
response_model=schemas.Item,
71+
dependencies=[Depends(get_db)],
72+
)
73+
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
74+
return crud.create_user_item(item=item, user_id=user_id)
75+
76+
77+
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
78+
def read_items(skip: int = 0, limit: int = 100):
79+
items = crud.get_items(skip=skip, limit=limit)
80+
return items
81+
82+
83+
@app.get(
84+
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
85+
)
86+
def read_slow_users(skip: int = 0, limit: int = 100):
87+
global sleep_time
88+
sleep_time = max(0, sleep_time - 1)
89+
time.sleep(sleep_time) # Fake long processing request
90+
users = crud.get_users(skip=skip, limit=limit)
91+
return users
92+
93+
94+
# Run Fast API app
1795
if __name__ == "__main__":
1896
uvicorn.run(app, host="0.0.0.0", port=8000)

requirements.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
fastapi~=0.73.0
2-
uvicorn~=0.17.1
2+
uvicorn~=0.17.3
3+
peewee~=3.14.8
4+
pydantic~=1.9.0
5+
contextvars~=2.4.0
6+
pymysql~=1.0.2
7+
cryptography~=36.0.1
8+
environs~=9.5.0

sql_app/__init__.py

Whitespace-only changes.

sql_app/crud.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from . import models, schemas
2+
3+
4+
def get_user(user_id: int):
5+
return models.User.filter(models.User.id == user_id).first()
6+
7+
8+
def get_user_by_email(email: str):
9+
return models.User.filter(models.User.email == email).first()
10+
11+
12+
def get_users(skip: int = 0, limit: int = 100):
13+
return list(models.User.select().offset(skip).limit(limit))
14+
15+
16+
def create_user(user: schemas.UserCreate):
17+
fake_hashed_password = user.password + "notreallyhashed"
18+
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
19+
db_user.save()
20+
return db_user
21+
22+
23+
def get_items(skip: int = 0, limit: int = 100):
24+
return list(models.Item.select().offset(skip).limit(limit))
25+
26+
27+
def create_user_item(item: schemas.ItemCreate, user_id: int):
28+
db_item = models.Item(**item.dict(), owner_id=user_id)
29+
db_item.save()
30+
return db_item

sql_app/database.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from environs import Env
2+
from contextvars import ContextVar
3+
4+
import peewee
5+
6+
# Environment
7+
env = Env()
8+
env.read_env()
9+
10+
DATABASE_NAME = env("MYSQL_DATABASE", default="mysql_db")
11+
DATABASE_USER = env("MYSQL_USER", default="mysql")
12+
DATABASE_PASS = env("MYSQL_PASSWORD", default="mysql")
13+
DATABASE_PORT = env("MYSQL_HOST", default="db")
14+
DATABASE_PORT = env.int("MYSQL_PORT", default=3306)
15+
16+
17+
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
18+
db_state = ContextVar("db_state", default=db_state_default.copy())
19+
20+
21+
class PeeweeConnectionState(peewee._ConnectionState):
22+
def __init__(self, **kwargs):
23+
super().__setattr__("_state", db_state)
24+
super().__init__(**kwargs)
25+
26+
def __setattr__(self, name, value):
27+
self._state.get()[name] = value
28+
29+
def __getattr__(self, name):
30+
return self._state.get()[name]
31+
32+
33+
db = peewee.MySQLDatabase(DATABASE_NAME, user=DATABASE_USER, password=DATABASE_PASS, host="db", port=DATABASE_PORT)
34+
35+
db._state = PeeweeConnectionState()
36+
37+
# Peewee was not designed for async frameworks, or with them in mind.

sql_app/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import peewee
2+
from .database import db
3+
4+
5+
class User(peewee.Model):
6+
email = peewee.CharField(unique=True, index=True)
7+
hashed_password = peewee.CharField()
8+
is_active = peewee.BooleanField(default=True)
9+
10+
class Meta:
11+
database = db
12+
13+
14+
class Item(peewee.Model):
15+
title = peewee.CharField(index=True)
16+
description = peewee.CharField(index=True)
17+
owner = peewee.ForeignKeyField(User, backref="items")
18+
19+
class Meta:
20+
database = db

sql_app/schemas.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from typing import Any, List, Optional
2+
3+
import peewee
4+
from pydantic import BaseModel
5+
from pydantic.utils import GetterDict
6+
7+
8+
class PeeweeGetterDict(GetterDict):
9+
def get(self, key: Any, default: Any = None):
10+
res = getattr(self._obj, key, default)
11+
if isinstance(res, peewee.ModelSelect):
12+
return list(res)
13+
return res
14+
15+
16+
class ItemBase(BaseModel):
17+
title: str
18+
description: Optional[str] = None
19+
20+
21+
class ItemCreate(ItemBase):
22+
pass
23+
24+
25+
class Item(ItemBase):
26+
id: int
27+
owner_id: int
28+
29+
class Config:
30+
orm_mode = True
31+
getter_dict = PeeweeGetterDict
32+
33+
34+
class UserBase(BaseModel):
35+
email: str
36+
37+
38+
class UserCreate(UserBase):
39+
password: str
40+
41+
42+
class User(UserBase):
43+
id: int
44+
is_active: bool
45+
items: List[Item] = []
46+
47+
class Config:
48+
orm_mode = True
49+
getter_dict = PeeweeGetterDict

0 commit comments

Comments
 (0)