flask 之(六) --- API|RestfulApi

接口概念

  IOP:面向接口编程,不再关注具体的实现;只关注输入、输出。

  http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html

服务器返回数据类型:

  网页数据html,为浏览器使用

  Json数据,ajax javascript发请求的一种方式;也可以使用request的python请求方式

为移动端编写接口关注:

  接口地址是什么:/xxx/yyy/zzz

  接口需要什么参数:参数根据业务场景

  返回什么格式的数据:大多数是json数据  

RestfulAPI:

    一种软件架构风格、设计风格、而不是标准,只是提供了一组设计原则和约束条件。它主要用户客户端和服务器交互类的软件。

  基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。REST全程是Representational State Transfer,表征性状态转移。

  首次在2000年Roy Thomas Fielding的博士论文中出现,Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者,

  Apache服务器软件的作者之一,Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了广泛的关注。

理解RESTful

介绍:https://github.com/RockTeach/PythonCourse/blob/master/web/flask/restful.md

  要理解RESTful架构,最好的就是去理解它的单词 Representational State Transfer 到底是什么意思,它的每一个词到底要表达什么。

  REST的释义,"(资源的)表现层状态转化",其实这省略了主语。“表现层”其实指的是“资源(Resource)”的“表现层”。

状态码

  服务器向用户返回的状态码和提示信息,常见的有以下一些地方

  • 200:OK - [GET]:服务器成功返回用户请求的数据
  • 201:CREATED -[POST/PUT/PATCH]:用户新建或修改数据成功
  • 202:Accepted - [*] :表示一个请求已经进入后台排队(异步任务)
  • 204:NO CONTENT - [DELETE]:表示数据删除成功
  • 400:INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误
  • 401:Unauthorized - [*] :表示用户没有权限(令牌,用户名,密码错误)
  • 403:Forbidden - [*]:表示用户得到授权,但是访问是被禁止的
  • 404:NOT FOUND - [*]:用户发出的请求针对的是不存在的记录
  • 406:Not Acceptable - [*]:用户请求格式不可得
  • 410:Gone - [GET] :用户请求的资源被永久移除,且不会再得到的
  • 422:Unprocesable entity -[POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误
  • 500:INTERNAL SERVER EROR - [*] :服务器内部发生错误

资源(Resource)

  所谓“资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。

  它可以是一段文本,一张图片,一首歌曲,一种服务,总之就是一个具体的实例。

  你可以使用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。

  要获取这个资源,访问它的URI就可以了,因此URI就成了每一个资源的地址或独一无二的识别符。所谓“上网”就是与互联网上一系列的“资源”互动,调用它们的URI。

表现层(Representation)

  “资源”是一种信息实体,它可以有多种外在表现形式。我们把“资源”具体呈现出来的形式,叫做它的”表现层“(Representation)。

  URI只代表资源的实体,不代表它的形式。严格地说,有些网站最后的”.html“后缀名是不必要的,因为这个后缀表示格式,属于”表现层“范畴,而URI应该只代表”资源“的位置。

  它的具体表现形式,应该在HTTP请求头的信息中使用Accept和Content-Type字段指定。

状态转换(State Transfer)

  访问一个网站,就代表客户端和服务端的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

  互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务端。

  因此,如果客户端想要操作服务器,就必须通过某种手段,让服务器端发生”状态转换(State Transfer)“。

  而这种转换是建立在表现层之上的,所以就是”表现层状态转化“。

  客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议中,四个表示操作方式的动词:GET,POST,PUT,DELETE。

  它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可用于更新资源),PUT用来更新资源,DELETE用来删除资源

