文章目录
【拍出不一样的学校】航拍中央财经大学
这是我的学校——财经黄埔,你想来吗o( ̄▽ ̄)ブ
一、Restful API规范
Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。
1.协议
采用http或者https协议。
2.数据传输格式
数据之间传输的格式应该使用json形式,而不是xml。
3.url链接
url链接中不能有动词,只能有名词;
对于一些名词,如果出现复数,应该在后面加s。
4.HTTP请求方法
方法 | 含义 | 举例 |
---|---|---|
GET | 从服务器上获取资源 | /users/ 获取所有用户 |
… | … | /user/id/ 根据id获取一个用户 |
POST | 在服务器上新创建一个资源 | /user/ 新建一个用户 |
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 |
二、Flask-Restful插件的基本使用
1.基本概念
Flask-Restful是Flask中专门用来实现Restful API的插件,使用它可以快速集成Restful API功能。
在app的后台以及纯API的后台中,这个插件可以帮助我们节省很多时间;
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。
2.安装
Flask-Restful的环境要求:
- Flask版本>=0.8
- Python版本为2.6、2.7,或3.3及以上。
在虚拟环境中使用pip install flask-restful
命令安装。
3.基本使用
使用Flask-Restful定义视图类时,要继承自flask_restful.Resource
类,再根据具体的请求的方法来定义相应的方法。
例如,如果期望用户在客户端中使用get方法发送请求,那么就定义一个get方法,类似于MethodView。
简单使用测试如下:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class IndexView(Resource):
def get(self):
return {'username': 'Corley'}
def post(self):
return {'info': 'Login Successfully!!'}
api.add_resource(IndexView, '/', endpoint='index')
if __name__ == '__main__':
app.run(debug=True)
运行之后,使用Postman模拟请求如下:
显然,用get方法和post方法请求得到的结果是不一样的。
说明:
- endpoint用来指定使用
url_for()
方法反转url时传入的参数;如果不设置endpoint参数,会使用视图的名字的小写作为endpoint的值。 add_resource()
方法的第二个参数是视图函数的路由地址,这个地址与视图函数的route一样,可以传递参数;有一点不同的是,这个方法可以传递多个url来指定视图函数。
三、Restful参数解析
Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,即reqparse
。
RequestParser对象的add_argument()
方法可以指定字段的名字、数据类型等,常见的参数有:
- default
默认值,如果参数没有值,那么将使用这个参数指定的值。 - required
是否必须。
默认为False;如果设置为True,则必须提交这个参数。 - type
参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交得到的值。 - choices
选项,提交上来的值只有满足选项中的值才符合验证通过,否则验证不通过。 - help
错误信息,如果验证失败后,将会使用help参数指定的值作为错误信息。 - trim
是否要去掉前后的空格。
练习如下:
from flask import Flask
from flask_restful import Api, Resource, reqparse, inputs
app = Flask(__name__)
api = Api(app)
class IndexView(Resource):
def get(self):
return {'username': 'Corley'}
def post(self):
parse = reqparse.RequestParser()
parse.add_argument('username', type=str, help='用户名验证错误', required=True)
parse.add_argument('password', type=str, help='密码验证错误', trim=True)
parse.add_argument('age', type=int, help='年龄错误')
parse.add_argument('gender', type=str, help='性别错误', choices=['male', 'female', 'secret'], default='secret')
parse.add_argument('blog', type=inputs.url, help='博客地址错误')
parse.add_argument('phone', type=inputs.regex(r'1[35789]\d{9}'), help='电话号码错误')
args = parse.parse_args()
return {'info': 'Register Successfully!!', 'args': args}
api.add_resource(IndexView, '/', endpoint='index')
if __name__ == '__main__':
app.run(debug=True)
显示:
四、Restful高级使用
1.输出字段
对于一个视图类,可以预设好一些字段用于返回,以后使用ORM模型或者自定义模型的时候,会自动获取模型中相应的字段,生成json数据,再返回。
这需要导入flask_restful.marshal_with
装饰器,在视图类中定义一个字典来指定需要返回的字段,以及该字段的数据类型。
测试如下:
from flask import Flask
from flask_restful import Api, Resource, reqparse, inputs, fields, marshal_with
app = Flask(__name__)
api = Api(app)
class IndexView(Resource):
def get(self):
return {'username': 'Corley'}
def post(self):
parse = reqparse.RequestParser()
parse.add_argument('username', type=str, help='用户名验证错误', required=True)
parse.add_argument('password', type=str, help='密码验证错误', trim=True)
parse.add_argument('age', type=int, help='年龄错误')
parse.add_argument('gender', type=str, help='性别错误', choices=['male', 'female', 'secret'], default='secret')
parse.add_argument('blog', type=inputs.url, help='博客地址错误')
parse.add_argument('phone', type=inputs.regex(r'1[35789]\d{9}'), help='电话号码错误')
args = parse.parse_args()
return {'info': 'Register Successfully!!', 'args': args}
class ArticleView(Resource):
resource_fields = {
'title': fields.String,
'content': fields.String
}
@marshal_with(resource_fields)
def get(self):
return {'title': 'Python Flask'}
api.add_resource(IndexView, '/', endpoint='index')
api.add_resource(ArticleView, '/article/', endpoint='article')
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,get方法使用了marshal_with
装饰器后,即使返回时未定义content字段,在网页中也会渲染该字段。
还可以在get方法中直接返回数据库的查询结果,示例如下:
class ArticleView(Resource):
resource_fields = {
'title': fields.String,
'content': fields.String,
'tags': fields.String
}
@marshal_with(resource_fields)
def get(self, article_id):
article = Article.query.get(article_id)
return article
在get方法中返回article的时候,flask_restful会自动读取Article模型上的title、content和tags属性,组装成json格式的字符串返回给客户端。
2.复杂结构与格式化输出
有时候想要在返回的数据格式中,形成复杂的结构、美观的格式,此时可以使用一些特殊的字段来实现。
要在一个字段中放置一个列表,可以使用fields.List
;
在一个字段下面又是一个字典,可以使用fields.Nested
。
练习如下:
在当前项目目录下创建主程序文件flask_restful_test.py如下:
from flask import Flask
from flask_restful import Api, Resource, fields, marshal_with
import config
from exts import db
from models import Article
app = Flask(__name__)
api = Api(app)
app.config.from_object(config)
db.init_app(app)
@app.route('/')
def index():
return '这是首页'
class ArticleView(Resource):
resource_fields = {
'title': fields.String,
'content': fields.String,
'author': fields.Nested({
'username': fields.String,
'email': fields.String
}),
'tags': fields.List({
'id': fields.Integer,
'name': fields.String
})
}
@marshal_with(resource_fields)
def get(self, article_id):
article = Article.query.get(article_id=article_id)
return article
api.add_resource(ArticleView, '/article/<article_id>', endpoint='article')
if __name__ == '__main__':
app.run(debug=True)
创建配置文件config.py如下:
# 数据库连接配置
HOSTNAME = '127.0.0.1'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DATABASE = 'flask_restful'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
创建模型文件models.py如下:
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(30))
email = db.Column(db.String(50))
article_tag_table = db.Table(
'article_tag',
db.Column('article_id', db.Integer, db.ForeignKey('article.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(30))
content = db.Column(db.String(500))
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
author = db.relationship('User', backref='articles')
tags = db.relationship('Tag', secondary=article_tag_table, backref='tags')
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30))
创建中间文件exts.py如下:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
创建数据库迁移映射文件manage.py如下:
from flask_script import Manager
from flask_restful_test import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
import models
manager = Manager(app)
Migrate(app, db)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
在命令行当前目录下依次执行python manage.py db init
、python manage.py db migrate
、python manage.py db upgrade
,执行成功后可以在flask_restful数据库中看到建立了四个表,为了方便,手动插入数据如下:
mysql> select * from user;
+----+----------+-------------+
| id | username | email |
+----+----------+-------------+
| 1 | Corley | 123@163.com |
+----+----------+-------------+
1 row in set (0.00 sec)
mysql> select * from article;
+----+--------------+------------------+-----------+
| id | title | content | author_id |
+----+--------------+------------------+-----------+
| 1 | Python Flask | Migrate, Restful | 1 |
+----+--------------+------------------+-----------+
1 row in set (0.00 sec)
mysql> select * from tag;
+----+-------------+
| id | name |
+----+-------------+
| 1 | Python |
| 2 | Programming |
+----+-------------+
2 rows in set (0.00 sec)
mysql> select * from article_tag;
+------------+--------+
| article_id | tag_id |
+------------+--------+
| 1 | 1 |
| 1 | 2 |
+------------+--------+
2 rows in set (0.01 sec)
运行主程序后,访问http://127.0.0.1:5000/article/1,可以看到:
显然, 得到了美化的json格式的数据。
3.重命名属性
很多时候面向用户的字段名称是不同于内部的属性名,此时使用attribute
参数可以配置这种映射。
比如想要在返回article的id时表现的形式是article_id
,可以写成'article_title': fields.String(attribute='title')
。
主程序文件中属性重命名示例如下:
from flask import Flask
from flask_restful import Api, Resource, fields, marshal_with
import config
from exts import db
from models import Article
app = Flask(__name__)
api = Api(app)
app.config.from_object(config)
db.init_app(app)
@app.route('/')
def index():
return '这是首页'
class ArticleView(Resource):
resource_fields = {
'article_title': fields.String(attribute='title'),
'article_content': fields.String(attribute='content'),
'article_author': fields.Nested({
'username': fields.String,
'email': fields.String
}, attribute='author'),
'article_tags': fields.List(fields.Nested({
'id': fields.Integer,
'name': fields.String
}), attribute='tags')
}
@marshal_with(resource_fields)
def get(self, article_id):
article = Article.query.get(article_id)
return article
api.add_resource(ArticleView, '/article/<article_id>', endpoint='article')
if __name__ == '__main__':
app.run(debug=True)
再访问http://127.0.0.1:5000/article/1,看到:
显然, 显示的属性是重命名之后的属性。
4.指定默认值
在返回一些字段的时候,有时候可能没有值,此时可以在指定fields时使用default参数给定一个默认值。
修改主程序文件实现指定默认值示例如下:
from flask import Flask
from flask_restful import Api, Resource, fields, marshal_with
import config
from exts import db
from models import Article
app = Flask(__name__)
api = Api(app)
app.config.from_object(config)
db.init_app(app)
@app.route('/')
def index():
return '这是首页'
class ArticleView(Resource):
resource_fields = {
'article_title': fields.String(attribute='title'),
'article_content': fields.String(attribute='content'),
'article_author': fields.Nested({
'username': fields.String,
'email': fields.String
}, attribute='author'),
'article_tags': fields.List(fields.Nested({
'id': fields.Integer,
'name': fields.String
}), attribute='tags'),
'read_count': fields.Integer(default=0)
}
@marshal_with(resource_fields)
def get(self, article_id):
article = Article.query.get(article_id)
return article
api.add_resource(ArticleView, '/article/<article_id>', endpoint='article')
if __name__ == '__main__':
app.run(debug=True)
再访问http://127.0.0.1:5000/article/1,看到:
显然, 增加了文章的默认阅读量为0,返回的页面中也将其渲染出来。
在给模型中存在的字段设置默认值时,如果数据库中对应记录的该字段的值存在,则页面渲染数据库中的值;
如果数据库中该字段的值不存在即为空时,则渲染给定的默认值。