ساخت API توسط Flask-RESTPlus، Flask و Swagger UI
۲۴ تیر ۱۳۹۹
هنگام کار بر روی پروژههای یادگیری ماشین، تصمیم گرفتم که یک برنامه کامل را توسعه دهم. این قضیه به توسعه APIها نیاز دارد که ما بتوانیم دادهها را وارد کنیم (post data) و از طرف دیگر پیشبینیها را دریافت کنیم (get data). در اینجاست که Flask و Flask-RESTPlus وارد صحنه میشوند.
Flask این قابلیت را به توسعهدهنده میدهد که از توابع پایتونی برای ایجاد APIها استفاده کند. Flask-RESTPlus یک افزونه برای Flask است که قابلیتهای بیشتر، نظیر ایجاد REST APIها را به Flask به میدهد. این افزونه نه تنها به ما اجازه میدهد که REST APIها را ایجاد کنیم، بلکه میتوان تمام APIها را به Swagger UI اضافه کرد.
در این مقاله، نحوه توسعه یک برنامه توسط Flask به همراه چندین API و مقداری دیتا را بررسی خواهیم کرد. تمام کدهایی که در مقاله میبینید در این ریپازیتوری در دسترس هستند.
نصب
مراحل انجام این پروژه را با ایجاد یک محیط مجازی (virtual environment) برای کتابخانههای پایتون، با استفاده از دستور pipenv
شروع میکنم. برای مشاهد تفاوت میان محیطهای مجازی، میتوانید به این مقاله مراجعه کنید. ابتدا Flask و Flask-RESTPlus را نصب میکنیم:
pipenv install flask
pipenv install flask-restplus
بههرحال اگر نخواهید از pipenv و یا محیطهای مجازی استفاده کنید، میتوانید به سادگی از pip استفاده کنید:
pip install flask
pip install flask-restplus
Import
پروژه را با واردکردن (import
کردن) Flask
از ماژول flask
و Api
و Resource
از ماژول flask_restplus
آغاز میکنم. از Api
برای ساخت برنامه و از Resource
به عنوان ورودی برای کلاسهایی که در داخل پروژه تعریف کردیم، استفاده میکنم:
from flask import Flask
from flask_restplus import Api, Resource
ساخت برنامه
برنامه flask را توسط تابع Flask
، که نام آن را توسط __name__
تنظیم میکند، ایجاد کردم. در مرحله بعد از Api
برای راهاندازی برنامه استفاده میکنم:
flask_app = Flask(__name__)
app = Api(app = flask_app)
name_space = app.namespace('main', description='Main APIs')
در اینجا namespace
را ایجاد کردم. مفهوم و تصور کلی از آن بسیار ساده است. هرگاه APIهایی را در زیر یک namespace
تعریف کنیم، آنها در زیر یک دستهبندی در Swagger UI قرار میگیرند (Swagger UI را در ادامه بررسی میکنیم). در تعریف namespace
، متغیر اول، مسیر را تعریف میکند و متغیر دوم توضیحی اضافه برای آن قسمت را تعریف میکند.
در مثال بالا آدرس namespace
ایجاد شده به این صورت است: http://127.0.0.1:5000/main
و توضیحات آن در Swagger برابر با Main APIs
است.
تعریف APIها
در مرحله قبلی یک namespace
ایجاد کردیم. از آنجایی که متغیری که namespace
را در آن ایجاد کردیم، name_space
نام دارد، برای ایجاد route
در ادامه این namespace
از @name_space.route("/")
قبل ایجاد متدها استفاده میکنیم. در ادامه نیاز است که endpointها را در داخل این route
ایجاد کنیم. متدهای داخل این route
میتواند get()
و یا post()
و … باشد.
@name_space.route("/")
class MainClass(Resource):
def get(self):
return {
"status": "Got new data"
}
def post(self):
return {
"status": "Posted new data"
}
در این مثال، API از طریق مسیر http://127.0.0.1:5000/main
در دسترس است. نام کلاس MainClass
است که شامل دو متد get()
و post()
میشود. هرگاه درخواستی با متد GET
به این API ارسال کنم، فیلدی بنام status
با مقدار Got new data
دریافت میکنم، زمان استفاده از متد POST، مقدار این فیلد برابر با Posted new data
خواهد بود.
اجرای برنامه
حالا که همه چیز آماده است، باید برنامه را که در فایلی به نام basic.py
ذخیره کردهایم، توسط pipenv
اجرا کنیم:
pipenv shell
FLASK_APP=basic.py flask run
اما اگر در مرحله اول از pip
به جای pipenv
استفاده کردید، از دستور زیر برای اجرای برنامه استفاده کنید:
FLASK_APP=basic.py flask run
Swagger UI
بهترین بخش Flask-RESTPlus این است که به صورت خودکار مستندات APIهایی که ایجاد کردیم ساخته میشود و در Swagger UI قابل مشاهده هستند. آدرس http://127.0.0.1:5000
را در مرورگر خود وارد کنید در نتیجه تمامی APIهایی که ایجاد کردهاید را مشاهده خواهید کرد.
هردوی APIهایی که ایجاد کردیم، در زیر namespace
با نام main
که توضیحات آن Main APIs
است، قابل مشاهده است. میتوانیم هردو آنها را به همراه کارکردشان توسط دکمه Try it out
امتحان کنیم.
آزمایش API
از curl برای ایجاد یک درخواست GET
و POST
در ترمینال استفاده میکنم:
به هنگام استفاده از دستور curl
، ابتدا از واژه curl
به همراه نوع متد درخواست بعد از سوییچ -X
استفاده کنید. در انتها هم مقصد را مشخص کنید. با توجه به پاسخی که از curl
دریافت کردیم، میفهمیم که دیتای درستی را از هر دو API، یعنی هم GET
و هم POST
دریافت شدهاست.
استفادههای بیشتر
موارد قابل استفاده بیشتری در رابطه با Flask و FlaskRESTPlus وجود دارد. بیایید آنها را عمیقتر بررسی کنیم تا بتوانیم آنها را بهتر متوجه شویم. کدی که در ادامه بررسی خواهیم کرد، تحت عنوان app.py
در ریپازیتوری موجود است.
میتوانیم از متد POST
برای ارسال دیتا و ذخیرهشان و از متد GET
هم برای دریافت و مشاهده آنها بهره ببریم. بیایید در نظر بگیریم که پروژهای داریم که در آن اسامی اشخاص را ذخیره و مدیریت میکنیم. یک endpoint با متد GET
ایجاد کردیم که میتوانیم توسط آن یک اسم را با استفاده id
آن، دریافت کنیم، همچنین endpoint دیگری داریم که در آن از متد POST
برای ذخیره هر اسم در مقابل یک id
، استفاده میکنیم.
در اینجا، این مسیر را ایجاد کردهام: http//127.0.0.1:5000/names/<int:id>
که هر بار مقدار id
را در آن ارسال میکنیم. برای ذخیره سازی آنها، آبجکتی با نام list_of_names
ایجاد کردهام که برای دریافت و ارسال دیتا مورد استفاده قرار خواهد گرفت.
استفاده بیشتر از کتابخانهها
تا به اینجای کار Api
، Flask
و Resource
را وارد کدمان کردهایم. در این بخش request
را از ماژول flask
وارد کدمان میکنیم، که به ما این امکان را میدهد یک درخواست را دریافت کنیم و بتوانیم از اطلاعات آن در قالبی مثل JSON
استفاده کنیم. همچنین fields
را از ماژول flask_restplus
، برای تعریف انواع داده مختلف، مثل String
وارد کد کردیم.
from flask import Flask, request
from flask_restplus import Api, Resource, fields
افزودن اطلاعات برنامه
همجنین میتوانیم اطلاعات بیشتری به برنامه flaskمان اضافه کنیم. این اطلاعات در برخی موارد بسیار مفید خواهند بود و حتی در Swagger UI هم نمایش داده خواهند شد.
flask_app = Flask(__name__)
app = Api(app = flask_app,
version = "1.0",
title = "Name Recorder",
description = "Manage names of various users of the application")
name_space = app.namespace('names', description='Manage names')
میتوانیم title
، version
و description
برنامهمان را تعریف کنیم. همچنین نام تنها namespace را names
مقدار دادیم. حالا سرتیتر در Swagger UI باید مانند تصویر زیر باشد:
تعریف مدلها
هرگاه بخواهیم اطلاعات را در قالب خاصی (JSON
) ارسال و یا دریاف کنیم، از model
استفاده میکنیم. ابتدا نام آن را مشخص میکنیم. در مرحله بعد اطلاعاتی که نیاز دارد، به همراه ویژگیهای آنها را تعریف میکنیم.
model = app.model('Name Model',
{'name': fields.String(required = True,
description="Name of the person",
help="Name cannot be blank.")})
نام این مدل را Name Model
تنظیم کردیم که شامل یک پارامتر با نام name
میشود که این فیلد، یک فیلد ضروری (required
) است و نمیتواند خالی باشد، همچنین توضیحات و متن راهنما برای آن تنظیم کردیم. API زمانی که قصد استفاده از این مدل را داشته باشد، انتظار دریافت دیتا در قالب JSON
با کلیدی به نام name
را دارد.
list_of_names = {}
برای ذخیره تمامی اسمها، آنها را در list_of_names
ذخیره میکنم.
تعریف APIها
@name_space.route("/<int:id>")
class MainClass(Resource):
@app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }, params={ 'id': 'Specify the Id associated with the person' })
def get(self, id):
try:
name = list_of_names[id]
return {
"status": "Person retrieved",
"name" : list_of_names[id]
}
except KeyError as e:
name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500")
except Exception as e:
name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400")
@app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }, params={ 'id': 'Specify the Id associated with the person' })
@app.expect(model)
def post(self, id):
try:
list_of_names[id] = request.json['name']
return {
"status": "New person added",
"name": list_of_names[id]
}
except KeyError as e:
name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500")
except Exception as e:
name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400")
بیایید کد بالا را به بخشهای کوچکتری تقسیم کنیم تا فهم آن آسانتر شود. ابتدا بخش POST
را بررسی میکنیم، کارکرد بخش GET
مشابه POST
خواهد بود.
تعریف route و کلاس
@name_space.route("/<int:id>")
class MainClass(Resource):
از name_space
که در بالاتر ایجاد کردیم، برای ایجاد route
استفاده میکنیم. http://127.0.0.1:5000/main/<int:id>
این آدرس انتظار دارد id
، که از نوع عدد صحیح است را به عنوان پارامتر دریافت کند. نام کلاس MainClass
است که Resource
را به عنوان ورودی به آن دادهایم.
ایجاد مستندات برای API
@app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }, params={ 'id': 'Specify the Id associated with the person' })
استفاده از doc
باعث میشود که بتوانیم مستنداتی برای APIمان در Swagger UI ایجاد کنیم. responses
، انواع مختلفی از کدهای وضعیت مختلف HTTP
را برای شرایط مختلف شامل میشود. برای هر کد وضعیت، توضیحاتی تعریف کردهایم که اطلاعات بیشتری به کاربر میدهد. params
، پارامترهایی که انتظار دریافت آنها را داریم، مشخص میکند. API در درخواستی که از سمت کاربر دریافت میکند انتظار وجود id
را در URL
دارد و همچنین یک متن راهنما برای نمایش به کاربر تعیین کردهایم. تا به اینجای کار Swagger UI باید شبیه تصویر زیر باشد:
پارامترها در بخش بالایی تصویر نمایش داده شدهاند. تمام پاسخهایی که ممکن است به سمت کاربر برگردد، به همراه توضیحات آنها، در بخش پایینی تصویر قرار دارد.
تعریف متد
@app.expect(model)
def post(self, id):
try:
list_of_names[id] = request.json['name']
return {
"status": "New person added",
"name": list_of_names[id]
}
except KeyError as e:
name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500")
except Exception as e:
name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400")
حالا میتوانیم متد خودمان را تعریف کنیم. اما قبل از تعریف متد، @app.expect(model)
را نوشتهایم که به این معناست API انتظار دریافت model
را دارد. همچنین کدمان را در داخل بلوک try
گذاشتهایم تا بتوانیم تمام خطاهای احتمالی را کنترل کنیم. request.json['name']
مقدار name
دریافتی را برمیگرداند که درنتیجه میتوانیم آن را ذخیره کنیم همانطور که میتوانیم آن را در پاسخ به کاربر نیز ارسال کنیم. اگر کلید name
وجود نداشته باشد، با خطای KeyError
روبرو خواهیم شد و در پاسخ کاربر، کد وضعیت 500
را به همراه توضیحاتی که برای آن تعیین کردیم ارسال خواهیم کرد. در سایر خطاها کد وضعیت 400
را ارسال خواهیم کرد.
آزمایش برنامه
با دستور زیر برنامه را اجرا میکنیم:
FLASK_APP=app.py flask run
POST
ابتدا بعد از دریافت درخواست، مقدار name
را از داخل آن بدست میآوریم و سپس آن را در مقابل یک id
در list_of_names
ذخیره میکنیم. همچنین وضعیت و نام شخص جدیدی که آن را ذخیره کردیم را نیز برمیگردانیم.
خطا در درخواست POST
فرض کنیم در موردی فراموش شده که به name
مقداری اختصاص بدهیم. در این حالت با خطا مواجه خواهیم شد.
همانطور که مشاهده کردید، کلید name
را در دیتای ارسالی خود به API مشخص نکردیم و با خطایی با کد 500
و پیام Mapping key not found.
روبرو شدیم.
GET
در این متد تنها نیاز داریم که id
نامی که میخواهیم مقدار آن را دریافت کنیم را در درخواست ارسال کنیم، در پاسخ اگر شخصی با چنین id
وجود داشته باشد، کد وضعیت و نام آن شخص را دریافت خواهیم کرد.
خطا در درخواست GET
در نظر بگیرید که شخصی با id
برابر 2
نداشته باشیم. اگر درخواستی برای دریافت اطلاعات شخص با id = 2
کنیم، با خطا روبرو خواهیم شد.
از آنجایی که چنین شخصی با id
مشخص شده وجود نداشت، به خطا با کد 500
و پیام Mapping key not found.
برخورد کردیم.
جمعبندی
در این مقاله ایجاد یک API توسط Flask و Flask-RESTPlus را بررسی کردیم. هر دو این ماژولها، موارد مناسبی برای ساخت APIهای مستندسازیشده توسط پایتون است که در عین حال بتوان از Swagger UI استفاده کرد.
توسعهدهندگان دربارهی ما چه میگویند
تجربه کار باliara_cloud@امروز خیلی خوب بود. یکی از سرویس هام رو منتقل کردم روش و راضیم. انقد سریع و جذاب کارم راه افتادم اصن باورم نمیشد! برعکس سرویس های PaaS دیگه با اون همه پیچیدگیشون. دمتون گرم
...
MohammadReza
keikaavousi
بعد از بسته شدن @fandoghpaas و ناراحتی همهمون از اینکه یه سرویس خوب و صادق نمیتونه از پس هزینهها بر بیاد، سرویسم رو منتقل کردم به پاس لیارا (https://liara.ir @liara_cloud) . تجربه راحت و خوب. تفاوتهایی داشت که کمی کار میخواست ولی تا الان کاملا راضی.
jadi
jadi
یه خسته نباشید باید به تصمیمliara_cloud@بگم،
بعد از چندین روز سرکله زدن با سرویس های مشابه بالاخره تصمیم گرفتم لیارا رو امتحان کنم و باور نمیشه ۱۰ دقیقه بیشتر وقت نبرد،
دمتون گرم.
Arch
EbadiDev
واسه سرویس PaaS با اختلاف لیارا بهترین رابط کاربری داره و یکی از مزیتهای سرویس دیتابیسشون اینه که خودشون به صورت دورهای بکآپ میگیرن.
...
Ali Najafi
me_ali_najafi
یکی از کارهای خوبی که جدیداً میکنم اینه که یه دیتابیس روی لیارا میسازم و به پروژه وصل میکنم اینطوری هم خونه و هم محل کار دیتابیس بروز رو دارم و راحت میتونم ادامه بدم کار روliara_cloud@
Navid
1navid
عاشقliara_cloud@شدم درسته در حد AWS نیست ولی خب تجربه خوبی واسه پروژه های داخل ایران ارائه میده، میتونم رو CD هم اجراش کنم
Amir H Shekari
vanenshi