总结 :

  1. 每一个URI代表一种资源
  2. 客户端和服务器之间,传递这种资源的某种表现层
  3. 客户端通过四个HTTP动词,对服务端资源进行操作,实现”表现层状态转换“
  4. 同一个url针对用户的不同的请求操作,表现出来的状态是不同的。表现出来的多种形式,就是表现层状态转换。
  • RESTful是软件架构设计思想。使用在CS,客户端和服务端这种架构模型中。

  • 表现层状态转换

    • 主语 (资源)

    • URI 每个URI代表一种资源

    • 资源展现给我们的形式就叫做表现层

    • 通过HTTP的请求谓词来实现表现层转换

  • 重要概念

    • URI、HTTP请求谓词、JSON

注意:

  postman/前端 在向服务器提交json数据时,需要声明提交的类型。在postman的请求的headers增加content-type:application/json。

  flask 在确认请求数据是通过json提交后,会将json字符产转换成 字典。保存在request.json中

FBV简单体验:

  • views.py
 1 from flask import Blueprint, request
 2 
 3 restful_bp = Blueprint("restful_bp",__name__)
 4 """
 5 动作     URL              状态码      数据库操作       含义
 6 GET     /posts             200        SELECT        从 blog_posts 表中查询一组数据 
 7 POST    /posts             201        INSERT        向 blog_posts 表中插入一条数据
 8 
 9 GET     /posts/123         200        SELECT        从 blog_posts 表中查询 id 为123 的记录
10 PUT     /posts/123         200        UPDATE        更新 blog_posts 表中 id 为123 的记录(请求时提供全部字段的更新)
11 PATCH   /posts/123         200        UPDATE        更新 blog_posts 表中 id 为123 的记录(请求时提供部分字段的更新)
12 DELETE  /posts/123         204        DELETE        删除 blog_posts 表中 id 为123 的记录
13 """

14 @restful_bp.route("/posts",methods = ["POST","GET"]) 15 def post_list(): 16 if request.method == "POST": 17 return "向 blog_posts 表中插入一条数据",201 # 201 表示创建成功 18 elif request.method == "GET": 19 return "从 blog_posts 表中查询一组数据",200 20 21 22 @restful_bp.route("/posts/<post_id>",methods=["GET","POST","PUT","PATCH","DELETE"]) 23 def post_detail(post_id): 24 if request.method == "GET": 25 return "从 blog_posts 表中查询 id 为{} 的记录".format(post_id),200 26 elif request.method == "PUT": 27 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供全部字段的更新)".format(post_id),200 28 elif request.method == "PATCH": 29 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供部分字段的更新)".format(post_id),200 30 elif request.method == "DELETE": 31 return "删除 blog_posts 表中 id 为{} 的记录".format(post_id),204 # 204 表示删除成功
  •  models.py
 1 from flask_sqlalchemy import SQLAlchemy
 2 
 3 db = SQLAlchemy()
 4 
 5 class Channel(db.Model):
 6     __tablename__ = "channels"
 7 
 8     id = db.Column(db.Integer,primary_key=True)
 9     name = db.Column(db.String(16),unique=True,nullable=False)
10     sort = db.Column(db.Integer,nullable=False)
11     # 对象方法。将类的对象形式转化成字典形式
12     def to_dict(self):
13         return {
14             'id': self.id,
15             'name': self.name,
16             'sort': self.sort,
17         }

FBV实现:创建POST | 查询GET

  • views.py
    • POST:http://127.0.0.1:5000/channel_dict
    • GET:http://127.0.0.1:5000/channel_dict
 1 @restful_bp.route('/channel_dict/',methods=["GET","POST"])
 2 def channel_list():
 3     if request.method == 'POST':
 4         # # 结果:b'{\n\t"\xe5\x90\x8d\xe5\xad\x97":"\xe7\xa7\x91\xe6\x8a\x80",\n\t"sort":1\n}'
 5         # # 需在postman中设置一下请求头headers。Content-Type:application/json
 6         # print(request.data)
 7         # # 结果:None
 8         # print(request.json)
 9         if 'name' not in request.json or 'sort' not in request.json:
