Flask RESTful接口编写总结(一)
1、创建一个虚拟环境:
> mkdir myflask
> cd myflask
> virtualenv -p /usr/bin/python venv
> source venv/bin/activate
(venv)> python # 查看一下python版本,我使用mac自带的2.7.10
(venv)>>> exit()
2、创建应用目录:
> mkdir app
按照下面结构创建目录结构:
———— app
|—— __init__.py 创建工厂函数
|—— ext.py 插件实例化对象脚本
|—— config.py 配置脚本
|—— core/ 核心目录
|—— common/ 通用目录
|—— shared/ 共享目录
|—— resource/ 所有接口
|—— utils/ 工具库
...
3、实现项目基本运行
开始编写项目的入口创建flask对象实例的工厂函数(__init__.py):
首先安装flask:
(venv)> pip install flask
在__init__.py中:
from flask import Flask
def create_app(app_config='default'):
app = Flask(__name__)
return app
if __name__ == "__main__":
app = create_app()
app.run(debug=True)
这样最简单的一个flask应用就可以通过命令运行:
(venv)> python __init__.py
* Serving Flask app "__init__" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 183-285-309
但是这样启动完全没有体现我们工厂函数的价值,接下来就要在此基础上使用flask_script插件拓展功能:
首先安装flask_script:
(venv)> pip install flask_script
进到项目根目录(app的上层),创建一个manage.py文件,以后这个文件将成为我们管理flask应用的脚本:
(venv)> cd ..
(venv)> touch manage.py
注意:从这以后创建目录,文件以及安装插件的命令都不在给出!
在manage.py中:
# -*- coding: utf-8 -*-
import os
import sys
from flask_script import Manager
from app import create_app
# 设置默认编码为utf-8
# 关于python2的编码,我也踩过不少坑,下面这篇文章讲得不错
# https://blog.csdn.net/vickyrocker1/article/details/50952095
reload(sys)
sys.setdefaultencoding('utf-8')
app = create_app()
manager = Manager(app)
# 在shell模式下默认引入的上下文对象
@manager.shell
def make_shell_context():
return dict()
if __name__ == '__main__':
manager.run() # 启动
在此尝试执行命令:
python manage.py runserver -h 0.0.0.0 -p 5000
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
OK,又成功了,但是怎么会有一个WARNING呢?原来是我没有给flask应用提供相关配置,接下来开始配置应用:
进入app/config.py:
# -*- coding: utf-8 -*-
import os
# 取到根目录(../../myflask)
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 由于我们可能有多个配置,传入app_config
def load_config(app, app_config):
app.config.from_json(os.path.join(base_path, 'config/{}.json'.format(app_config)))
通过from_json这个方法导入配置,顾名思义需要一个json文件,方便管理,我们统一放在config文件夹下:
在config/default.json中:
{
"ENV": "development",
"TESTING": true,
"DEBUG": true,
"CSRF_ENABLED": true,
"SECRET_KEY": "you-will-never-guess"
}
具体配置参数请查看官方文档,这里不一一介绍。
然后在工厂函数create_app中添加:
load_config(app, app_config)
这样再次启动警告就会消失。
4、实现RESTful接口
进入app/resource目录,创建目录结构:
———— demo 以蓝本实例划分的业务模块
|———— __init__.py 创建蓝本实例
|———— greet 以Api实例划分的任务模块
|—— view.py 视图逻辑
|—— url.py 路由注册
|—— __init__.py 创建Api实例
...
...
在demo/__init__.py中:
# -*- coding: utf-8 -*-
from flask import Blueprint
# 默认使用当前模块的文件夹名作为蓝本名称
blueprint = Blueprint(__name__.split('.')[-1], __name__)
# 注册路由到蓝本
from greet import url as greet_url
在demo/greet/__init__.py中:
# -*- coding: utf-8 -*-
from flask_restful import Api
from .. import blueprint
# 默认使用当前模块的文件夹名作为蓝本名称
api = Api(blueprint, prefix='/' + __name__.split('.')[-1])
在demo/greet/view.py中创建HelloResource接口:
# -*- coding: utf-8 -*-
from flask_restful import Resource
class HelloResource(Resource):
def get(self, name):
return 'hello {}!'.format(name)
在demo/greet/url.py中:
from . import api
from view import *
api.add_resource(HelloResource, '/hello/')
之后我们需要在工厂函数中添加:
from resource.demo import blueprint as demo_blueprint
app.register_blueprint(demo_blueprint, url_prefix='/' + demo_blueprint.name)
这样就把api->blueprint->app一层层向上绑定,再次启动项目,访问浏览器 http://0.0.0.0:5000/demo/greet/hello/world 收到响应:
"hello world!"
至此我们完成了从基本架构搭建到响应简单http请求等任务,接下来围绕处理请求,返回响应为核心,我们需要提供更多功能,包括以下方面:
1、请求参数的序列化解析/校验
2、响应(对象->字典)的序列化
3、错误请求的处理
4、异常的处理和记录
5、数据库的增删改查(核心、核心、核心,重要的事情说三遍)
5、实现输入输出的序列化
我使用到了webargs插件和flask_restful marsh_with函数分别定义输出输出字段
新建一个接口:
class WhoAreYouResource(Resource):
@req_in({
'name': _in.String(required=True),
'gender': _in.String(required=True),
'age': _in.Integer(required=True)
})
@req_out({
'name': _out.String(),
'gender': _out.String(),
'age': _out.Integer()
})
def post(self, params):
return params
在头部导入相关包:
from flask_restful import Resource, fields as _out, marshal_with as req_out
from webargs import fields as _in
from webargs.flaskparser import use_args as req_in
这里需要注意的是使用webargs的use_args装饰器,需要给应用绑定一个异常处理函数:
from webargs.flaskparser import parser
@parser.error_handler
def handle_request_parsing_error(error, request, schema):
abort(status.UNPROCESSABLE_ENTITY, message=error.messages)
而且查看它的源码:
if as_kwargs:
kwargs.update(parsed_args)
return func(*args, **kwargs)
else:
# Add parsed_args after other positional arguments
new_args = args + (parsed_args,)
return func(*new_args, **kwargs)
会把序列化的结果作为一个字典传递给函数定位参数的最后一个(路由参数在kwargs中):
# 路由(../)有参数id的情况下
@req_in(...)
def get(self, params, id):
pass
并且,输入输出的序列化都支持嵌套:
class WhoAreYouResource(Resource):
@req_in({
'name': _in.String(required=True),
'gender': _in.String(required=True),
'age': _in.Integer(required=True),
'deskmate': _in.Nested({
'name': _in.String(required=True),
'gender': _in.String(required=True),
'age': _in.Integer(required=True)
})
}, locations=('json',)) # 指定取参的位置
@req_out({
'name': _out.String(),
'gender': _out.String(),
'age': _out.Integer(),
'deskmate': _out.Nested({
'name': _out.String(),
'gender': _out.String(),
'age': _out.Integer()
})
})
def post(self, params):
return params
就算嵌套字段的缺失,报错提示信息也非常直观:
{
"message": {
"deskmate": {
"age": ["Missing data for required field."]
}
}
}
接下来进行参数的校验,参数校验只会发生在输入,输出往往由内部逻辑保证准确性和合法性,一般不需要再次校验。
对于需要校验的字段,可以指定它的validate属性:
'name': _in.String(required=True, validate=lambda val: len(val.decode('utf-8)) >= 8) # 用户名长度大于等于8
还可以指定为函数,只要返回结果为False或者触发ValidationError都意味着校验失败,错误信息默认为"Invalid value."。
既然遇到了错误请求,那么我们来处理这一问题:
首先修改flask_restful abort函数的功能,血的教训告诉我,只打印错误信息,之后来排错必定会无所适从,
我们需要打印错误的堆栈信息,准确定位错误代码:
在app/core/error.py中:
# -*- coding: utf-8 -*-
import sys
import traceback
from flask import current_app
from flask_restful import abort as rest_abort
def abort(http_status_code, **kwargs):
"""
封装flask_restful的abort函数
仅当开发环境时,接口返回详细错误信息
"""
exc = traceback.format_exc()
if not exc.startswith('None'):
current_app.logger.exception(sys.exc_info())
if current_app.config.get('ENV') == 'development':
kwargs.setdefault('detail', exc)
rest_abort(http_status_code, **kwargs)
这样开发环境下,错误返回会携带detail字段,说明错误的堆栈信息。
目前我们只在控制台打印出来了日志,实际生产环境中,我们还需要日志文件来定位问题:
在app/logger.py中创建函数add_file_logger:
def add_file_logger(app):
handler = TimedRotatingFileHandler(app.config['LOG_FILE_PATH'], 'D', 1, 7, None, False, False)
handler.setLevel(logging.DEBUG)
handler.setFormatter(
logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s'))
app.logger.addHandler(handler)
在app/config.py中添加:
log_dir_path = os.path.join(base_path, 'logs')
make_dirs(log_dir_path)
log_file_path = os.path.join(log_dir_path, time.strftime('%Y-%m-%d', time.localtime(time.time())) + '.log')
app.config['LOG_FILE_PATH'] = log_file_path
add_file_logger(app)
这样根目录下会根据日期生成日志:
———— logs
|—— 2018-12-25.log
...
项目地址:https://github.com/dollphis/myyyyflask (commit: 1e0c28fcc233f975fc2e427fed7ce08491e61ab9)
未完待续