Flask-RESTFul API

版权声明:FatPuffer https://blog.csdn.net/qq_42517220/article/details/88847732

使用 pip 安装 Flask-RESTful:

pip install flask-restful

开发的版本可以从 GitHub 上的页面 下载

git clone https://github.com/twilio/flask-restful.git

cd flask-restful

python setup.py develop

一个最小的 API

from flask import Flask
from flask_restful import Api, Resource


app = Flask(__name__)
api = Api(app)


class HellowWOrld(Resource):
    def get(self):
        return {"hello": "world"}


api.add_resource(HellowWOrld, '/')


if __name__ == "__main__":
    app.run(debug=True)

在这里插入图片描述
在这里插入图片描述

资源丰富的路由

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


app = Flask(__name__)
api = Api(app)

todos = {}


class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}

    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}


api.add_resource(TodoSimple, '/<string:todo_id>')

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

在这里插入图片描述
在这里插入图片描述
通过requests库发送请求
在这里插入图片描述

设置响应状态码以及响应头

from flask import Flask
from flask_restful import Api, Resource


app = Flask(__name__)
api = Api(app)


class Todo1(Resource):
    def get(self):
    	# 默认响应状态码为200
        return {"task": 'Hello World'}
    
    
class Todo2(Resource):
    def get(self):
    	# 修改状态码为201
        return {"task": "Hello World"}, 201
    

class Todo3(Resource):
    def get(self):
    	# 添加响应头信息
        return {"task": "Hello World"}, 201, {"Python": "Flask", "python": "Tornado"}


api.add_resource(Todo1, '/todo1')
api.add_resource(Todo2, '/todo2')
api.add_resource(Todo3, '/todo3')

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

在这里插入图片描述

请求参数解析

  • 验证请求参数,类似于flask中的WTF扩展表单验证
from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name', type=str)
args = parser.parse_args()
# coding:utf-8

from flask import Flask, request
from flask_restful import Api, Resource, reqparse


app = Flask(__name__)
api = Api(app)

# 创建请求参数解析对象
parser = reqparse.RequestParser()
# 添加验证字段,以及验证信息
parser.add_argument('uname', type=str, help='is must str')
parser.add_argument('password', type=str, required=True, help='is must exists')
parser.add_argument('age', type=int, help='is must int')


class Register(Resource):
    def get(self):
        return "welcome come to register page"

    def post(self):
        data = parser.parse_args(strict=True)
        return data


api.add_resource(Register, '/register')

if __name__ == "__main__":
    app.run(debug=True)

在这里插入图片描述

限制该字段不能为空

  • 只需要添加 required=True
parser.add_argument('name', type=str, required=True, help="Name cannot be blank!")

接受该字段可以有多个值&列表

  • action='append'
parser.add_argument('name', type=str, action='append')
curl http://api.example.com -d "Name=bob" -d "Name=sue" -d "Name=joe"

args = parser.parse_args()
args['name']    # ['bob', 'sue', 'joe']

设置请求参数来源(url、表单、请求头、cookie…)

# 表单数据
parser.add_argument('name', type=int, location='form')

# url数据
parser.add_argument('PageSize', type=int, location='args')

# 请求头数据
parser.add_argument('User-Agent', type=str, location='headers')

# cookies数据
parser.add_argument('session_id', type=str, location='cookies')

# 多媒体文件
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')

多个位置参数

  • 列表中最后一个优先出现在结果集中。(例如:location=[‘headers’, ‘values’],解析后 ‘values’ 的结果会在 ‘headers’ 前面)
parser.add_argument('text', location=['headers', 'values'])

继承解析

  • 往往你会为你编写的每个资源编写不同的解析器。这样做的问题就是如果解析器具有共同的参数。不是重写,你可以编写一个包含所有共享参数的父解析器接着使用 copy() 扩充它。你也可以使用 replace_argument() 覆盖父级的任何参数,或者使用 remove_argument() 完全删除参数。 例如:
from flask.ext.restful import RequestParser

parser = RequestParser()
parser.add_argument('foo', type=int)

parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)

# parser_copy has both 'foo' and 'bar'