10             return abort(400)
11 
12         channel = Channel()
13         channel.name = request.json['name']
14         channel.sort = request.json['sort']
15 
16         db.session.add(channel)
17         db.session.commit()
18 
19         return jsonify({'channel':channel.to_dict()}),201
20 
21     elif request.method == "GET":
22         # 数据库查询的返回的是list类型的数据
23         _channels = Channel.query.all()
24         # 将数据库中查询出的对象 转化成 字典形式
25         channels = [channel.to_dict() for channel in _channels]
26         # flask视图函数只能返回 str 或 response 对象。可以将数据库查询出来的转化成json,以满足flask视图函数的返回要求
27         ret = {
28             'channels':channels
29         }
30         # import json
31         # json_str = json.dumps(ret)
32         # return json_str
33         return jsonify(ret)

FBV实现:GET | PUT | PATCH | DELETE

  • views.py:http://127.0.0.1:5000/channels/2
 1 @restful_bp.route("/channels/<channel_id>",methods=["GET","POST","PUT","PATCH","DELETE"])
 2 def channel_detail(channel_id):
    # 根据条件查询对象
3 channel = Channel.query.get(channel_id) 4 5 # 获取信息:http://127.0.0.1:5000/channel 6 if request.method == "GET": 7 return jsonify({'channel':channel.to_dict()}),200 8 9 # 更新数据:全量更新 10 elif request.method == "PUT": 11 if 'name' not in request.json or 'sort' not in request.json: 12 return abort(400) 13 channel.name = request.json['name'] 14 channel.sort = request.json['sort'] 15 db.session.add(channel) 16 db.session.commit() 17 return jsonify({'channel':channel.to_dict()}),200 18 19 # 更新数据:差量更新 20 elif request.method == 'PATCH': 21 # name = channel.name 22 # if 'name' in request.json: 23 # name = request.json['name'] 24 # channel.name = name 25 # 等价于 上四行代码。如果有数据更新就更新{先从json中获取数据,如果有就赋值给channel.name},没有数据更新就用原来的当做默认值 26 channel.name = request.json.get('name',channel.name) 27 channel.sort = request.json.get('sort',channel.sort) 28 db.session.commit() 29 return jsonify({'channel':channel.to_dict()}),200 30 31 # 删除数据。删除成功后返回的是空数据。 32 elif request.method == "DELETE": 33 db.session.delete(channel) 34 db.session.commit() 35 return "",204 

Flask-RestfulApi

框架优点:

  • 会让代码逻辑更加清晰,不会再有过多的if-else语句
  • 框架会提供参数的验证。像form一样提供验证数据的功能
  • 框架会提供丰富的输出格式,自定义输出的结构

框架注意:⚠️

  1. flask-restful 返回字典。框架内部自动转换为json。return {'channels':channels},200
  2. 如果在扩展文件ext.py实例化和注册路由资源,必须在加载时就注册好,不可以在调用函数懒加载时注册
  3. 根据请求的同名方法执行同名的视图函数,来完成不同的请求。request.method.lower() 获取请求方式然后将其变成小写

CBV简单体验:pip install Flask-RESTful   

    # method = request.method
    # method = lower(method)
    # 获取请求方式后将其转化为小写,然后去类中匹配,如果有同名的函数,就执行相应的请求方法函数

# Resource 是一个资源类,其实就是一个url
# Resource 的父类MethodView是flask自己的views中的一个类,不同Resource我们自己也可以完成各种请求。
# Resource 的父类MethodView也是根据不同的请求方法执行不同的请求函数,来完成各种请求操作
# 源码变小写 meth = getattr(self, request.method.lower(), None)。getattr是从一个实例中将类中的一个方法取出来赋值给一个变量,执行这个变量就是执行此函数方法

