這篇文章是閱讀Asabeneh的30 Days Of Python: Day 29 - Building an API後的學習筆記與心得。
因為原文中 Day 28 - API 主要在說明 API 是什麼,基於前端應該都很熟悉這個,就直接來看原文的 Day 29 實際寫一個 API 了。另一方面,就算不熟悉 API 定義,實際看過它運作應該也比較好了解這個概念。
這章會使用 Day 27 及 Day 28 使用的 Flask 框架及 MongoDB 來實作 RESTful API 對應 HTTP 請求方法中的 GET、PUT,POST 和 DELETE 來操作資料。
什麼是 RESTful API,可以參考 AWS 的這篇文章。
輔助工具: Postman
這篇文章最後做出來的 API server 檔案:https://github.com/AlliesChen/flask-api
route
flask
模組中的 Response
類別來回傳資料給使用者,回傳資料的格式需要用到 Python 內建的 json
模組來轉成 JSON 格式 (直接回傳 student_list
是會報錯的)。from flask import Flask, Response
import json
import os
app = Flask(__name__)
@app.route("/api/v1.0/students", methods = ["GET"])
def students():
student_list = [
{
'name':'Asabeneh',
'country':'Finland',
'city':'Helsinki',
'skills':['HTML', 'CSS','JavaScript','Python']
},
{
'name':'David',
'country':'UK',
'city':'London',
'skills':['Python','MongoDB']
},
{
'name':'John',
'country':'Sweden',
'city':'Stockholm',
'skills':['Java','C#']
}
]
return Response(json.dumps(student_list), mimetype="application/json")
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(debug=True, host="0.0.0.0", port=port)
可以透過 Postman (本地端) 版本,向 http://localhost:5000/api/v1.0/students 使用 GET,會拿到:
[
{
"name": "Asabeneh",
"country": "Finland",
"city": "Helsinki",
"skills": [
"HTML",
"CSS",
"JavaScript",
"Python"
]
},
{
"name": "David",
"country": "UK",
"city": "London",
"skills": [
"Python",
"MongoDB"
]
},
{
"name": "John",
"country": "Sweden",
"city": "Stockholm",
"skills": [
"Java",
"C#"
]
}
]
上面的例子是回傳寫死的 data 給使用者,在 Day 28 有實作過跟 MongoDB 互動,讓回傳的資料變成動態的:
bson.json_util
的 dumps
方法處理 BSON -- 參考這篇文章
initiate_data
函式判斷 students
不存在的話新增資料到 MongoDB,存在的話則不做任何事。try...except
—在 Day 17 有學過。from flask import Flask, Response
from bson.json_util import dumps
import os
from pymongo import MongoClient
app = Flask(__name__)
MONGODB_URI = "mongodb+srv://username:your_password@30daysofpython-twxkr.mongodb.net/?retryWrites=true&w=majority"
client = MongoClient(MONGODB_URI)
db = client["thirty_days_of_python"]
def initiate_data():
if "students" in db.list_collection_names():
print("db is ready")
return None
students = [
{
'name': 'Asabeneh',
'country': 'Finland',
'city': 'Helsinki',
'skills': ['HTML', 'CSS', 'JavaScript', 'Python']
},
{
'name': 'David',
'country': 'UK',
'city': 'London',
'skills': ['Python', 'MongoDB']
},
{
'name': 'John',
'country': 'Sweden',
'city': 'Stockholm',
'skills': ['Java', 'C#']
}
]
try:
db.students.insert_many(students)
print("Adding init data to db")
except Exception as err:
print(f"Something wrong while initiating data - Error: {err}")
return None
@app.route("/api/v1.0/students", methods=["GET"])
def students():
try:
students = db.students.find()
json_data = dumps(students)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while GETting students - Error: {err}"
if __name__ == "__main__":
initiate_data()
port = int(os.environ.get("PORT", 5000))
app.run(debug=True, host="0.0.0.0", port=port)
現在訪問 http://localhost:5000/api/v1.0/students 一樣能拿到前面的資料,但資料現在是存在 MongoDB 上。
如果只想要單筆資料,可以透過判斷 URL 中是否有加入資料 id 的方式,像是 http://localhost:5000/api/v1.0/students/<id>
:
<id>
,這樣就能取得使用者想拜訪的 id。bson.objectid
中的 ObjectId
這個類別,讓我們能在 MongoDB 查找這個物件。from bson.objectid import ObjectId
# 中間沒變的程式碼省略
@app.route("/api/v1.0/students/<id>", methods=["GET"])
def single_student(id):
try:
student = db.students.find({"_id": ObjectId(id)})
json_data = dumps(student)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while GETting student - Error: {err}"
這邊必須搭配 Postman 使用 POST 方法,因為瀏覽器不透過 JavaScript 的話,只能做 GET:
Postman 要選 POST,然後 Body,選擇 raw,並且必須是 JSON:
{"name": "Nanako", "country": "Japan", "city": "Tokyo", "skills": ["HTML", "CSS", "Vue"]}
然後在 app.py
:
flask
中再引入 request
這個物件,要透過 request.json
來拿到要新增的資料。 -- 參考這則回答
students
這個函式的內容,當 request.method == "POST"
的時候,要對資料庫新增內容。insert_one
的回傳值中有被新增物件的 id,透過這個 inserted_id
再配合 find_one({"_id": <id>})
去回傳新增的物件。from flask import request
@app.route("/api/v1.0/students", methods=["GET", "POST"])
def students():
if request.method == "POST":
student = request.json
print(student)
try:
result = db.students.insert_one(student)
new_student = db.students.find_one({"_id": result.inserted_id})
json_data = dumps(new_student)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while POSTing students - Error: {err}"
else:
try:
students = db.students.find()
json_data = dumps(students)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while GETting students - Error: {err}"
這邊一樣借助 Postman,這次方法選擇 PUT,然後要把網址的末端加上要改的 id,比如: http://localhost:5000/api/v1.0/students/6347d13fd24d777606b2a399
然後要改的內容,一樣必須是選擇 JSON,這邊改的是 city
和 skills
的內容:
{"name": "Nanako", "country": "Japan", "city": "Osaka", "skills": ["HTML", "CSS", "Vue", "Sass"]}
app.py
新增一個段落,可以從 single_student
—透過 id 取得資料,複製做修改:
methods
改為 PUT。update_one
使用方法。@app.route("/api/v1.0/students/<id>", methods=["PUT"])
def update_student(id):
query = {"_id": ObjectId(id)}
new_value = {"$set": request.json}
try:
db.students.update_one(query, new_value)
updated_value = db.students.find_one(query)
json_data = dumps(updated_value)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while updating student's info - Error: {err}"
這個段落跟 PUT 的寫法相像:
methods
改成 DELETEupdate_one
方法換成 delete_one(filter) ,id
做為 filter
參數。find()
。@app.route("/api/v1.0/students/<id>", methods=["DELETE"])
def delete_student(id):
query = {"_id": ObjectId(id)}
try:
db.students.delete_one(query)
updated_data = db.students.find()
json_data = dumps(updated_data)
return Response(json_data, mimetype="application/json")
except Exception as err:
return f"Something wrong while DELETing student's info - Error: {err}"