Web後端學習筆記 Flask (12)Restful

Restful API規範

restful API是用於在前端與後臺進行通信的一套規範,使用這個規範可以讓前後端開發變得更加輕鬆:

1. 協議:http或者https

2. 數據傳輸格式:json

3. url鏈接:url鏈接中,不能有動詞,只能有名詞,並且對於一些名詞,如果出現複數,就用複數的形式

4. http請求方法:

GET:在服務器上獲取資源

POST:在服務器上新創建一個資源

PUT:在服務器上更新資源(客戶端提供所有改變後的數據)

PATCH:在服務器上更新資源(只需提供需要改變的信息)

DELETE:從服務器上刪除資源

舉例:
GET/users/ :獲取所有用戶

POST/user/:創建一個用戶

GET/user/id/:根據id獲取一個用戶

PUT/user/id/:更新某個id的用戶信息(需要提供用戶的所=所有信息)

PATCH/user/id/:更新某個id的用戶信息(只需提供需要修改的數據)

DELETE/user/id/:刪除一個用戶

狀態碼:

狀態碼 原生描述 描述
200 OK 服務器成功響應客戶端請求
400 INVALID REQUEST 用戶發出的請求有誤,服務器沒有進行新建或者修改數據
401 Unauthorized 用戶沒有權限訪問這個請求
403 Forbidden 因爲某些原因禁止訪問這個請求
404 NOT FOUND 用戶請求的url不存在
406 NOT Acceptable 用戶請求不被服務器接受,例如服務器期望客戶端發送某個字段,但是沒有發送
500 Internal Server error 服務器內部錯誤,比如出現了bug

Restful插件:

通過 pip install flask-restful安裝flask-restful插件

定義restful視圖函數:如果使用flask-restful,那麼定義視圖函數的時候,需要繼承自flask-restful.Resource這個類,然後再根據當前請求的method來定義響應的方法。類似於類視圖的功能,可以分別定義get方法與post方法。

工具:postman做接口測試

from flask import Flask, render_template, request
from flask_restful import Api, Resource
import config

app = Flask(__name__)
app.config.from_object(config)

api = Api(app=app)


class LoginView(Resource):
    def post(self):
        return {"username": "tom"}


api.add_resource(LoginView, '/login/', endpoint='login')


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

注意事項:

1. endpoint是用來給url_for反轉url的時候使用的。如果不寫endpoint,那麼將會使用視圖的名字的小寫來作爲endpoint。

2. add_resource的第二個參數是訪問這個視圖函數的url,這個url和之前的route一樣,也可以傳遞參數,並且這個方法還可以傳遞多個url來指定這個視圖函數。

class LoginView(Resource):
    # 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
    # 此時即使url中沒有傳遞參數,也不會報錯
    def post(self, username=None):    
        return {"username": username}


api.add_resource(LoginView, '/login/<username>/', '/register/', endpoint='login')

注意事項:
1. 如果向返回json數據,那麼就使用flask-restful,如果向渲染模板,那麼好是採用視圖函數或者類視圖的方法。

2. url還是和之前的一樣,可以傳遞參數。不同的是視圖函數的是,可以綁定多個視圖函數。

flask-restful參數

flask-restful插件提供了類似於WTForms來驗證提交的數據是否合法,叫做reparse

class LoginView(Resource):
    # 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
    # 此時即使url中沒有傳遞參數,也不會報錯
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument("username", type=str, help="用戶名驗證錯誤")
        parse.add_argument("password", type=str, help="密碼驗證錯誤")
        args = parse.parse_args()
        return {"username": args.get("username"), "password": args.get("password")}


api.add_resource(LoginView, '/login/', endpoint='login')

在不傳參數的時候,返回的結果:

在post方法中傳遞參數之後,可以看到返回的結果:

add_argument可以指定的字段:
1. default: 當沒有傳遞相應參數的時候,設置這個參數的默認值

2. required:是否必須傳遞這一參數, 布爾值

3. type:指定提交的參數的類型,如果指定,那麼將使用指定的數據類型來強制轉換提交上來的值。

4. choices:選項,提交上來的值只有滿足這個選項中的值才能驗證通過

5. help:錯誤信息,如果驗證失敗,將會使用這個參數指定的值作爲錯誤信息

6. trim:是否要取出前後的空格

type字段的值,可以使用python內置的數據類型,也可以使用flask-restful.input中的類型,例如:

url:判斷傳入的參數是否爲url

regex:正則表達式

date:將這個字符串轉換爲datetime.date,如果轉換失敗,則會拋出異常

from flask import Flask, render_template, request
from flask_restful import Api, Resource, reqparse, inputs
import config

app = Flask(__name__)
app.config.from_object(config)

api = Api(app=app)


class LoginView(Resource):
    # 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
    # 此時即使url中沒有傳遞參數,也不會報錯
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument("username", type=str, help="用戶名驗證錯誤", trim=True)
        parse.add_argument("password", type=str, help="密碼驗證錯誤", trim=True)
        parse.add_argument("age", type=int, help="年齡驗證錯誤", trim=True)
        parse.add_argument("render", type=str, choices=["female", "male"], trim=True, help="性別填寫錯誤")
        parse.add_argument("link", type=inputs.url, trim=True, help="url填寫錯誤")
        parse.add_argument("phone", type=inputs.regex(r"1[3578]\d{9}"), trim=True, help="電話號碼格式錯誤")
        args = parse.parse_args()
        return {"username": args.get("username"),
                "password": args.get("password"),
                "age": args.get("age"),
                "render": args.get("render")}


api.add_resource(LoginView, '/login/', endpoint='login')


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