CBV类视图

 1 from flask import Flask
 2 from flask_restful import Api, Resource
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
6 """ 7 动作 URL 状态码 数据库操作 含义 8 GET /posts 200 SELECT 从 blog_posts 表中查询一组数据 9 POST /posts 201 INSERT 向 blog_posts 表中插入一条数据 10 """ 11 class PostList(Resource): 12 """ 13 flask-restful 的所有请求是通过 类 来处理的。继承自Resource类。 14 flask-restful 框架内部会自动根据请求的HTTP METHOD调用同名的实例方法 15 GET: /posts -> def get(self): pass 16 POST:/posts -> def post(self): pass 17 """ 18 def get(self): 19 return "从 blog_posts 表中查询一组数据",200 20 21 def post(self): 22 return "向 blog_posts 表中插入一条数据",201 23 24 25 """ 26 动作 URL 状态码 数据库操作 含义 27 GET /posts/123 200 SELECT 从 blog_posts 表中查询 id 为123 的记录 28 PUT /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供全部字段的更新) 29 PATCH /posts/123 200 UPDATE 更新 blog_posts 表中 id 为123 的记录(请求时提供部分字段的更新) 30 DELETE /posts/123 204 DELETE 删除 blog_posts 表中 id 为123 的记录 31 """ 32 class PostDetail(Resource): 33 """ 34 flask-restful 框架内部也支持 URL 上配置的路由参数,路由参数会传递到具体处理请求的实例方法中 35 """ 36 def get(self,post_id): 37 return "从 blog_posts 表中查询 id 为{} 的记录".format(post_id),200 38 39 def put(self,post_id): 40 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供全部字段的更新)".format(post_id),200 41 42 def patch(self,post_id): 43 return "更新 blog_posts 表中 id 为{} 的记录(请求时提供部分字段的更新)".format(post_id),200 44 45 def delete(self,post_id): 46 return "删除 blog_posts 表中 id 为{} 的记录".format(post_id),204 47 48 49 # 路由配置。参数[前两个参数必填]:(resource类(资源类)、访问的url地址、endpoint="相当于一个路由名称 如:blue.login。可以在url_for中使用") 50 api.add_resource(PostList,'/posts',endpoint='post_list') # 如果不定义endpoint参数,默认endpoint的值是 类名 51 api.add_resource(PostDetail,'/posts/<int:post_id>',endpoint='post_detail') 52 53 if __name__ == '__main__': 54 app.run(debug=True)
 1 # 非restfulAIPI中不用装饰器的写法
 2 # @app.route("/")
 3 def index():
 4     return "Index"
 5 
 6 # 参数:路由、描述、资源
 7 app.add_url_rule("/index/","index",index)
 8 
 9 if __name__ == '__main__':
10     app.run(debug=True)
非API非装饰器写法

简单拆分格式:

  • manage.py、ext.py、models.py
from flask_script import Manager
from flask_migrate import MigrateCommand
from app import create_app

app = create_app()
manager = Manager(app)
manager.add_command("db",MigrateCommand)

if __name__ == '__main__':
    manager.run()
manage.py
import os
from flask_migrate import Migrate
from app.models import db

migrate = Migrate()

def init_db(app):
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.root_path, 'sqlite3.db')
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db.init_app(app=app)

def init_migrate(app):
    migrate.init_app(app=app,db=db)
ext.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Channel(db.Model):
    __tablename__ = "channels"

    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(16),unique=True,nullable=False)
    sort = db.Column(db.Integer,nullable=False)
    # 对象方法。将类的对象形式转化成字典形式
    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'sort': self.sort,
        }
models.py
  • __innit__.py
 1 from flask import Flask
 2 from flask_restful import Api
 3 from app import ext
 4 from app.apis import ChannelList, ChannelDetail
 5 from app.views import restful_bp
 6 
 7 def create_app():
 8     app = Flask(__name__)
 9 