parser_copy.replace_argument('foo', type=str, required=True, location='json')
# 'foo' is now a required str located in json, not an int as defined
#  by original parser

parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument

输出字段

(1)基本用法

  • 你可以定义一个字典或者 fields 的 OrderedDict 类型,OrderedDict 类型是指键名是要呈现的对象的属性或键的名称,键值是一个类,该类格式化和返回的该字段的值。下面例子有三个字段,两个是字符串(Strings)以及一个是日期时间(DateTime),格式为 RFC 822 日期字符串(同样也支持 ISO 8601)。
from flask_restful import Resource, fields, marshal_with

resource_fields = {
    'name': fields.String,
    'address': fields.String,
    'date_updated': fields.DateTime(dt_format='rfc822'),
}

class Todo(Resource):
    @marshal_with(resource_fields, envelope='resource')
    def get(self, **kwargs):
        return db_get_todo()  # Some function that queries the db
  • 这个例子假设你有一个自定义的数据库对象(todo),它具有属性:nameaddress, 以及 date_updated。该对象上任何其它的属性可以被认为是私有的不会在输出中呈现出来。一个可选的 envelope 关键字参数被指定为封装结果输出。
  • 装饰器 marshal_with 是真正接受你的数据对象并且过滤字段。marshal_with 能够在单个对象,字典,或者列表对象上工作。
  • 注意:marshal_with 是一个很便捷的装饰器,在功能上等效于如下的 return marshal(db_get_todo(), resource_fields), 200。这个明确的表达式能用于返回 200 以及其它的 HTTP 状态码作为成功响应。
    (2)示例演示
# coding:utf-8

from flask import Flask, request
from flask_restful import Api, Resource, reqparse, fields, marshal_with
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand


app = Flask(__name__)
api = Api(app)


class Config(object):
    """配置参数"""
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/db_flask"
    # 设置sqlalchemy自动跟踪数据库
    SQLALCHEMY_TRACK_MODIFICATIONS = True

    SECRET_KRY = '#%*(_)?./DFVDjnd34534'


app.config.from_object(Config)

# 创建数据库sqlalchemy工具对象
db = SQLAlchemy(app)


# 创建flask脚本管理工具对象
manager = Manager(app)

# 创建数据库迁移工具对象
Migrate(app, db)

# 向manager对象中添加数据库操作命令
manager.add_command('db', MigrateCommand)


# 创建请求参数解析对象
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, help='is must str')
parser.add_argument('password', type=str, required=True, help='is must exists')
parser.add_argument('id', type=int, help='is must int')

# 对象输出属性
resource_fields = {
    'id': fields.String,
    'name': fields.String,
    'email': fields.String
}


# 创建数据库模型类
class Role(db.Model):
    """用户角色表"""
    __tablename__ = 'tbl_roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True)
    # 需要手动添加,方便使用Role.user查询用户对象,user列不是真实存在的,backref="role"为了方便通过User.role获取角色对象,
    # 因为使用User.role_id只能获取到角色id,要想获取角色对象,还需要再在Role表中查询一次
    users = db.relationship("User", backref="role")

    def __repr__(self):
        """定义之后,可以让显示对象的时候更直观,类似于Django中的__str__"""
        return "Rloe object: name=%s" % self.name


class User(db.Model):
    """用户表"""
    __tablename__ = 'tbl_users'  # 指明数据库表名
    id = db.Column(db.Integer, primary_key=True)  # 整型主键,会默认设置为自增主键
    name = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(128), unique=True)
    password = db.Column(db.String(128), nullable=False)  # nullable=False 参数必须传
    role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))


class Users(Resource):
    @marshal_with(resource_fields, envelope='users')
    def post(self):
        args = parser.parse_args()
        id = args.get('id')
        name = args.get('name')
        password = args.get('password')
        user = User.query.filter_by(name=name, password=password).all()
        return user


api.add_resource(Users, '/users')