Flask-restful標準:
輸出字段: 

對於一個視圖函數,可以指定好一些字段用於返回。在可以使用ORM模型或者自定義模型的時候,它會自動獲取模型中的相應字段,生成json數據,然後再返回給客戶端。這其中需要導入flask_restful.marshal_with裝飾器,並且需要寫一個字典,來指示需要返回的字段,以及該字段的數據類型。 

from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config

app = Flask(__name__)
app.config.from_object(config)

api = Api(app=app)


class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return {"title": "xxx", "content": "yyy"}


api.add_resource(ArticleView, '/articles/', endpoint="articles")


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

有的字段沒有值,在return的字典中沒有寫,也會返回爲null值:

class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return {"title": "xxx"}    # 即使此時content字段沒有值,也會返回爲null,這樣就保證了接口比較規範


api.add_resource(ArticleView, '/articles/', endpoint="articles")

返回的結果:

這種方式還有一個好處,就是如果相應的數據是一個模型,則可以直接返回模型的對象即可,而不用構建字典

from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config

app = Flask(__name__)
app.config.from_object(config)

api = Api(app=app)


class Article(object):
    def __init__(self, title, content):
        self.title = title
        self.content = content


article = Article(title="gone with wind", content="xsddkkjsdv")


class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return article    # 可以根據article對象中的屬性自動構造字典,返回字典


api.add_resource(ArticleView, '/articles/', endpoint="articles")


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

複雜結構:

返回的字段的值任然是一個模型,則需要使用Nested()方法再定義一個字典,返回相應的字段

# 定義一個restful視圖
class ArticleView(Resource):
    resource_field = {
        "title": fields.String,
        "content": fields.String,
        "author": fields.Nested({     # User類型
            "username": fields.String,
            "email": fields.String
        }),
        "tags": fields.Nested({
            "name": fields.String
        })
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

使用field.Nested()之後,返回的的數據如下所示:

重命名屬性:

在讀取模型中的某個字段之後,需要用一個新的名字將其返回,可以使用attribute屬性實現:

例如,需要將article中的title屬性返回,並賦予一個新的屬性名:

# 定義一個restful視圖
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改變屬性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User類型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags")
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

返回結果:

默認值:

在resource_field中定義的一些字段,如果模型中沒有與之對應的字段,則該字段的值會被設置爲null返回。除此之外,還可以給沒有的字段設置默認值,最後返回的該字段的值就是默認值,而不是null.

class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改變屬性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User類型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

此時返回的read_cnt字段的值就是默認值100:

Flask-restful細節:

1. flask-restful結合藍圖使用

在flask-restful中使用藍圖,需要將藍圖定義到一個單獨的文件即可,此時在定義api的時候,以藍圖爲參數,而不用再以app作爲定義api的參數,在主app主註冊藍圖:

例如,定義藍圖的文件,articleViews.py

# -*- coding: utf-8 -*-

from flask import Blueprint
from flask_restful import Resource, fields, marshal_with, Api
from models import User, Article, Tag
from exts import db

article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp)      # api


# 定義一個restful視圖
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改變屬性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User類型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, "/<article_id>/", endpoint="article")

在主app.py文件中註冊藍圖:

from flask import Flask, render_template, request
import config
from exts import db
from models import User, Article, Tag
from articleViews import article_bp

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app=app)

app.register_blueprint(article_bp)   # 註冊藍圖


@app.route('/')
def index():
    user = User(username="kong", email="[email protected]")
    article = Article(title="gone", content="hover")
    article.author = user
    tag1 = Tag(name="java")
    tag2 = Tag(name="python")
    article.tags.extend([tag1, tag2])
    db.session.add(article)
    db.session.commit()
    return "Hello"


if __name__ == '__main__':
    app.run()

其中models文件爲定義ORM模型的文件:models.py

# -*- coding: utf-8 -*-
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)
    email = db.Column(db.String(50), nullable=False)


# tag和article之間是多對多的關係
article_tag = db.Table("article_tag",
                       db.Column("article_id", db.Integer, db.ForeignKey("article.id"), primary_key=True),
                       db.Column("tag_id", db.Integer, db.ForeignKey("tag.id"), primary_key=True),
                       )


class Article(db.Model):
    __tablename__ = "article"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text)

    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))

    author = db.relationship("User", backref="articles")
    tags = db.relationship("Tag", secondary=article_tag, backref="articles")


class Tag(db.Model):
    __tablename__ = "tag"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))





2. flask-restful渲染模板

falsk-restful規定的數據交互的形式是JSON,所以即使返回渲染的模板(及render_templlate(html文件)),此時在瀏覽器中顯示的還是字符串形式的,而不會當作html代碼被瀏覽器解析:此時需要藉助一個裝飾器,@api.representation("text/html")來定義一個函數,在這個函數中對html代碼進行封裝,再進行返回

# -*- coding: utf-8 -*-

from flask import Blueprint, render_template, make_response
from flask_restful import Resource, fields, marshal_with, Api
from models import User, Article, Tag
from exts import db

article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp)      # api


@api.representation("text/html")      # 保證restful也能夠通過render_template渲染模板
def output_html(data, code, headers):
    # data是字符串形式的html文本
    resp = make_response(data)    # 構造Response對象
    return resp


# 定義一個restful視圖
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改變屬性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User類型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


class ArticleListView(Resource):
    def get(self):
        return render_template("html/list.html")
        # 此時,restful返回的依然是字符串形式的html文本,不能被瀏覽器解析


api.add_resource(ArticleView, "/<article_id>/", endpoint="article")
api.add_resource(ArticleListView, '/list/', endpoint="list")

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章