10     ext.init_db(app)
11     ext.init_migrate(app)
12 
13     # 注册实例化api扩展
14     api = Api(app)
15     # 路由注册
16     api.add_resource(ChannelList, '/api/ChannelList', endpoint='ChannelList')
17     api.add_resource(ChannelDetail, '/api/ChannelDetail/<int:id>',endpoint='ChannelDetail')
18     # 蓝图注册
19     app.register_blueprint(blueprint=restful_bp)
20     return app
  • api.py 初级[在models.py中定义函数用来字段转字典格式]
 1 from flask_restful import Resource,reqparse
 2 from app.models import Channel, db
 3 
 4 # 输出格式化。字段自定义验证函数。自定义验证完成后,必须把验证通过后的数值返回出去以方便后续保存操作
 5 def validate_channel_name(value):
 7     if Channel.query.filter_by(name=value).count() > 0:
 8         raise ValueError("频道名重复")
 9     return value
10 
11 # 输入的验证。flask-restful通过reqparse包中的RequestParser类提供客户端请求的数据参数(通常是json)验证校验功能
12 # 验证需要的参数(客户端提交的需要验证的字段名称,required=True表示必填项,类型,help="错误提示信息")
13 channel_parser = reqparse.RequestParser()
14 channel_parser.add_argument('name',required=True,type=validate_channel_name) # type='自定义验证函数名'
15 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型')
16 
17 # http://127.0.0.1:5000/api/ChannelList  [get请求、post请求]
18 class ChannelList(Resource):
19     """
20     GET     /channels
21     POST    /channels
22     """
23     def get(self):
24         # 数据库查询返回的是list类型的数据。需要将从数据库查询到的数据转为字典形式
25         _channels = Channel.query.all()
26         channels = [channel.to_dict() for channel in _channels]
27         # flask中视图函数只能返回str或者response对象。
28         # flask-restful可以将数据库中查询出来的数据转换成json;以满足flask视图函数的返回要求
29         # flask-restful返回字典,框架内部自动转换为json。return {'channels':channels},200
30         res = {
31             'channels':channels
32         }
33         return res,200
34 
35     def post(self):
36         # 通过RequestParser的实例对象 验证前端页面传递来的数据。如果验证不通过返回400错误
37         args = channel_parser.parse_args()
38 
39         channel = Channel()
40         channel.name = args['name']
41         channel.sort = args['sort']
42         db.session.add(channel)
43         db.session.commit()
44      # flask-restful 的类视图方法只能返回字典格式
45         return {'channel':channel.to_dict()},201
  • api.py 高级[使用装饰器转字典]
 1 from flask_restful import Resource, reqparse, fields, marshal_with, abort
 2 from app.models import Channel, db
 3 
 5 def validate_channel_name(value):
 6     if Channel.query.filter_by(name=value).count() > 0:
 7         raise ValueError("频道名重复")
 8     return value
11 channel_parser = reqparse.RequestParser() 12 channel_parser.add_argument('name',required=True,type=validate_channel_name) 13 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型') 14 16 # 通过marshal_withfields一起完成自定义输出的功能,让api接口可以返回对象,系统内部将对象根据fields的定义格式转换成字段类型 18 channel_fields = { 19 "id":fields.Integer, 20 # url链接。"url": "http://127.0.0.1:5000/api/ChannelDetail/3", 21 "url":fields.Url(endpoint="ChannelDetail",absolute=True), # absolute=True 表示加上前缀http://127.0.0.1:5000 22 "name":fields.String, 23 "sort":fields.Integer, 24 }
 1 # http://127.0.0.1:5000/api/channels  可以get、post请求
 2 class ChannelList(Resource):
 3 
 4     @marshal_with(fields=channel_fields)
 5     def get(self):
 6         _channels = Channel.query.all()
 7         return _channels, 200
 8         # 原来写法
 9         # channels = [channel.to_dict() for channel in _channels]
