Easy automate OpenAPI with Flask ง่าย ๆ ไม่ต้องเขียนเอง

An easy and simple guide to create Flask API with documentation step by step

Irisia
8 min readApr 21, 2024
Cover image
Cover image

เวลาพูดถึงการสร้าง 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 Python 3.3, a subset of it has been integrated into the standard library under the venv module.

เพื่อให้เห็นภาพมากขึ้นจะขออธิบายด้วยรูปประกอบนะครับ

ภาพตัวอย่างการใช้ virtualenv ในการแบ่งแต่ละโปรเจกต์
ภาพตัวอย่างการใช้ virtualenv ในการแบ่งแต่ละโปรเจกต์

จากรูปจะเห็นว่าใน 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 ที่กำหนดใน requirements.text

แล้วจากนั้นจึงใช้คำสั่งเพื่อติดตั้ง 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 ไม่กี่บรรทัดดังนี้

code สำหรับเริ่มต้นสร้าง API ด้วย Flask

เพียงเท่านี้เราก็ได้ 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 ทั้งหมดที่ได้ก็จะออกมาประมาณนี้

code สำหรับเริ่มต้นสร้าง API ด้วย Flask พร้อมคำสั่งเริ่มทำงาน

แล้วจากนั้นก็จะสามารถใช้คำสั่งในการ 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 สำหรับเพิ่มคำอธิบายเพิ่มเติมเกี่ยวกับ API
  • template_folder path ไปยัง folder ที่เก็บไฟล์ UI เอาไว้
  • static_url_path path ไปยัง folder ที่เก็บไฟล์ static ต่าง ๆ เช่น logo
  • doc_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 ที่ได้อธิบายไปก่อนหน้า

http://127.0.0.1:8000/openapi
ภาพหน้าต่างของ document ทั้งหมดที่มีให้เลือกใช้

จะเห็นว่าเรามี 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

หน้า Swagger UI

แต่ก็จะพบว่ายังไม่เห็นมีอะไรใน 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 ได้ด้วย

หน้า Swagger UI พร้อม API ที่มี
หน้า Swagger UI พร้อม API ที่มี

เพียงเท่านี้ทุกครั้งที่ 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 วิธีคือ

  1. Form body เป็นการส่งข้อมูลแนบไปกับการ request เหมือนเป็นจดหมายในซองที่แนบไปด้วย โดยส่วนใหญ่จะส่งกันใน format ของ json
  2. Path เป็นการส่งข้อมูลผ่าน url เปรียบเสมือนการจ่าที่อยู่หน้าซอง ซึ่งที่อยู่ก็สามารถเปลี่ยนไปตามที่ ๆ เราต้องการจะส่งจดหมาย
  3. QueryString เป็นการแนบข้อมูลต่อท้ายจาก Path เหมือนเป็นการแนบข้อความต่อท้ายว่า “บ้านที่อยู่ตรงหัวมุม” ประโยชน์คือข้อมูลตรงนี้จะติดไปกับ url หมายความว่าต่อให้ใครก็ตามมากดที่ url นี้ ก็จะส่งข้อมูลไปเหมือนกัน
ตัวอย่างโครงสร้าง 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

ทดสอบ request /echo
ทดสอบ request /echo
ผลลัพธ์ที่ได้
ผลลัพธ์ที่ได้จากการทดสอบ

สามารถดูตัวอย่างเพิ่มเติมจาก 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

--

--