# 後端學習紀錄 Backend with FastAPI - *Website with Departure* ###### tags: `backend` Copyright 2021, [月下麒麟](https://hackmd.io/@YMont/note-catalog) --- ## Target >該筆記主要目標為使用Web之Python框架**FastAPI**, >並結合資料庫框架**Flask-SQLAlchemy**與資料庫**SQLite**, >另外,會著重在==前後端分離==的應用,並以後端技術為主 ## Requirements ```txt anyio==3.3.4 asgiref==3.4.1 click==8.0.3 colorama==0.4.4 fastapi==0.70.0 greenlet==1.1.2 h11==0.12.0 idna==3.3 Jinja2==3.0.2 MarkupSafe==2.0.1 pydantic==1.8.2 sniffio==1.2.0 SQLAlchemy==1.4.26 starlette==0.16.0 typing-extensions==3.10.0.2 uvicorn==0.15.0 ``` ## Architecture ``` |---app | |---__init__.py | |---fastDB.db | |---database.py | |---model.py | |---schemas.py | |---crud.py | |---main.py ``` ## Run Virtual Enviroment 關於創建與啟用虛擬,這邊就不再重複說明,可以參考我的另一篇[後端學習紀錄 Backend with FastAPI - Setting Enviroment](https://hackmd.io/@YMont/python-fastapi-1) ## Run ASGI Server ``` (app) D:\app> uvicorn main:app --reload ... ... [32mINFO [0m: Waiting for application startup. [32mINFO [0m: Application startup complete. ... ... ``` ![](https://i.imgur.com/bBamFVd.png) --- ## Source Code **Python** database主要在建立資料庫與物件對應的聯結,都繞乎於[ORM](https://docs.sqlalchemy.org/en/14/orm/) Reference:[[Flask教學] Flask-SQLAlchemy 資料庫操作-ORM篇](https://www.maxlist.xyz/2019/10/30/flask-sqlalchemy/) Reference:[SQLAlchemy 1.4 Documentation](https://docs.sqlalchemy.org/en/14/orm/) ```python= # database.py from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker SQLALCHEMY_DATABASE_URL = r"sqlite:///./fastDB.db" # create SQL engine engine = create_engine(SQLALCHEMY_DATABASE_URL, encoding='utf-8', echo=True) # create SQL communication session and bind SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # create SQL mapping table Base = declarative_base() ``` --- 剛才我們建立了與資料庫的連結,在model裡面則是建立資料庫表格的**欄位** ```python= # model.py # create model attribute/column from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Float # relationship with ORM #from sqlalchemy.orm import relationship from sql_app.database import Base from sqlalchemy.orm import relationship class PeopleInfo(Base): __tablename__ = "humanInfo" id = Column(Integer, primary_key=True) name = Column(String(length=30)) height = Column(Float) weight = Column(Float) habbit = Column(String(length=252)) ``` 對應真正的資料庫就會長這個樣子 ![](https://i.imgur.com/jhqcPqx.png) --- 在schemas則是會於產生物件時對其輸入資料屬性做檢查 ```python= # schemas.py from typing import List from pydantic import BaseModel class PeopleBase(BaseModel): name: str height: float weight: float habbit: str class PeopleUpdate(PeopleBase): id: int class Config: orm_mode = True class PeopleType(BaseModel): skip: int limit: int data: List[PeopleUpdate] ``` --- 這邊就是對資料庫的操作行為 **get(Create), post(Read), put(Update, delete(Delete)** ```python= # crud.py from typing import List from fastapi.exceptions import HTTPException from sql_app import model from sqlalchemy.orm import Session, session from sql_app.model import PeopleInfo from sql_app.schemas import PeopleBase, PeopleUpdate, PeopleType def get_user_by_id(boxSession: Session, _id: int): return boxSession.query(PeopleInfo).filter(PeopleInfo.id == _id).first() def get_users(boxSession: Session, _skip: int=0, _limit: int=100) -> List[PeopleInfo]: return boxSession.query(PeopleInfo).offset(_skip).limit(_limit).all() def create_user(boxSession: Session, _createData: PeopleUpdate) -> PeopleInfo : peopleDetail = boxSession.query(PeopleInfo).filter(PeopleInfo.name == _createData.name, PeopleInfo.height == _createData.height, PeopleInfo.weight == _createData.weight, PeopleInfo.habbit == _createData.habbit).first() if peopleDetail is not None: raise HTTPException(status_code=409, detail="People already exist.") newPeopleInfo = PeopleInfo(**_createData.dict()) boxSession.add(newPeopleInfo) boxSession.commit() boxSession.refresh(newPeopleInfo) return newPeopleInfo def update_user(boxSession: Session, _id: int , infoUpdate: PeopleUpdate) -> PeopleInfo: orignalInfo = get_user_by_id(boxSession, _id) if orignalInfo is None: raise HTTPException(status_code=404 , detail="404 Not Found.") orignalInfo.name = infoUpdate.name orignalInfo.height = infoUpdate.height orignalInfo.weight = infoUpdate.weight orignalInfo.habbit = infoUpdate.habbit boxSession.commit() boxSession.refresh(orignalInfo) return orignalInfo def delete_user(boxSession: Session, _id: int): res = get_user_by_id(boxSession, _id) if res is None: raise HTTPException(status_code=404, detail="404 Not Found.") boxSession.delete(res) boxSession.commit() return { "code": 0 } ``` --- 在ASGI Server啟動後會執行該main函式,所以這邊也定義了對網頁行為的操作 **Get, Post, Put, Delete** ```python= # main.py from fastapi import FastAPI, Depends, HTTPException from fastapi.middleware.cors import CORSMiddleware from sql_app import crud, model from sql_app.model import PeopleInfo from sql_app.schemas import PeopleBase, PeopleType, PeopleUpdate from sql_app.database import SessionLocal, engine from sqlalchemy.orm.session import Session model.Base.metadata.create_all(bind=engine) app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() # ----------------------------------- origins = ["http://localhost:8000", "http://localhost:3000"] app.add_middleware(CORSMiddleware, allow_origins = origins, allow_credentials = True, allow_methods=["*"], allow_headers = ["*"]) # ----------------------------------- @app.get("/users/{user_id}",response_model=PeopleUpdate) def read_user_id(user_id: int, db:Session= Depends(get_db)): res = crud.get_user_by_id(db,user_id) if res is None: raise HTTPException(status_code=404, detail="404 Not Found.") return res @app.get("/users",response_model=PeopleType) def read_users(skip: int=0, limit: int=100, db:Session= Depends(get_db)): res = crud.get_users(db, skip, limit) response = {"skip":skip , "limit":limit , "data":res} return response @app.post("/users",response_model=PeopleUpdate) def create_user(userForm: PeopleUpdate, db:Session= Depends(get_db)): try: res = crud.create_user(db, userForm) return res except Exception as err: return HTTPException(**err.__dict__) @app.put("/users/{user_id}", response_model=PeopleUpdate) def update_user(userForm: PeopleUpdate, user_id: int, db:Session= Depends(get_db)): try: res = crud.update_user(db, user_id, userForm) return res except Exception as err: raise HTTPException(**err.__dict__) @app.delete("/users/{user_id}") def delete_user(user_id: int, db:Session= Depends(get_db)): try: crud.delete_user(db,user_id) except Exception as err: raise HTTPException(status_code=404, detail="404 Not Found.") return {"code": 0} ``` ## Testing **Open FastAPI Document** >run server後 ![](https://i.imgur.com/TBrmeQQ.png) >開啟網頁 ![](https://i.imgur.com/6A7bft3.png) >做CRUD的測試,這個API測試文檔是fastAPI自帶的 只要在port後面加入/docs路由就可開啟使用 ![](https://i.imgur.com/32zAcWF.png) >舉例: 對這支API測試讀取資料庫裏面的第6筆資料 ![](https://i.imgur.com/ik9LfFZ.png) >如果有取得成功,Server就會回覆內容與status code ![](https://i.imgur.com/mrhGOuX.png) >對應資料庫data是一樣的 ![](https://i.imgur.com/s2PSmjV.png) 其他的API testing也是相似的方法,就不再舉例。 --- ## Summary 這算是自我練習比較接近前後端分離,後端的架構應用 所以,當你再去閱讀我的前端筆記,就可以將前後端接起來了(工商一下) * [Day1學前端 Frontend with React - Start React](https://hackmd.io/@YMont/frontend-react-day1) * [Day2學前端 Frontend with React - Hello World React](https://hackmd.io/@YMont/frontend-react-day2) * [Day3學前端 Frontend with React - 串接API with Ajax](https://hackmd.io/@YMont/frontend-react-day3) * [Day4學前端 Frontend with React - show on UI](https://hackmd.io/@YMont/frontend-react-day4) 最後,關乎於將database, model, crud, schemas, main的Python檔分門別類 是參考以下的兩個連結去改寫的,當中有一些類別的應用與ORM的對應 之後,有機會再來寫一篇關乎於物件的探討。 reference: [SQL (Relational) Databases](https://fastapi.tiangolo.com/tutorial/sql-databases/) reference: [Building a CRUD APP with FastAPI and MySQL](https://blog.balasundar.com/building-a-crud-app-with-fastapi-and-mysql) 謝謝您的收看,歡迎留言一起探討~