Restful API規范
restful api是用於前端和后台進行通信的一套規范。使用這個規范可以讓前后端開發變得更加輕松。
協議
采用http或者https
數據傳輸格式
數據之間傳輸的格式應該都使用json,而不是xml
url鏈接
url鏈接中,不能有動詞,只能有名詞。並且對於一些名詞,如果出現復數,那么應該在后面加s
比如: 獲取文章列表,應該使用/articles/, 而不應該使用/get_article/
HTTP請求方法
GET: 從服務器上獲取資源 (常用)
POST:在服務器上新創建一個資源 (常用)
PUT:在服務器上更新資源。(客戶端提供所有改變后的數據)
PATCH:在服務器上更新資源。(客戶端只提供需要改變的屬性)
DELETE: 從服務器上刪除資源
狀態碼
狀態碼 | 原生描述 | 描述 |
200 | OK | 服務器成功響應客戶端的請求 |
400 | INVALID REQUEST | 用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作 |
401 | Unauthorized | 用戶沒有權限訪問這個請求 |
403 | Forbidden | 因為某些原因禁止訪問這個請求 |
404 | NOT FOUND | 用戶發送的請求的url不存在 |
406 | NOT Accept | 用戶請求不被服務器接收(比如服務器期望客戶端發送某個字段,但沒有發送) |
500 | Internal server error | 服務器內部錯誤,比如出現了bug |
Flask-Restful插件的基本使用
安裝flask-restful插件
pip install flask-restful
定義Restful視圖
如果使用flask-restful,那么定義視圖函數的時候,就要繼承自flask_restful.Resource類,然后再根據當前請求的method來定義相應的方法。比如期望客戶端使用get方法發送請求,那么就定義一個get方法,期望客戶端使用post方法發送請求,就定義一個post方法。類似於MethodView.
因為我們只定義了post方法,如果直接用瀏覽器去訪問http://127.0.0.1:5000/login/(get方法),得不到我們想要的結果
這個時候我們就可以用一些測試工具,我這里使用的是Insomnia,同類的軟件還有postman
url中也可以傳遞參數
url也可以是多個
參數驗證
Flask-Restful插件提供了類似WTForm來驗證提交的數據是否合法的包,叫做reqparse。以下是基本用法
parser = reqparse.RequestParser() parser.add_argument('username', type=str, required=True, help='用戶名驗證錯誤') args = parser.parse_args()
add_argument可以指定這個字段的名字,這個字段的數據類型等
default:默認值,如果這個參數沒有值,那么將使用這個參數指定的值。
required:是否必須。默認為False,如果設置為True,那么這個參數就必須提交上來。
type:這個參數的數據類型,如果指定,那么將使用指定的數據類型來強制轉換提交上來的值。
choices:選項。提交上來的值只有滿足這個選項中的值才符合驗證通過,否則驗證不通過。
help:錯誤信息。如果驗證失敗后,將會使用這個參數指定的值作為錯誤信息。
trim:是否要去掉前后的空格。
其中的type,可以使用python自帶的一些數據類型,也可以使用flask_restful.inputs下的一些特定的數據類型來強制轉換。比如一些常用的:
url:會判斷這個參數的值是否是一個url,如果不是,那么就會拋出異常。
regex:正則表達式。
date:將這個字符串轉換為datetime.date數據類型。如果轉換不成功,則會拋出一個異常。
操作演示
因為username字段添加了參數required=True,為必填項,因此如果客戶端不傳此字段,就會提示錯誤,這個錯誤提示就是我們自定的help--->"用戶名驗證錯誤"
... from flask_restful import Api, Resource, reqparse, inputs app = Flask(__name__) app.config.from_object(config) api = Api(app) class RegisterView(Resource): def post(self): parser = reqparse.RequestParser() parser.add_argument('username', type=str, required=True, help='用戶名字段驗證錯誤') parser.add_argument('birthday', type=inputs.date, required=True, help='生日字段驗證錯誤') parser.add_argument('gender', type=str, choices=('male', 'female'), help='性別字段驗證錯誤') parser.add_argument('money', type=int, trim=True, default=0, help='金額字段驗證錯誤') parser.add_argument('phone', type=inputs.regex(r'1[3458]\d{9}'), help='手機字段驗證錯誤') parser.add_argument('blog', type=inputs.url, help='博客地址字段驗證錯誤') args = parser.parse_args() print(args) return 'success' api.add_resource(RegisterView, '/register/', endpoint='register') ...
Flask-Restful標准化返回參數
對於一個視圖函數,我們可以指定好一些參數用於返回,在規范中要求:即使這個參數沒有值也應該返回,返回一個None回去
... from flask_restful import Api, Resource, fields, marshal_with api = Api(app) class ArticleView(Resource): resource_field = { #先定義好返回哪些參數 'name': fields.String, #參數的數據類型 'age': fields.String, 'school': fields.String } @marshal_with(resource_field) #利用marshal_with裝飾器傳入定義好的返回參數 def get(self): return {} 就算這里返回個空字典,也會把定義好的參數返回 api.add_resource(ArticleView, '/article/', endpoint='article')
... class ArticleView(Resource): resource_field = { 'name': fields.String, 'age': fields.String, 'school': fields.String } @marshal_with(resource_field) def get(self): return {'name': 'heboan', 'age': 18} ...
當使用ORM模型或者自定義的的模型的時候,它會自動的獲取模型中相應的字段,生成json數據,然后返回給客戶端
class ProfileView(Resource): resource_fields = { 'username': fields.String, 'age': fields.Integer, 'school': fields.String } @marshal_with(resource_fields) def get(self,user_id): user = User.query.get(user_id) return user
在get方法中,返回user的時候,flask_restful會自動的讀取user模型上的username, age以及school屬性。組裝成一個json格式的字符串返回給客戶端
重名屬性
如果我們想把面向公眾的字段名稱不用與內部的屬性名。使用attribute可以配置這種屬性,比如現在想要返回user.school中的值,但是在返回給外面的時候,想以education返回回去,那么可以這樣寫:
resource_fields = { 'education': fields.String(attribute='school') }
默認值
在返回一些字段的時候,有時候可能沒有值,那么這時候可以在指定fields的時候給定一個默認值,示例代碼如下:
resource_fields = { 'age': fields.Integer(default=18) }
復雜的結構
有時候想要在返回的數據格式中,形成比較復雜的結構。那么可以使用一些特殊的字段來實現。比如要在一個字段中放置一個列表,那么可以使用fields.List,比如在一個字段下面又是一個字典,那么可以使用fields.Nested。以下將講解下復雜結構的用法:
定義數據庫結構:
user表,article表,tag表, 因為article與tag是多對多的關系,因此需要一個中間表來關聯article_tag

from exts import db class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(50), nullable=False) class Article(db.Model): __tablename__ = 'article' id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(50), nullable=False) content = db.Column(db.Text, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) author = db.relationship('User', backref='articles') tags = db.relationship('Tag', secondary='article_tag') class Tag(db.Model): __tablename__ = 'tag' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(50), nullable=False) article_tag = db.Table( 'article_tag', db.Column('article_id', db.Integer, db.ForeignKey('article.id')), db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')) )
插入測試數據

... from models import User, Article, Tag @app.route('/') def index(): user = User(username='heboan') tag_1 = Tag(name='Linux') tag_2 = Tag(name='Python') article = Article(title='python自動化運維', content='Life is short, I use python...') article.tags.append(tag_1) article.tags.append(tag_2) article.author = user db.session.add(article) db.session.commit() return '數據插入成功!'
當我們全部使用field.String
class ArticleView(Resource): resource_fields = { 'title':fields.String, 'content': fields.String, 'author':fields.String, 'tags':fields.String } @marshal_with(resource_fields) def get(self): article = db.session.query(Article).get(1) return article api.add_resource(ArticleView, '/article/', endpoint='article')
使用field.Nested、field.List
class ArticleView(Resource): resource_fields = { 'title':fields.String, 'content': fields.String, 'author':fields.Nested({ #字典里面嵌套字典使用field.Nested 'username': fields.String }), 'tags':fields.List(fields.Nested({ #tags是列表,使用field_List類型然后嵌套field.Nested 'id': fields.Integer, 'name': fields.String })) } @marshal_with(resource_fields) def get(self): article = db.session.query(Article).get(1) print(article.title) return article api.add_resource(ArticleView, '/article/', endpoint='article')
在藍圖使用使用Flask-Restful
新建一個articles.py
from flask import Blueprint from flask_restful import Api, Resource, fields, marshal_with from exts import db from models import Article article_bp = Blueprint('article', __name__, url_prefix='/article') api = Api(article_bp) #這里使用article_bp了 class ArticleView(Resource): resource_fields = { 'title':fields.String, 'content': fields.String, 'author':fields.Nested({ 'username': fields.String }), 'tags':fields.List(fields.Nested({ 'id': fields.Integer, 'name': fields.String })) } @marshal_with(resource_fields) def get(self): article = db.session.query(Article).get(1) print(article.title) return article api.add_resource(ArticleView, '/1/', endpoint='article')
主程序注冊此藍圖
from article import article_bp ... app.register_blueprint(article_bp)
Flask-Restful渲染模板
先看看直接渲染模板是什么效果
class HelloView(Resource): def get(self): return render_template('hello.html') api.add_resource(HelloView, '/hello/', endpoint='hello')
可以發現這樣渲染出來的頁面並不是我們想要的。要想使用Flask-Restful渲染頁面還需要定義一個out_html函數
@api.representation('text/html') def out_html(data, code, headers): resp = make_response(data) return resp class HelloView(Resource): def get(self): return render_template('hello.html') api.add_resource(HelloView, '/hello/', endpoint='hello')
其中那個data就是頁面的內容