เวลาพูดถึงการสร้าง web API เราก็อาจจะนึกถึงภาษาอื่น ๆ เช่น JavaScript, TypeScript, .NET หรืออื่น ๆ อีกมากมาย Python คงเป็นตัวเลือกท้าย ๆ เลยทีเดียว แต่ในบางงานมันก็มีความจำเป็นที่แบบว่าต้องเป็นภาษานี้ครับ อย่างเช่นในงานด้าน AI ที่มักจะทำกันด้วย Python การสร้าง API เพื่อให้สามารถใช้งาน AI ดังกล่าว Python ก็จะกลายเป็นตัวเลือกแรก ๆ แทนใช่ไหมล่ะครับ
ซึ่งในการสร้างระบบหลังบ้านขึ้นมาสักระบบ หากต้องการให้ผู้พัฒนาคนอื่น ๆ สามารถเชื่อมต่อและใช้งานกับระบบของเราได้ สิ่งที่ขาดไปไม่ได้เลยก็คือคู่มือการใช้งาน แต่เมื่อระบบมีการแก้ไข เพิ่มตรงนู้น ลดตรงนี้ ผู้พัฒนาระบบอย่างเรา ๆ ก็ต้องตามมาแก้คู่มือการใช้งานอีกครั้งไปเรื่อย ๆ หากแก้ไม่เยอะยังพอไหว แต่ถ้าต้องแก้เยอะจากที่ทำระบบเสร็จจะได้พัก แต่ดันต้องมานั่งแก้เอกสารต่ออีก
⚙️ คงจะดีกว่าเยอะถ้าสามารถ auto generate API doc ได้
เพราะฉะนั้นวันนี้เราจะมาสร้าง Python API ด้วย Flask โดยที่สามารถ auto generate document หรือคู่มือการใช้งานได้ ไม่ต้องมาคอยแก้คู่มือกันให้ยุ่งยากอีกต่อไปแล้วครับ
Table of Contents
· Preparation
· Start create API with Flask
· Config API server
· Adding route
· Adding API document
· Summary
Preparation
ทุกครั้งก่อนจะเริ่มโปรเจกต์กับ Python สิ่งที่ควรทำเสมอก็คือการสร้าง virtual environment หรือ virtualenv ซึ่งจะเป็นการแยก library ของแต่ละโปรเจกต์ออกจากกันเพื่อลด conflict ที่อาจจะเกิดขึ้น
virtualenv
is a tool to create isolated Python environments. Since Python3.3
, a subset of it has been integrated into the standard library under the venv module.
เพื่อให้เห็นภาพมากขึ้นจะขออธิบายด้วยรูปประกอบนะครับ
จากรูปจะเห็นว่าใน Project 1 และ Project 2 ใช้เวอร์ชันของ Python ไม่เหมือนกัน ซึ่งถ้าเราไม่ใช้ venv
ก็จะทำให้เกิดความยุ่งยากในการจัดการเวอร์ชันในการทำงานทั้ง 2 โปรเจกต์พร้อมกัน
ในทำนองเดียวกันที่ Project 2 และ Project 3 ใช้ library Numpy
เหมือนกันทั้งคู่แต่เป็นคนละเวอร์ชัน หากติดตั้ง library ไว้แบบ global ก็อาจจะทำให้การทำงานของ library ในทั้งสองโปรเจกต์ไม่สมบูรณ์หรือเกิดความขัดข้องไม่เป็นไปตาม code ที่เขียนเอาไว้ venv
จึงเข้ามาแก้ไขปัญหาในส่วนนี้ครับ
Create virtual environment
ในการสร้าง venv เราก็จะใช้คำสั่งตามนี้ แต่หากเป็น Python เวอร์ชันต่ำกว่า 3.3 ก็อาจจะต้องติดตั้ง virtualenv เองก่อนอีกทีนะครับ
# python -m venv <venv name
python -m venv .venv
ในตรงนี้ python
ก็อาจจะเป็น python3
ก็ได้ และหากใช้คำสั่ง venv
แล้วไม่พบ library อาจลองเปลี่ยนเป็น virtualenv
ซึ่งขึ้นอยู่กับว่าที่ติดตั้งเอาไว้ในเครื่องเป็นเวอร์ชันไหนครับ
<venv name>
เป็นชื่อของ folder venv ที่ต้องการ ในที่นี้คือ .venv
หลังจากที่สร้าง venv
เสร็จ จะมี folder หนึ่งที่ชื่อว่า .venv อยู่ใน root directory ของโปรเจกต์ ซึ่งนั่นจะเป็นที่เก็บ library ต่าง ๆ ที่เราติดตั้งเอาไว้ครับ แต่หากต้องการจะใช้งานได้ตามที่อธิบายไปก่อนหน้า จำเป็นต้องมีการ “activate” venv ก่อน โดยจะใช้คำสั่ง
Windows
.venv\Scripts\activate
Linux/MacOS มี 2 แบบให้เลือกใช้
source env/bin/activate
# or
. .venv/bin/activate
หาก “activate” ได้สำเร็จ ที่ prompt จะมีคำว่า (venv)
นำหน้า ซึ่งแสดงว่าต่อจากนี้ทุกคำสั่งที่ใช้ในโปรเจกต์นี้จะใช้ environment ในการทำงานแยกออกมาจาก global ❗️แต่ว่าหากปิด cli ไปแล้วเปิดมาใหม่ก็ต้องอย่าลืม activate ใหม่ด้วยนะครับ
Install library
ในการติดตั้ง library สำหรับ Python จะใช้สิ่งที่เรียกว่า pip ในการติดตั้ง ซึ่งสามารถใช้งานได้ผ่าน cli (Command-line interface)
pip is the package installer for Python.
โดยเราจะพิมพ์ library ที่ต้องการติดตั้งทีละอย่างก็ได้ ด้วยคำสั่ง
pip install <library name>[==<version>]
ในส่วนของ [==<version>]
เป็น optional สำหรับระบุเวอร์ชันที่ต้องการ เช่น ==1.0.0
ต่อท้าย <library name>
แต่ในการติด library ถ้าทำที่ละอันก็จะทำให้เสียเวลา และเมื่อเราต้องไปทำงานที่เครื่องอื่นก็ต้องไล่ติดตั้งใหม่ทั้งหมด ซึ่งอาจเกิดการลืมไปแล้วว่ามี library อะไรบ้างที่เคยติดตั้งเอาไว้
เพราะฉะนั้นผมจึงขอนำเสนอวิธีติดตั้ง library อีกวิธีที่ดีกว่า นั่นก็คือติดตั้งผ่าน “requirements” ไฟล์แทน 🚀 โดยสร้างไฟล์ที่มีชื่อว่า “requirements.txt” ขึ้นมาภายใต้ root directory ของโปรเจกต์ และกำหนด library พร้อมเวอร์ชันที่ต้องการติดตั้งเอาไว้ภายในนั้น ดังนี้
แล้วจากนั้นจึงใช้คำสั่งเพื่อติดตั้ง library ทั้งหมดที่ระบุเอาไว้ใน requirement ไฟล์ด้วยคำสั่ง
# pip install -r <requirements file>
pip install -r requirements.txt
โดยให้เปลี่ยน <requirements file>
ให้เป็นชื่อไฟล์ที่เราสร้างขึ้นก่อนหน้า หรือก็คือ “requirements.txt” pip ก็จะทำการติดตั้ง library ทั้งหมดที่เรากำหนดเอาไว้ให้โดยที่ไม่ต้องมาติดตั้งเองทีละอันแล้วครับ ✨
ถึงจุด ๆ นี้ทุกอย่างก็พร้อมสำหรับก็เริ่มเขียน code แล้ว เพราะฉะนั้นเราจะไปกันต่อที่
Start create API with Flask
ในการพัฒนา Web server หรือ API ด้วย Flask สามารถทำได้ง่ายมาก ๆๆๆ ก่อนอื่นก็สร้างไฟล์ใหม่สำหรับเขียน code ชื่อว่า “index.py” หรือชื่ออื่น ๆ ตามต้องการ แต่ต้องนามสกุล .py
ด้วยนะ จากนั้นเขียน code ไม่กี่บรรทัดดังนี้
เพียงเท่านี้เราก็ได้ Web server มาแล้วหนึ่งอัน ซึ่งในการจะเริ่มต้นการทำงานของ server ก็มีอยู่ 2 วิธีด้วยกัน
Option 1: Start server with command
ในการสั่งให้ server ทำงานด้วย cli เราจะใช้คำสั่ง
# flask [--app <flask app file name>] run
flask --app index run
โดยคำสั่งหลัก ๆ คือ
flask run
ที่จะเริ่มการทำงานของ server โดย Flask จะหา Flask app จากในไฟล์ที่ชื่อว่า “app”, “application”, “create_app” หรือ “make_app”--app <flask app flie name>
ที่ใช้สำหรับระบุชื่อไฟล์ที่มี Flask app อยู่ ในที่นี้คือ “index.py” จึงเป็น--app index
เพราะฉะนั้นหากตั้งชื่อไฟล์ว่า “app.py” ก็ไม่จำเป็นต้องระบุ option --app
เพิ่มครับ
สามารถอ่านรายละเอียดเพิ่มเติมได้ด้วยคำสั่ง flask --help
Option 2: Start server with code
สามารถทำได้โดยการเพิ่ม code เข้าไปหนึ่งบรรทัดที่ท้ายสุดของไฟล์ คือ
app.run()
หน้าตาของ code ทั้งหมดที่ได้ก็จะออกมาประมาณนี้
แล้วจากนั้นก็จะสามารถใช้คำสั่งในการ run ไฟล์ Python ปกติได้เลย
python index.py
Default value
ทั้ง 2 วิธีในการเริ่มการทำงานของ server จะมีค่าเริ่มต้น default value ของ IP คือ 127.0.0.1
และ port คือ 5000
สามารถทดสอบได้ด้วยการเข้าไปที่ http://127.0.0.1:5000
ซึ่งจะได้ผลลัพธ์คือ message “Hello, World!” ที่เราเขียนเอาไว้ใน code นั่นเอง
{
"message": "Hello, World!"
}
ส่วนการยกเลิกหรือ stop server สามารถทำได้ด้วยการกด CTRL + C
ซึ่งในบทความนี้จะใช้ option 2 เป็นหลัก จริง ๆ Flask not recommended ให้ใช้วิธีนี้ครับ แต่ในตอนนี้เพื่อให้ง่ายต่อการพัฒนาตามบทความนี้ จึงขอเลือกใช้เป็นวิธีนี้ครับ ทั้งนี้สามารถเข้าไปอ่านรายละเอียดเพิ่มเติมสำหรับ Option 1 แบบเต็ม ๆ ได้ที่
Config API server
ในการตั้งค่าการเริ่มต้นการทำงานของ server เบื้องต้นมีอยู่ 3 อย่างที่สามารถกำหนดได้ผ่าน parameter ของ app.run()
คือ
host
มีค่าเป็นstring | None
หากใส่None
จะได้ default คือ127.0.0.1
ซึ่งใช้ในการกำหนด IP ที่จะทำงานport
มีค่าเป็นint | None
หากใส่None
จะได้ default คือ5000
เช่นเดียวกับ host คือใช้ในการกำหนด port ที่ต้องการให้ server ทำงานdebug
มีค่าเป็นbool | None
หากใส่None
จะได้ default คือFalse
สามารถกำหนดให้ server ทำงานใน debug mode ได้ ซึ่งเมื่อมีการแก้ไข code เมื่อใด server ก็จะทำการ restart อัตโนมัติ
สำหรับ host
โดย default จะมีค่าเป็น 127.0.0.1
คือ loopback ซึ่งจะทำให้สามารถเข้าถึงได้ผ่าน localhost หรือเครื่องตัวเองเท่านั้น หากต้องการให้สามารถเข้าผ่านจากเครื่องอื่น ๆ หรือ IP อื่น ๆ ได้ จะต้องใช้ 0.0.0.0
(นี่เป็นการอธิบายแบบย่อเพื่อให้เข้าใจได้ง่ายเท่านั้น สามารถอ่านข้อมูลเพิ่มเติมได้ที่นี่) โดยสามารถกำหนดได้ด้วยการส่ง parameter host="0.0.0.0"
ให้ Flask app ตอนที่ start server
ส่วน port
สามารถกำหนดได้ตามต้องการ แต่ไม่ควรต่ำกว่า 1024 เนื่องจากว่าบาง port ที่ต่ำกว่า 1024 จะถูกใช้สำหรับการทำงานอื่น ๆ ที่จำเป็นของเครื่องครับ
และสุดท้าย debug
สามารถกำหนดได้ตามต้องการเลย
เพราะฉะนั้นหน้าตาสุดท้ายของ code ในการสั่งให้ start server ก็จะได้ออกมาประมาณนี้
app.run(host="0.0.0.0", port=8000, debug=True)
Adding route
ในบทความนี้ไม่ได้เน้นไปที่การสอนใช้งาน Flask แต่ผมก็ยังอยากจะอธิบายแบบไว ๆ เอาไว้เผื่อไปต่อยอดครับ
@app.get("/")
def hello_world():
return {"message": "Hello, World!"}, 200
จากตัวอย่างในหัวข้อก่อนหน้าจะมีตัวอย่างการสร้าง route ที่มี method เป็น GET
เอาไว้หนึ่งอันแล้ว ซึ่งจะสามารถแบ่งหลัก ๆ ได้เป็นสองส่วนคือส่วนของ route config
และ function
Route config
ใช้ในการกำหนด route นั้น ๆ ว่าจะให้มี url แบบไหน รับ method อะไร
@app
คำว่าapp
จะเปลี่ยนไปตามชื่อตัวแปรที่เก็บค่า Flask app เอาไว้get()
ใช้ในการกำหนด method ของ function นี้ว่า request ที่เข้ามา route นี้จะต้อง request มาด้วย method ที่กำหนดเท่านั้นถึงจะเริ่มทำงานส่วน function ซึ่ง method ที่มีให้ใช้จะมีget()
,post()
,put()
และdelete()
("/")
คือ url ของ route ซึ่งสามารถกำหนดให้รับ parameter ผ่านทาง url ได้
Function
เป็นส่วนที่กำหนดการทำงานของ route นั้น ๆ ว่าจะทำอะไรบ้างเมื่อมี request เข้ามา โดยในตอนสุดท้ายจะ return response body
และ http response code
หน้าตาเหมือน function ปกติใน Python เลยครับ
Adding API document
ต่อจากตรงนี้จะเป็นส่วนเนื้อหาหลักของบทความนี้แล้วครับ ก็คือการทำ auto generate API document นั่นเอง ในตอนนี้เรามี code server อย่างง่ายที่ทำด้วย Flask แล้ว ต่อมาเราจะใช้ library ที่ชื่อว่า flask-openapi3 ในการช่วยสร้างครับ
Step 1: Edit code
จากตอนแรกที่สร้าง Flask server ด้วยคำสั่ง app = Flask()
เราจะเปลี่ยนเป็นสร้าง server ด้วยคำสั่ง app = OpenAPI()
แทน ซึ่งจะมี parameters มากมายให้ส่งค่าไปสำหรับปรับแต่ง doc ตัวอย่างเช่น
info
สำหรับเพิ่มคำอธิบายเพิ่มเติมเกี่ยวกับ APItemplate_folder
path ไปยัง folder ที่เก็บไฟล์ UI เอาไว้static_url_path
path ไปยัง folder ที่เก็บไฟล์ static ต่าง ๆ เช่น logodoc_prefix
prefix สำหรับ route ไปยัง document
และยังมี paremeters อื่น ๆ ให้ใช้อีกมาก สามารถอ่านเพิ่มเติมได้ที่
สุดท้ายก็จะได้ออกมาประมาณนี้
# from flask import Flask
# app = Flask(__name__)
# Change from above to this
from flask_openapi3 import OpenAPI, Info
app = OpenAPI(__name__, info=Info(title="My API", version="1.0.0"))
เมื่อทำการ start server อีกครั้งแล้วเข้าไปที่ http://127.0.0.1:8000/openapi จะพบกับหน้าที่รวม document แต่ละแบบเอาไว้ โดย /openapi
สามารถเปลี่ยนเป็นอย่างอื่นได้ด้วย paremeter doc_prefix
ที่ได้อธิบายไปก่อนหน้า
จะเห็นว่าเรามี document ถึง 3 แบบให้เลือกใช้ ผมจะเลือกใช้ Swagger เป็นหลักครับ แต่จะให้มาเข้าหน้านี้แล้วคอยกดเข้าเองอีกทีทุกครั้งก็ดูจะน่ารำคาญและเสียเวลาไม่น้อย เพราะฉะนั้นเราจะแก้ไขเพื่อให้สามารถเข้าถึง Swagger ได้ในทันทีที่กดลิงก์ครับ
Step 2: Custom path to document
ในการแก้ไข url เราก็จะปรับกันด้วย parameters เหมือนเดิมครับ ซึ่ง parameters ที่เกี่ยวข้องก็จะมี
doc_prefix
โดยตัวนี้เรารู้จักกันแล้วว่าคืออะไร สามารถส่ง prefix ที่ต้องการได้เลยครับ ในที่นี้ผมเลือกเป็น"api/v1/docs"
swagger_url
ใช้ในการกำหนด url ที่จะเข้าถึง Swagger โดยการนำค่าจาก parameter นี้ไปต่อท้ายdoc_prefix
เพราะฉะนั้นผมเลือกที่จะใช้เป็น"/"
เพื่อให้เมื่อกดที่ลิงก์สำหรับเข้า document แล้วจะถูกพาไปยังหน้า Swagger ทันที
app = OpenAPI(
__name__,
info=Info(title="My API", version="1.0.0"),
doc_prefix="/api/v1/docs",
swagger_url="/",
)
เมื่อ start server อีกครั้งและ refresh หน้าที่เปิดค้างเอาไว้อีกครั้งก็จะพบว่า “Not Found” เพราะว่าเราได้เปลี่ยน path ที่จะเปิด document แล้ว แต่หากเข้าไปที่ลิงก์ http://127.0.0.1:8000/api/v1/docs/ จะพบกับ Swagger UI พร้อมรายละเอียดที่เราเขียนเอาไว้เป็น info
แต่ก็จะพบว่ายังไม่เห็นมีอะไรใน document เลยทั้ง ๆ ที่เราก็จะมีอยู่ route หนึ่งแล้วไม่ใช่หรอ
Step 3: Add route to document
ในการจะเพิ่ม route เราจะใช้สิ่งที่เรียกว่า “blueprint” ครับ
Flask uses a concept of blueprints for making application components and supporting common patterns within an application or across applications.
ในการใช้ blueprint จะช่วยให้เราสามารถแบ่ง API ให้เป็นหมวดหมู่ได้ง่ายยิ่งขึ้นครับ สามารถเพิ่ม prefix และอื่น ๆ ได้ อ่านรายละเอียดการใช้งานเพิ่มเติมได้ที่
เริ่มจากการสร้าง API blueprint ขึ้นมาก่อนด้วยคำสั่ง
from flask_openapi3 import APIBlueprint
api = APIBlueprint("api", __name__, url_prefix="/api/v1")
เมื่อมี API blueprint แล้ว ถัดมาเราจะทำการเพิ่ม route ที่มีอยู่เข้าไปยัง blueprint นี้ครับ ซึ่งวิธีนั้นง่ายมาก ๆ แก้เพียงแค่นิดเดียว แล้วยังสามารถเพิ่มเติมรายละเอียดอื่น ๆ ได้อีกด้วย
# @api.get("/")
# Change from above to this
from flask_openapi3 import Tag
util_tag = Tag(name="util", description="Utility API functions")
@api.get(
"/",
description="Hello, World!",
tags=[util_tag],
)
def hello_world():
return {"message": "Hello, World!"}, 200
จะเห็นว่าแก้แค่เพียงจากเดิมที่ใช้ @app.get()
ก็เปลี่ยนเป็น @api.get()
หรือก็คือสร้าง route ผ่าน API blueprint แทนที่จะสร้างตรง ๆ ผ่าน Flask app อีกทั้งยังสามารถเพิ่มรายละเอียดอื่น ๆ เข้าไปได้อีก เพื่อให้ document หรือคู่มือการใช้งานที่สร้างขึ้นมีความเป็นระเบียบ จัดเป็นหมวดหมู่ และง่ายต่อการอ่านหรือใช้งานครับ
description
ตรงตัวเลยคือคำอธิบายของ route นั้น ๆ ว่ามีหน้าที่ทำอะไรtags
สิ่งนี้จะช่วยในการจัดหมวดหมู่ของ API ว่ามีหน้าที่ทำงานเกี่ยวกับอะไร ช่วยให้หา API ที่ต้องการใช้ได้ง่ายขึ้น มีความเป็นระเบียบเรียบร้อยครับ แต่การสร้าง Tag ก็ต้องใช้ function จากของ flask-openapi3 นะครับ
หลังจากที่แก้ไขเรียบร้อย เมื่อสั่ง start server และเข้าหน้า Swagger อีกครั้งก็จะพบว่ามี route ที่เราสร้างเมื่อสักครู่แล้ว พร้อม tag และคำอธิบายเขียนเอาไว้ สามารถกดทดลอง request ได้ด้วย
เพียงเท่านี้ทุกครั้งที่ route ใหม่ถูกสร้างขึ้นก็จะถูกเพิ่มเข้าไปยัง doc หรือว่าคู่มือการใช้ทันที ไม่ต้องทำอะไรเพิ่มแล้ว ลดโอกาสการเกิด conflict ระหว่างเอกสารและ application ของเรา ลดความวุ่นวายและปริมาณงานของเราที่ต้องมาคอยแก้เอกสารให้ตรงกัน อีกทั้งนักพัฒนาอื่น ๆ ที่มาใช้ระบบก็สามารถอ่านและทราบได้ทันทีว่าระบบของเรามีอะไรให้ใช้บ้าง
สุดท้ายเมื่อนำ code ทั้งหมดมารวมกันก็จะได้ออกมาเป็นแบบนี้
สั้น ๆ แค่นี้ก็สามารถใช้งานได้แล้ว ไม่ยุ่งยากเลยใช่ไหมล่ะครับ 🎊
Extra: Schema and Validation
ในเมื่อทำ document ขึ้นมาทั้งที ไหน ๆ ก็ทำมาจนถึงตรงนี้แล้วก็น่าจะทำให้สมบูรณ์ไปเลย ด้วยการทำตัวอย่าง request/response example body เพื่อให้ง่ายต่อผู้ใช้งาน จะได้กลายเป็นคู่มือการใช้งานที่สมบูรณ์แบบ
อีกทั้งยังเป็นการทำ validation ว่า request/response มีหน้าตาถูกต้องตามที่กำหนดหรือไม่ ซึ่งเป็นจุดสำคัญที่จะช่วยให้ระบบของเรามีมาตรฐานที่ดีและใช้งานง่าย เพราะถ้าหากว่า request/response ไม่ถูกต้อง ระบบก็จะฟ้องทันที ทำให้ลดโอกาสการทำงานผิดพลาดด้วย
🔧 Tools
ก่อนจะเริ่มทำ เราควรทำความรู้จักกับ flask-openapi3 ให้ดีขึ้นซะก่อน เพราะการรู้จักเครื่องมือที่ใช้จะนำไปสู่การใช้เครื่องมือได้อย่างมีประสิทธิภาพ และเข้าใจถึงสิ่งที่เกิดขึ้นว่าเพราะอะไร ทำงานอย่างไร อีกทั้งยังทำให้สามารถแก้ไขปัญหาที่เกิดได้ง่ายขึ้นด้วย
flask-openapi3 is dependent on the following libraries:
Flask for the web app.
Pydantic for the data validation.
หมายความว่าเราที่กำลังจะทำ validation จะต้องทำงานกับ library ที่มีชื่อว่า Pydantic เมื่อรู้อย่างนี้ก็จะทำให้เราสามารถหาข้อมูลได้ง่ายยิ่งขึ้นครับ
API I/O
ในการรับ input ผ่าน API สามารถทำได้ด้วย 3 วิธีคือ
- Form body เป็นการส่งข้อมูลแนบไปกับการ request เหมือนเป็นจดหมายในซองที่แนบไปด้วย โดยส่วนใหญ่จะส่งกันใน format ของ json
- Path เป็นการส่งข้อมูลผ่าน url เปรียบเสมือนการจ่าที่อยู่หน้าซอง ซึ่งที่อยู่ก็สามารถเปลี่ยนไปตามที่ ๆ เราต้องการจะส่งจดหมาย
- QueryString เป็นการแนบข้อมูลต่อท้ายจาก Path เหมือนเป็นการแนบข้อความต่อท้ายว่า “บ้านที่อยู่ตรงหัวมุม” ประโยชน์คือข้อมูลตรงนี้จะติดไปกับ url หมายความว่าต่อให้ใครก็ตามมากดที่ url นี้ ก็จะส่งข้อมูลไปเหมือนกัน
ในการสร้าง schema สำหรับทำ validate จะใช้วิธีสร้าง class ที่ inherit มาจาก BaseModel
ของ Pydantic เป็นหลัก ไม่ว่าจะใช้สำหรับ Form body, Path หรือ QueryString ก็ตาม
from pydantic import BaseModel, Field
from typing import Optional
class ExampleSchema(BaseModel):
message: str = Field(description="this is message")
number: Optional[int] = Field(ge=2, le=4, description='this is number')
จากตัวอย่างจะเห็นว่าเหมือนเป็นการสร้าง class ปกติของ Python เลย เพียงแค่มีการ inheritance มาจาก BaseModel
และกำหนดค่าของ attribute
ให้เป็น Field
จาก Pydantic ซึ่งมีข้อดีคือสามารถกำหนด description
และทำ validation ได้ด้วย
ทั้งหมดนี้สามารถนำไปใช้กับส่วนของ response ได้ด้วย วิธีในการนำไปใช้กับ ก็มีดังนี้เลย
Usage
- Form body สามารถนำ schema มาใช้ได้โดยการเพิ่ม parameter ที่ชื่อว่า
body
ให้กับ function แล้วกำหนด type ของ parameter ให้เป็น schema
from pydantic import BaseModel, Field
class BodySchema(BaseModel):
message: str = Field(description="example message")
# @api.post(...)
def example(body: BodySchema):
# ...
return {"message": body.message}, 200
- Path สามารถกำหนดได้ด้วยการนำ schema มากำหนดเป็น type ให้ parameter ที่ชื่อว่า
path
from pydantic import BaseModel, Field
class PathSchema(BaseModel):
id: int = Field(description="example int")
name: str = Field(description="example string")
@api.post("/example/<id>/<name>")
def example(path: PathSchema):
# ...
return {"path": f"{path.id}/{path.name}"}, 200
path จะแตกต่างกับ body ตรงที่จะกำหนดเอาไว้ใน url ว่าจะให้มีชื่อและ type เป็นอย่างไร โดยชื่อที่ url ต้องตรงกับที่ schema ไม่เช่นนั้นจะนับว่าเป็นคนละตัวแปร แล้วจะทำให้ validate ไม่ผ่าน
- QueryString กำหนดได้โดยการเพิ่ม parameter ที่ชื่อว่า
query
แล้วกำหนด type ให้เป็น schema ที่สร้าง
from pydantic import BaseModel, Field
class QuerySchema(BaseModel):
query: str = Field(description="example query")
# @api.post(...)
def example(query: QuerySchema):
# ...
return {"query": query.query}, 200
- Response ในการกำหนด schema ให้ response สามารถทำได้โดยการกำหนดผ่าน route config ด้วย parameter ที่ชื่อว่า
response
ที่สามารถกำหนดได้ว่า http code ไหน จะ response ด้วย schema อะไร
from pydantic import BaseModel, Field
class ExampleQuery(BaseModel):
query: str = Field(description="example query")
class ExampleResponse(BaseModel):
query: ExampleQuery = Field(description="example query body")
@api.post("/example",
responses={
200: ExampleResponse,
},
)
def example(query: ExampleQuery):
# ...
return ExampleResponse(query=query).model_dump(), 200
ในตัวอย่างนี้จากที่ return ตรง ๆ เหมือนก่อนหน้าได้เปลี่ยนเป็นนำข้อมูลส่งเข้าไปยัง ExampleResponse
ก่อนเพื่อเป็นการทำ validation ให้แน่ใจว่า output ที่เรากำลังจะส่งออกไปนั้นมีหน้าตาถูกต้องแล้วหรือยัง
ข้อมูลเพิ่มเติมจากเอกสารของ flask-openapi3 เรื่องการกำหนด input จาก request
ซึ่งการกำหนด schema ให้กับ response มีประโยชน์คือทำให้เราสามารถกำหนดหน้าตาของ output ที่จะออกไปได้ สิ่งนี้เป็นเรื่องที่สำคัญมากเวลาทำงานร่วมกับนักพัฒนาคนอื่น ๆ เนื่องจากเมื่อรู้ output ที่แน่นอนก็สามารถพัฒนาระบบให้สามารถสื่อสารกันได้โดยที่ไม่ต้องรอให้ระบบของเราพัฒนาเสร็จก่อน
Example
หลังจากได้รู้จักกับทุกองค์ประกอบแล้ว ต่อมาก็จะเป็นการนำไปทดลองใช้จริงเพื่อให้เข้าใจถึงวิธีการใช้งาน ซึ่งผมก็ได้ลองทำ route ง่าย ๆ สำหรับทำการ echo message
ที่เราส่งไป และสามารถกำหนดจำนวนครั้งที่ต้องการให้ echo ได้ผ่านทาง query string
ทดลองใช้งานผ่าน Swagger UI
สามารถดูตัวอย่างเพิ่มเติมจาก doc ของ flask-openapi3 ได้ที่นี่
Defect
ถึงแม้ว่า flask-openapi3 จะสามารถช่วย generate document ได้อย่างดี แต่ก็ยังมีจุดที่ยังไม่สามารถทำได้สมบูรณ์อยู่ ❗️นั่นก็คือถึงแม้ว่าเราจะกำหนดให้ค่าใด ๆ ก็ตามเป็น Optional
แล้ว แต่ในหน้า doc ก็ยังจะขึ้น required บังคับให้เราต้องใส่อยู่ดี หากเป็นใน request body ก็ยังสามารถใส่ null
แทนได้ หากแต่ใน query string นั้นไม่สามารถทำได้เลย
ถึงอย่างนั้นในการ request โดยไม่ผ่าน document ก็ยังสามารถทำงานได้ตามปกติครับ
Summary
ท้ายที่สุดก็มาถึงจุดจบจนได้ หากข้อมูลส่วนไหนผิดผลาด ผมต้องขออภัยอย่างมากครับ สามารถบอกกล่าวแลกเปลี่ยนกันได้เลยครับ ผมเองนั้นใช้เวลาอยู่หลายวันในการเรียบเรียงข้อมูลแล้วเขียนบทความนี้ออกมาจนเสร็จ ถึงแม้ว่าจะไม่มีคนอ่านก็ไม่เป็นไร อย่างไรผมก็เขียนขึ้นมาเพื่อให้ตัวเองได้อ่านอีกครั้งในวันที่จำเป็นต้องใช้ความรู้เหล่านี้ แต่ไหน ๆ ก็ไหน ๆ แล้ว ผมก็อยากที่จะแบ่งปันข้อมูลให้คนอื่นด้วย ผมจึงได้เลือกที่จะเขียนเป็นบทความนี้ขึ้นมาครับ แต่ก็ยาวกว่าที่ผมคิดเอาไว้มากเลยทีเดียว 5555
สุดท้ายขอขอบคุณที่อ่านมาจนถึงตรงนี้มาก ๆ นะครับ
Bonus ✨
Template สำหรับ Flask API application with Open API