if __name__ == "__main__":
    app.run(debug=True)

  • 运行项目
    在这里插入图片描述
  • 发起post请求
    在这里插入图片描述
  • 我们将返回结果格式化在解释
    在这里插入图片描述
  • 这是我们定义的输出内容
    在这里插入图片描述
  • 这是数据库对象属性
    在这里插入图片描述
  • 视图返回
    在这里插入图片描述
    如果不定义输出字段,则只会返回该用户对象,定义了输出字段resource_fields后,使用@marshal_with(resource_fields, envelope='users'),则会返回resource_fields内定义了的字段属性,envelope:即你想要用户看到的对象,我们上述示例中因为获取的是user对象信息,所以我定义为users,在返回结果中看到的users即我们此处定义的。

(3)重命名属性

  • 很多时候你面向公众的字段名称是不同于内部的属性名。使用 attribute 可以配置这种映射。
fields = {
    'name': fields.String(attribute='private_name'),
    'address': fields.String,
}
  • lambda 也能在 attribute 中使用
fields = {
    'name': fields.String(attribute=lambda x: x._private_name),
    'address': fields.String,
}

(4)默认值

  • 如果由于某种原因你的数据对象中并没有你定义的字段列表中的属性,你可以指定一个默认值而不是返回 None。
fields = {
    'name': fields.String(default='Anonymous User'),
    'address': fields.String,
}

(5)自定义字段&多个值

class UrgentItem(fields.Raw):
    def format(self, value):
        return "Urgent" if value & 0x01 else "Normal"

class UnreadItem(fields.Raw):
    def format(self, value):
        return "Unread" if value & 0x02 else "Read"

fields = {
    'name': fields.String,
    'priority': UrgentItem(attribute='flags'),
    'status': UnreadItem(attribute='flags'),
}

(6)Url & 其它具体字段

  • Flask-RESTful 包含一个特别的字段,fields.Url,即为所请求的资源合成一个 uri。这也是一个好示例,它展示了如何添加真正在你的数据对象中存在的数据到你的响应中
class RandomNumber(fields.Raw):
    def output(self, key, obj):
        return random.random()

fields = {
    'name': fields.String,
    # todo_resource is:访问视图时的路由
    'uri': fields.Url('todo_resource'),
    'random': RandomNumber,
}
  • 示例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 默认情况下,fields.Url 返回一个相对的 uri。为了生成包含协议(scheme),主机名以及端口的绝对 uri,需要在字段声明的时候传入 absolute=True。传入 scheme 关键字参数可以覆盖默认的协议(scheme):
fields = {
    'uri': fields.Url('todo_resource', absolute=True)
    'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}
  • 示例
    在这里插入图片描述
    在这里插入图片描述

(7)复杂结构

  • 你可以有一个扁平的结构,marshal_with 将会把它转变为一个嵌套结构
>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['zip'] = fields.String
>>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'
  • 注意:address 字段并不真正地存在于数据对象中,但是任何一个子字段(sub-fields)可以直接地访问对象的属性,就像没有嵌套一样。

(8)列表字段

  • 你也可以把字段解组(unmarshal)成列表
>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'

(10)高级:嵌套字段

  • 尽管使用字典套入字段能够使得一个扁平的数据对象变成一个嵌套的响应,你可以使用 Nested 解组(unmarshal)嵌套数据结构并且合适地呈现它们。
>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> address_fields = {}
>>> address_fields['line 1'] = fields.String(attribute='addr1')
>>> address_fields['line 2'] = fields.String(attribute='addr2')
>>> address_fields['city'] = fields.String(attribute='city')
>>> address_fields['state'] = fields.String(attribute='state')
>>> address_fields['zip'] = fields.String(attribute='zip')
>>>
>>> resource_fields = {}
>>> resource_fields['name'] = fields.String
>>> resource_fields['billing_address'] = fields.Nested(address_fields)
>>> resource_fields['shipping_address'] = fields.Nested(address_fields)
>>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2}
>>>
>>> json.dumps(marshal_with(data, resource_fields))
'{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'

扩展 Flask-RESTful

默认的API仅支持JSON格式内容,我们可以通过扩展,使其可以支持更多类型数据@api.representation

  • 下面表示函数必须返回一个 Flask Response 对象。
app = Flask(__name__)
api = restful.Api(app)

@api.representation('application/json')
def output_json(data, code, headers=None):
    resp = make_response(json.dumps(data), code)
    resp.headers.extend(headers or {})
    return resp

