├── .gitignore ├── README.md ├── app.py ├── config ├── db.py └── openapi.py ├── docker-compose.yml ├── models └── user.py ├── requeriments.txt ├── routes └── user.py └── schemas └── user.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI & SQLAlchemy Mysql REST API -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from routes.user import user 3 | from config.openapi import tags_metadata 4 | 5 | app = FastAPI( 6 | title="Users API", 7 | description="a REST API using python and mysql", 8 | version="0.0.1", 9 | openapi_tags=tags_metadata, 10 | ) 11 | 12 | app.include_router(user) 13 | -------------------------------------------------------------------------------- /config/db.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine, MetaData 2 | 3 | engine = create_engine("mysql+pymysql://root:password@localhost:3306/storedb") 4 | 5 | meta = MetaData() 6 | 7 | conn = engine.connect() -------------------------------------------------------------------------------- /config/openapi.py: -------------------------------------------------------------------------------- 1 | tags_metadata = [ 2 | { 3 | "name": "users", 4 | "description": "users endpoint" 5 | } 6 | ] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | 4 | db: 5 | image: mysql 6 | command: --default-authentication-plugin=mysql_native_password 7 | restart: always 8 | environment: 9 | MYSQL_ROOT_PASSWORD: password 10 | MYSQL_DATABASE: storedb 11 | ports: 12 | - 3306:3306 13 | phpmyadmin: 14 | image: phpmyadmin 15 | restart: always 16 | ports: 17 | - 8080:80 -------------------------------------------------------------------------------- /models/user.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Table 2 | from sqlalchemy.sql.sqltypes import Integer, String 3 | from config.db import meta, engine 4 | 5 | users = Table( 6 | "users", 7 | meta, 8 | Column("id", Integer, primary_key=True), 9 | Column( 10 | "name", 11 | String(255), 12 | ), 13 | Column("email", String(255)), 14 | Column("password", String(255)), 15 | ) 16 | 17 | meta.create_all(engine) 18 | -------------------------------------------------------------------------------- /requeriments.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | asgiref==3.4.1 3 | black==21.6b0 4 | certifi==2021.5.30 5 | cffi==1.14.6 6 | click==8.0.1 7 | cryptography==3.4.7 8 | et-xmlfile==1.1.0 9 | fastapi==0.66.0 10 | greenlet==1.1.0 11 | h11==0.12.0 12 | mypy-extensions==0.4.3 13 | openpyxl==3.0.7 14 | pathspec==0.8.1 15 | pycparser==2.20 16 | pydantic==1.8.2 17 | PyMySQL==1.0.2 18 | regex==2021.7.6 19 | SQLAlchemy==1.4.20 20 | starlette==0.14.2 21 | toml==0.10.2 22 | typing-extensions==3.10.0.0 23 | uvicorn==0.14.0 24 | -------------------------------------------------------------------------------- /routes/user.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from config.db import conn 3 | from models.user import users 4 | from schemas.user import User, UserCount 5 | from typing import List 6 | from starlette.status import HTTP_204_NO_CONTENT 7 | from sqlalchemy import func, select 8 | 9 | from cryptography.fernet import Fernet 10 | 11 | user = APIRouter() 12 | key = Fernet.generate_key() 13 | f = Fernet(key) 14 | 15 | 16 | @user.get( 17 | "/users", 18 | tags=["users"], 19 | response_model=List[User], 20 | description="Get a list of all users", 21 | ) 22 | def get_users(): 23 | return conn.execute(users.select()).fetchall() 24 | 25 | 26 | @user.get("/users/count", tags=["users"], response_model=UserCount) 27 | def get_users_count(): 28 | result = conn.execute(select([func.count()]).select_from(users)) 29 | return {"total": tuple(result)[0][0]} 30 | 31 | 32 | @user.get( 33 | "/users/{id}", 34 | tags=["users"], 35 | response_model=User, 36 | description="Get a single user by Id", 37 | ) 38 | def get_user(id: str): 39 | return conn.execute(users.select().where(users.c.id == id)).first() 40 | 41 | 42 | @user.post("/", tags=["users"], response_model=User, description="Create a new user") 43 | def create_user(user: User): 44 | new_user = {"name": user.name, "email": user.email} 45 | new_user["password"] = f.encrypt(user.password.encode("utf-8")) 46 | result = conn.execute(users.insert().values(new_user)) 47 | return conn.execute(users.select().where(users.c.id == result.lastrowid)).first() 48 | 49 | 50 | @user.put( 51 | "users/{id}", tags=["users"], response_model=User, description="Update a User by Id" 52 | ) 53 | def update_user(user: User, id: int): 54 | conn.execute( 55 | users.update() 56 | .values(name=user.name, email=user.email, password=user.password) 57 | .where(users.c.id == id) 58 | ) 59 | return conn.execute(users.select().where(users.c.id == id)).first() 60 | 61 | 62 | @user.delete("/{id}", tags=["users"], status_code=HTTP_204_NO_CONTENT) 63 | def delete_user(id: int): 64 | conn.execute(users.delete().where(users.c.id == id)) 65 | return conn.execute(users.select().where(users.c.id == id)).first() 66 | -------------------------------------------------------------------------------- /schemas/user.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel 3 | 4 | class User(BaseModel): 5 | id: Optional[int] 6 | name: str 7 | email: str 8 | password: str 9 | 10 | class UserCount(BaseModel): 11 | total: int --------------------------------------------------------------------------------