10         # res = {
11         #     'channels':channels
12         # }
13         # return res,200
14 
15     # 此装饰器接收一个参数(以什么格式)。有了这个装饰器,在返回的时候对象会按照参数同名所设置的方式变成一个字典返回
16     @marshal_with(fields=channel_fields)
17     def post(self):
18         # 验证前端页面传递来的数据。如果验证不通过返回400错误
19         args = channel_parser.parse_args()
20 
21         channel = Channel()
22         channel.name = args['name']
23         channel.sort = args['sort']
24         db.session.add(channel)
25         db.session.commit()
26         # 原来返回方式
27         # return {'channel':channel.to_dict()},201
28         # flask-restful只能返回字典格式类型。添加装饰器marshal_with可以定制输出格式。
29         return channel,201
 1 # http://127.0.0.1:5000/api/ChannelDetail/3
 2 class ChannelDetail(Resource):
 3     """
 4     GET     /channels/123
 5     PUT     /channels/234
 6     PATCH   /channels/123
 7     DELETE  /channels/123
 8     """
 9     def get_object(self,id):
10         channel = Channel.query.get(id)
11         if channel is None:
12             return abort(404,message="找不到对象")
13         return channel
14 
15     @marshal_with(fields=channel_fields)
16     def get(self,id):
17         channel = self.get_object(id)
18         return channel,200
19 
20     @marshal_with(fields=channel_fields)
21     def put(self,id):
22         channel = self.get_object(id)
23         args = channel_parser.parse_args()  # 字段验证
24 
25         channel.name = args.get("name",channel.name)
26         channel.sort = args.get("sort",channel.sort)
27         db.session.commit()
28         return channel,200
29 
30     def patch(self,id):
31         self.put(id)
32 
33     def delete(self,id):
34         channel = self.get_object(id)
35         db.session.delete(channel)
36         db.session.commit()
37         return "",204


API 1:N 模型

  坑:'/api/ArticleDetails/<int:id>' 此id必须和数据库中的相应字段同名,否则匹配不上;.Nested:表示将一个比较复杂的数据对象拆分开,方便转为字典格式

 1 from flask import Flask
 2 from flask_restful import Api
 3 from app import ext
 4 from app.apis import ChannelList, ChannelDetail, ArticleList, ArticleDetail
 5 from app.views import restful_bp
 6 
 7 def create_app():
 8     app = Flask(__name__)
 9 
10     ext.init_db(app)
11     ext.init_migrate(app)
12 
13     # 注册实例化api扩展
14     api = Api(app)
15     # 注册频道路由
16     api.add_resource(ChannelList, '/api/ChannelLists', endpoint='ChannelLists')
17     api.add_resource(ChannelDetail, '/api/ChannelDetails/<int:id>',endpoint='ChannelDetails')
18     # 注册文章路由
19     api.add_resource(ArticleList, '/api/Articles', endpoint='Articles')
20     api.add_resource(ArticleDetail, '/api/ArticleDetails/<int:id>',endpoint='ArticleDetails')
21 
22     # 蓝图
23     app.register_blueprint(blueprint=restful_bp)
24     return app
__init__.py
 1 import datetime
 2 from flask_sqlalchemy import SQLAlchemy
 3 
 4 db = SQLAlchemy()
 5 
 6 # 频道 1
 7 class Channel(db.Model):
 8     __tablename__ = "channels"
 9 
