Flask-ORM-RESTful API 實作ToDo List(上)

jhong
8 min readApr 25, 2021

--

什麼是 API?

API ( Application Programming Interface),中文為應用程式介面。它是一段程式碼可以跟其他應用程式的程式碼之間當作溝通的橋樑。那 API 能夠幹嘛呢? 為了能夠讓其他的開發者可以額外開發應用程式來強化他們的產品,所推出可以與他們系統溝通的介面。

什麼是 ORM?

ORM ( Object Relational Mapping ),中文為物件關聯對映。它的作用是在關係型數據庫和對象之間作一個映射。使程式能夠通過操縱映射對象的方式來操縱數據庫。ORM 會在背後自動將 Python 代碼轉換成應對的SQL語法,再來進行對資料庫的操作。那為什麼要使用 ORM 呢?他有這幾個優勢:

● 少寫(幾乎不用寫)SQL,提升開發效率
● 支持多種類型的數據庫,方便切換

Flask SQLAlchemy 的使用

SQLAlchemy 是 Python 社群最廣泛使用的 ORM 套件,我們這次實作使用 MySQL 來做一個與 API 簡單的 ListToDo,需要先安裝下列兩個套件。

pip install flask-sqlalchemy
pip install pymysql

建立一個 app.py ,設定資料庫連線 “SQLALCHEMY_DATABASE_URI”
其中 username:password@ip:port/database_name 填入自己 MySQL 的設定。其他的配置可以參考 flask-sqlalchemy 的文件。

至於為什麼 from listtodo import app_api 這段沒有跟其他導入放在一起呢?
因為在後面我們其他 .py 的檔案也需要導入我們創建好的 db ,但我把它跟其他導入放在一起, db 還沒創建好就又被其他程式碼區塊讀取,然後又把資料導回來,這樣子會造成無限的重複循環,所以必須放在下面這個位置,才不會出錯。

#app.pyfrom flask_sqlalchemy import SQLAlchemy
from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://username:password@ip:port/database_name"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy()
db.init_app(app)
from listtodo import app_apiapp.register_blueprint(app_api, url_prefix='/api')if __name__ == "__main__":
app.run(debug=True)

這裡是事先建立好的 MySQL 資料表。以及測試用所新建的內容,我們是手動去創建,但如果想要直接使用 ORM 建立可以嗎?當然可以,詳細資料參考 flask-sqlalchemy 的文件。

CREATE TABLE list (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY UNIQUE KEY,
content longtext NOT NULL,
created_at timestamp NOT NULL,
updata_at timestamp,
deleted_at timestamp);

有了資料庫我們就可以建立一個 model.py 用來管理資料庫,先從 app.py 引入 db ,再建立一個 List 類別,List 類別繼承了 db.Model,未來使用這個類別與資料庫進行溝通呼叫,db.Column 對應資料表的欄位,在 List 類別中,創建相對應的值與設定。更多的類型可以參考 flask-sqlalchemy 的文件。

在 __init__ 初始化中只設定 content 、created_at 兩個欄位是因為在 MySQL 中設定了 NOT NULL 必須要值,而 id 不設定是因為會自動產生。

最後我們建立一個 to_json 函式,讓他返回我們想要呈現的形式以供閱讀。

#model.pyfrom app import dbclass List(db.Model):
__table__name = 'list'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
created_at = db.Column(
db.TIMESTAMP,default=datetime.datetime.now)
updata_at = db.Column(
db.TIMESTAMP,default=datetime.datetime.now,
onupdate=datetime.datetime.now)
deleted_at = db.Column(db.TIMESTAMP)
def __init__(self, content, created_at):
self.content = content
self.created_at = created_at
def to_json(self):
return {
"id": self.id,
"content": self.content,
"created_at": self.created_at,
"updata_at": self.updata_at}

建立一個 listtodo.py 用來執行實作簡單的增刪查改這裡我使用了 Flask 的藍圖 ( blueprints ),讓我可以更有結構的管理並把各功能分開,在上面 app.py 中用 app.register_blueprint 設定新的參數及新的 app_api 路由名稱。

先導入 model.py 的創建好的類,我們也使用 Flask 提供的 jsonify 函式,處理並返回序列化 json 的形式,另外已經在 model.py 中也建立 to_json 的函式,讓輸出的形式變成我們想要的方式以供閱讀。

#listtodo.pyfrom model import List
from flask.json import jsonify
from flask import Blueprint, request
from app import db
import datetime
app_api = Blueprint('api', __name__)

查詢紀錄

那麼我們如何從數據庫中查詢數據呢? Flask-SQLAlchemyquery 在你的Model 上提供了一個屬性 。當您訪問它時,您將在所有記錄上返回一個新的查詢對象。然後可以使用諸如 filter() 過濾記錄的方法,然後使用 all()或觸發選擇 first()。如果您想使用主鍵,也可以使用get()。

這裡我們先查詢所有的紀錄,所以使用 all() 。進入到http://127.0.0.1:5000/api/todo ,可以查詢到目前的所有紀錄

@app_api.route('/todo')
def get_all():
lists = List.query.all()
temp = []
for x in lists:
temp.append(x.to_json())
return jsonify(temp)

這裡我們先查詢單個紀錄,使用 get() 。進入到http://127.0.0.1:5000/api/todo_one/id ,在 id 位置打上你要查詢的數字,可以查詢到相對應的紀錄

也有另一種方式是使用 filter() 過濾記錄的方法,並且還能把尋找 id 換成尋找 content 喔!

@app_api.route('/todo/<id>')
def get_one(id):
#lists = List.query.filter(List.id == id).first() #使用 filter()
lists = List.query.get(id) #使用 get()
if lists == None:
return 'no id'
return jsonify(lists.to_json())

更多方法可以參考 Flask-SQLAlchemyquery 的文件,這裡整理出比較常用的方式。

過濾器
● filter() 條件查詢,要用類名以及兩個等號去判斷,返回一個新查詢。例如:List.id == id
● filter_by() 條件查詢,不支持比較運算符。只需要用(不帶類名的)以及單個等號就可以判斷,返回一個新查詢。例如:id = id
● limit() 使用指定的值限定原查詢返回的結果
● order_by() 對原查詢結果進行排序,返回一個新查詢
● group_by() 對原查詢結果進行分組,返回一個新查詢

結果方法
● all() 以返回查詢的所有結果
● first() 返回查詢的第一個結果
● get() 返回指定主鍵對應的行
● count() 返回查詢結果的數量
● paginate() 返回一個分頁器包含指定範圍內的結果

本篇簡單介紹 API 、 ORM 、 Flask SQLAlchemy 、用一個簡單的實作先完成了查詢的功能,在下一篇我們會繼續完善新增、刪除、更新的功能,也希望能透過本篇能讓你有初步的了解。

--

--