@api.representation('application/xml')
def xml(data, code, headers):
    resp = make_response(convert_data_to_xml(data), code)
    resp.headers.extend(headers)
    return resp

(1)自定义字段 & 输入

  • 自定义输出字段让你无需直接修改内部对象执行自己的输出格式。所有你必须做的就是继承 Raw 并且实现 format() 方法:
  • 下面示例:将用户定义的字段name改为大写NAME输出
class AllCapsString(fields.Raw):
    def format(self, value):
        return value.upper()


# example usage
fields = {
    'name': fields.String,
    'all_caps_name': AllCapsString(attribute=name),
}

(2)输入

  • 对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。
  • 请求参数解析RequestParser 中使用
parser = reqparse.RequestParser()
parser.add_argument('OddNumber', type=odd_number)
parser.add_argument('Status', type=task_status)
args = parser.parse_args()
  • 对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。
def odd_number(value):
    if value % 2 == 0:
        raise ValueError("Value is not odd")

    return value
  • 请求解析器在你想要在错误消息中引用名称的情况下将也会允许你访问参数的名称(OddNumber)
def odd_number(value, name):
    if value % 2 == 0:
        raise ValueError("The parameter '{}' is not odd. You gave us the value: {}".format(name, value))

    return value
  • 你还可以将公开的参数转换为内部表示(根据用户请求value返回列表中对应数据)
# 'init' => 0
# 'in-progress' => 1
# 'completed' => 2

def task_status(value):
    statuses = [u"init", u"in-progress", u"completed"]
    return statuses.index(value)

(3)响应格式

  • 为了支持其它的表示(像 XML,CSV,HTML),你可以使用 representation() 装饰器。你需要在你的 API 中引用它。
api = restful.Api(app)

@api.representation('text/csv')
def output_csv(data, code, headers=None):
    pass
    # implement csv output!
  • 这些输出函数有三个参数,data,code,以及 headers。
  • data 是你从你的资源方法返回的对象
  • code 是预计的 HTTP 状态码
  • headers 是设置在响应中任意的 HTTP 头。你的输出函数应该返回一个 Flask 响应对象。
def output_json(data, code, headers=None):
    """Makes a Flask response with a JSON encoded body"""
    resp = make_response(json.dumps(data), code)
    resp.headers.extend(headers or {})

    return resp

另外一种实现这一点的就是继承 Api 类并且提供你自己输出函数。

class Api(restful.Api):
    def __init__(self, *args, **kwargs):
        super(Api, self).__init__(*args, **kwargs)
        self.representations = {
            'application/xml': output_xml,
            'text/html': output_html,
            'text/csv': output_csv,
            'application/json': output_json,
        }

(4)自定义错误处理器

app = Flask(__name__)
api = flask_restful.Api(app, catch_all_404s=True)
  • Flask-RESTful 会处理除了自己路由上的错误还有应用程序上所有的 404 错误。
  • 有时候你想在发生错误的时候做一些特别的东西 - 记录到文件,发送邮件,等等。使用 got_request_exception() 方法把自定义错误处理加入到异常。
def log_exception(sender, exception, **extra):
    """ Log an exception to our logging framework """
    sender.logger.debug('Got exception during processing: %s', exception)

from flask import got_request_exception
got_request_exception.connect(log_exception, app)

(5)定义自定义错误消息

  • 在一个请求期间遇到某些错误的时候,你可能想返回一个特定的消息以及/或者状态码。你可以告诉 Flask-RESTful 你要如何处理每一个错误/异常,因此你不必在你的 API 代码中编写 try/except 代码块。
errors = {
    'UserAlreadyExistsError': {
        'message': "A user with that username already exists.",
        'status': 409,
    },
    'ResourceDoesNotExist': {
        'message': "A resource with that ID no longer exists.",
        'status': 410,
        'extra': "Any extra information you want.",
    },
}
  • 包含 ‘status’ 键可以设置响应的状态码。如果没有指定的话,默认是 500,一旦你的 errors 字典定义,简单地把它传给 Api 构造函数
app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)

猜你喜欢

转载自blog.csdn.net/qq_42517220/article/details/88847732