10     id = db.Column(db.Integer,primary_key=True)
11     name = db.Column(db.String(16),unique=True,nullable=False)
12     sort = db.Column(db.Integer,nullable=False)
13 
14     articles = db.relationship('Article',backref='channel',lazy='dynamic')
15 
16 # 文章 N
17 class Article(db.Model):
18     __tablename__ = "articles"
19 
20     id = db.Column(db.Integer,primary_key=True)
21     created_at = db.Column(db.DateTime,default=datetime.datetime.now())
22     updated_at = db.Column(db.DateTime,default=datetime.datetime.now(),onupdate=datetime.datetime.now())
23     title = db.Column(db.String(256),nullable=False)
24     content = db.Column(db.String(5000),nullable=False)
25 
26     channel_id = db.Column(db.Integer,db.ForeignKey("channels.id"))
models.py
  1 import datetime
  2 from flask_restful import Resource, reqparse, fields, marshal_with, abort
  3 from app.models import Channel, db, Article
  4 
  5 # ============================ N ===================================
  6 # 自定义一个类,用于时间格式化
  7 class MyDTFmt(fields.Raw):
  8     def format(self, value):
  9         return datetime.datetime.strftime(value,'%Y-%m-%d %H:%M:%S')
 10 
 11 # 定义参数验证格式
 12 article_parser = reqparse.RequestParser()
 13 article_parser.add_argument('title',required=True,type=str,help="标题必填")
 14 article_parser.add_argument('content',required=True,type=str,help="正文必填")
 15 article_parser.add_argument('channel_id',required=True,type=int,help="频道必填")
 16 
 17 # 定义返回输出格式
 18 article_fields = {
 19     'id':fields.Integer,
 20     'url':fields.Url(endpoint='ArticleDetails',absolute=True),
 21     'title':fields.String,
 22     'content':fields.String,
 23     # 等同于:'channel':fields.Nested(channel_fields),
 24     'channel':fields.Nested({ # 通过Nested将对象解开
 25         'name':fields.String,
 26         'url':fields.Url(endpoint="ChannelDetails",absolute=True),
 27         "sort": fields.Integer,
 28     }),
 29     'created_at':MyDTFmt,  # 进行自定义时间格式化
 30     'updated_at':fields.DateTime(dt_format="iso8601")
 31 
 32 }
 33 
 34 # 文章模块。get、post
 35 class ArticleList(Resource):
 36 
 37     @marshal_with(fields=article_fields)
 38     def get(self):
 39         articles = Article.query.all()
 40         return articles,200
 41 
 42     @marshal_with(fields=article_fields)
 43     def post(self):
 44         args = article_parser.parse_args()
 45 
 46         article = Article()
 47         article.title = args.get('title')
 48         article.content = args.get('content')
 49         article.channel_id = args.get('channel_id')
 50 
 51         db.session.add(article)
 52         db.session.commit()
 53         return article,201
 54 
 55 # 文章模块。get、put、patch、delete
 56 class ArticleDetail(Resource):
 57     def get_object(self,id):
 58         article = Article.query.get(id)
 59         if article is None:
 60             return abort(404,message="找不到对象")
 61         return article
 62 
 63     @marshal_with(fields=article_fields)
 64     def get(self,id):
 65         article = self.get_object(id)
 66         return article,200
 67 
 68     def put(self,id):
 69         pass
 70     def patch(self,id):
 71         pass
 72     def delete(self,id):
 73         pass
 74 
 75 
 76 # ================================== 1 =========================================
 77 # 自定义字段验证函数。 自定义验证后,必须把验证通过后的数值返回出去
 78 def validate_channel_name(value):
 79     if Channel.query.filter_by(name=value).count() > 0:
 80         raise ValueError("频道名重复")
 81     return value
 82 
 83 channel_parser = reqparse.RequestParser()
 84 channel_parser.add_argument('name',required=True,type=validate_channel_name) # type=自定义验证函数名
 85 channel_parser.add_argument('sort',required=True,type=int,help='sort字段为为必填项,int类型')
 86 
 87 channel_fields = {
 88     "id":fields.Integer,
 89     "url":fields.Url(endpoint="ChannelDetails",absolute=True),
 90     "name":fields.String,
 91     "sort":fields.Integer,
 92 }
 93 
 94 channel_article_fields = {
 95     "id": fields.Integer,
 96     "url": fields.Url(endpoint="ChannelDetails",absolute=True),
 97     "name": fields.String,
 98     "articles": fields.List(fields.Nested(article_fields)) # 列表是一个articles对象,通过Nested将其解开,输出出去。
 99 }
100 
101 
102 # 频道模块。get、post
103 class ChannelList(Resource):
104 
105     @marshal_with(fields=channel_fields)
106     def get(self):
107         _channels = Channel.query.all()
108         return _channels, 200
109 
110     # 此装饰器接收一个参数(以什么格式)。有了这个装饰器,在返回的时候对象会按照同名参数设置的方式变成一个字典返回
111     @marshal_with(fields=channel_fields)
112     def post(self):
113         # 验证前端页面传递来的数据。如果验证不通过返回400错误
114         args = channel_parser.parse_args()
115 
116         channel = Channel()
117         channel.name = args['name']
118         channel.sort = args['sort']
119         db.session.add(channel)
120         db.session.commit()
121         return channel,201
122 
123 
124 # 频道模块。get、put、patch、delete
125 class ChannelDetail(Resource):
126     """
127     GET     /channels/123
128     PUT     /channels/234
129     PATCH   /channels/123
130     DELETE  /channels/123
131     """
132     def get_object(self,id):
133         channel = Channel.query.get(id)
134         if channel is None:
135             return abort(404,message="找不到对象")
136         return channel
137 
138     @marshal_with(fields=channel_article_fields)
139     def get(self,id):
140         channel = self.get_object(id) # channel.articles 代表了当前频道下文章列表
141         return channel,200
142 
143     @marshal_with(fields=channel_fields)
144     def put(self,id):
145         channel = self.get_object(id)
146         args = channel_parser.parse_args()  # 字段验证
147 
148         channel.name = args.get("name",channel.name)
149         channel.sort = args.get("sort",channel.sort)
150         db.session.commit()
151         return channel,200
152 
153     def patch(self,id):
154         self.put(id)
155 
156     def delete(self,id):
157         channel = self.get_object(id)
158         db.session.delete(channel)
159         db.session.commit()
160         return "",204

 


高级拆分格式:

    

  • manage.py
 1 """ manage.py """
 2 
 3 import os
 4 from flask_migrate import MigrateCommand
 5 from flask_script import Manager
 6 from day05_chaiAdvanced import create_app
 7 
 8 # 从系统环境中获取参数FLASK_ENV的值给env,判断是什么环境下的服务器
 9 # 避免外人修改代码,方便代码放在什么环境服务器下,就在什么环境下运行
10 # 在系统终端 vim .bashrc 编写系统环境变量:#FLASK_ENV。export FLASK_ENV = "develop" 保存退出
11 env = os.environ.get("FLASK_ENV") or 'default'
12 
13 # 首先创建一个flask对象、加载配置、加载扩展库、初始化路由
14 app = create_app(env)
15 
16 # flask-scripy扩展
17 manager = Manager(app)
18 manager.add_command("db",MigrateCommand)
19 
20 if __name__ == '__main__':
21     manager.run()
  • __init__.py
 1 ''' 启动项目的__init__.py '''
 2 from flask import Flask
 3 
 4 from day05_chaiAdvanced.settings import Config, envs
 5 from day05_chaiAdvanced.extension import init_ext
 6 from day05_chaiAdvanced.views import init_api
 7 
 8 def create_app(env):
 9 
10     # 创建Flask对象
11     app = Flask(__name__,template_folder="../templates")
12 
13     # 加载初始化配置。 从类对象中加载。env参数确定在什么环境下的
14     app.config.from_object(envs.get(env))
15 
16     # 加载初始化扩展库。通过懒加载的方式加载(调用函数时参数的传递)
17     init_ext(app)
18 
19     # 加载初始化api路由器。通过懒加载的方式加载(调用函数时参数的传递)
20     init_api(app)
21 
22     return app
  • views.py
1 from Book.route import books_api
2 
3 def init_api(app):
4     books_api.init_app(app)
  • books/route.py
1 from flask_restful import Api
2 
3 from Book.views import BooksResource
4 
5 books_api = Api()
6 
7 books_api.add_resource(BooksResource, "/books/")
  • books/views.py
1 from flask_restful import Resource
2 
3 class BooksResource(Resource):
4 
5     def get(self):
6         return {"msg": "book ok"}


 

 

 

 

posted @ 2019-09-10 20:32  Tom's  阅读(625)  评论(0编辑  收藏  举报