23、Flask實戰第23天:Flask-Restful


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'))
)
models

插入測試數據

...
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就是頁面的內容

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM