写在前面:
本文章只是一个蒟蒻的一个小尝试,存在很多问题,请谨慎观看,欢迎批评指正!
0x01 接口文档
使用 showdoc 实现:https://www.showdoc.com.cn/marx
0x02 项目结构
![未命名绘图 (1)](https://s2.loli.net/2022/01/18/JsOnkIPhuHzxAoQ.png)
0x03 初始化项目 __init__.py
from flask import Flask
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
print("████████╗ ██████╗ ██████╗ ██████╗ ")
print("╚══██╔══╝ ██╔═══██╗ ██╔══██╗ ██╔═══██╗")
print(" ██║ ██║ ██║ ██║ ██║ ██║ ██║")
print(" ██║ ██║ ██║ ██║ ██║ ██║ ██║")
print(" ██║ ╚██████╔╝ ██████╔╝ ╚██████╔╝")
print(" ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ")
app = Flask(__name__) # 创建程序实例
app.config.from_pyfile('config.py') # 导入配置
api = Api(app)
db = SQLAlchemy(app) # 实例化SQLAlchemy类,传入程序实例app,
from todo_api import views, models, commands, config
# 这些模块也需要导入程序实例,为了避免循环依赖,导入语句在末尾定义
0x04 视图函数 viwes.py
数据格式化及参数解析
数据域
resource_fields = {
'id': fields.Integer,
'title': fields.String,
'body': fields.String,
'state': fields.Boolean,
'start_time': fields.DateTime(dt_format='rfc822', default=datetime.now()),
'deadline': fields.DateTime(dt_format='rfc822')
}
json
格式返回值
![image-20220118142002148](https://s2.loli.net/2022/01/18/Kza3iZv1IQCpcxA.png)
jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
flask
自带的jsonify()
函数功能类似json.dump()
,但是会把Content-Type
从text/html
转换成带有json
特征的application/json
marshal(data, fields, envelope=None)
获取原始数据(以字典、列表、对象的形式)和字段字典以输出并根据这些字段过滤数据。
- data – 从中获取字段的实际对象
- fields – 其键将构成最终序列化响应输出的字典
- envelope – 用于封装序列化响应的可选键
ReqParse
class reqparse.Argument( name, default=None, dest=None, required=False, ignore=False, type=<function <lambda> at 0x1065c6c08>, location=('json', 'values'), choices=(), action='store', help=None, operators=('=', ), case_sensitive=True, store_missing=True) """添加要解析的参数"""
- name – 名称或选项字符串列表
- default – 默认值
- dest – 要添加到 parse_args() 返回的对象的属性名称
- required (bool) – 参数是否可以省略
- action – 在请求中遇到此参数时要采取的基本操作类型。有效选项是“store”和“append”
- ignore – 是否忽略参数类型转换失败的情况
- type – 请求参数应转换为的类型。如果一个类型引发 ValidationError,则错误中的消息将在响应中返回。在 python2 中默认为 unicode,在 python3 中默认为 str。
- location – 用于获取参数的 flask.Request 对象的属性(例如:标头、参数等),可以是迭代器。列出的最后一项在结果集中具有优先权。
- choices – 参数允许值的容器
- help – 参数的简要描述,当参数名称无效时在响应中返回,并且传递给类型转换器引发的 ValidationError 的消息。
- case_sensitive (bool) – 请求中的参数是否区分大小写
- store_missing (bool) – 如果请求中缺少参数,是否应存储参数默认值
_friendly_location = { u'json': u'the JSON body', u'form': u'the post body', u'args': u'the query string', u'values': u'the post body or the query string', u'headers': u'the HTTP headers', u'cookies': u'the request\'s cookies', u'files': u'an uploaded file', }
def __init__(self):
self.reqparse = reqparse.RequestParser() # 允许在单个请求的上下文中添加和解析多个参数
self.reqparse.add_argument(
'title',
type=str,
required=True,
help='No task title provided',
location='values'
)
self.reqparse.add_argument('body', type=str, default="", location='values')
self.reqparse.add_argument('state', type=bool, default=False, location='values')
self.reqparse.add_argument(
'start_time',
type=str,
default=datetime.now().strftime("%m/%d/%y %H:%M:%S"),
location='values'
)
self.reqparse.add_argument(
'deadline',
type=str,
default=(datetime.now()+timedelta(days=7)).strftime("%m/%d/%y %H:%M:%S"),
# 默认7天
location='values'
)
super(TaskListApi, self).__init__()
args = self.reqparse.parse_args()
# 从提供的请求中解析所有参数并返回结果(字典格式)
定义路由
Flask-RESTful
提供了一个 Resource
基础类,它能够定义一个给定 URL 的一个或者多个 HTTP 方法
class TaskListApi(Resource):
"""返回多条记录"""
def __init__(self):
pass
def get(self):
"""查看多条记录"""
pass
def post(self):
"""添加一条新的待办事项"""
pass
def put(self):
"""修改多条记录"""
pass
def delete(self):
"""删除多条记录"""
pass
class TaskApi(Resource):
def get(self, note_id):
"""查看一条记录"""
pass
def put(self, note_id):
"""修改一条记录"""
pass
def delete(self, note_id):
"""删除一条记录"""
pass
注册路由
add_resource(resource, *urls, **kwargs)
- resource (
Resource
) – 你的资源的类名- urls (str) – 一个或多个与资源匹配的 url 路由,适用标准
flask
路由规则。 任何 url 变量都将作为 args 传递给资源方法。- endpoint (str) – 端点名称(默认为
Resource.__name__.lower()
可用于在fields.Url
字段中引用此路由
api.add_resource(TaskListApi, '/todo/api/v1.0/tasks')
api.add_resource(TaskApi, '/todo/api/v1.0/tasks/<int:note_id>')
数据库操作 Flask-SQLAlchemy
插入记录
note = Note(
title=args["title"],
body=args["body"],
state=bool(args["state"]),
start_time=datetime.strptime(args["start_time"], '%m/%d/%y %H:%M:%S'),
deadline=datetime.strptime(args["deadline"], '%m/%d/%y %H:%M:%S'),
) # 创建 Python 对象
db.session.add(note) # 添加到会话
db.session.commit() # 提交会话
# 插入后,该条记录的id会自动加入note对象中
删除记录
note = db.session.query(Note).filter(Note.id == note_id).first()
if note:
db.session.delete(note)
db.session.commit()
更新记录
note = db.session.query(Note).all()
if note:
note.state = True
db.session.commit()
查询记录
常用的 SQLAlchemy 查询 过滤器(都返回 “一个新的查询” )
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的SQLAlchemy查询执行器
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果未查到,返回None |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回None |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate对象,它包含指定范围内的结果 |
分页
paginate(page, per_page, error_out=True)
- page – 当前页数
- per_page – 每页显示的条数
- error_out – 是否打印错误信息
- paginate.page – 当前页数
- paginate.pages – 总页数
- paginate.total – 数据总条数
- paginate.has_prev – 是否存在上一页,返回布尔值
- paginate.has_next – 是否存在下一页,返回布尔值
- paginate.iter_pages() – 所有页码,返回列表 如[1, 2, 3, 4]
- paginate(page, per_page,error_out).items – 返回当前页的所有数据
具体实现
from flask_restful import Resource, fields, reqparse, marshal
from flask import request, jsonify
from todo_api import api, db
from todo_api.models import Note
from datetime import datetime, timedelta
resource_fields = {
'id': fields.Integer,
'title': fields.String,
'body': fields.String,
'state': fields.Boolean,
'start_time': fields.DateTime(dt_format='rfc822', default=datetime.now()),
'deadline': fields.DateTime(dt_format='rfc822')
} # 数据域
class TaskListApi(Resource):
"""返回多条记录"""
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument(
'title',
type=str,
required=True,
help='No task title provided',
location='values'
)
self.reqparse.add_argument('body', type=str, default="", location='values')
self.reqparse.add_argument('state', type=bool, default=False, location='values')
self.reqparse.add_argument(
'start_time',
type=str,
default=datetime.now().strftime("%m/%d/%y %H:%M:%S"),
location='values'
)
self.reqparse.add_argument(
'deadline',
type=str,
default=(datetime.now()+timedelta(days=7)).strftime("%m/%d/%y %H:%M:%S"), # 默认7天
location='values'
)
super(TaskListApi, self).__init__()
def get(self):
"""查看记录"""
type = int(request.args["type"])
if type == 0:
"""输入关键字查询事项"""
page = int(request.args["page"]) # 获取页数
select = request.args["select"] # 获取查询字符串
if select:
notes = db.session.query(Note).filter(Note.title.like('%'+select+'%')).paginate(page, 5).items
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "'select' cannot be null"}) # 查询字符串必须非空
elif type == 1:
"""查看所有已完成事项"""
page = int(request.args["page"])
notes = db.session.query(Note).filter(Note.state == True).paginate(page, 5).items
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
elif type == 2:
"""查看所有待办事项"""
page = int(request.args["page"])
notes = db.session.query(Note).filter(Note.state == False).paginate(page, 5).items
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
elif type == 3:
"""查看所有事项"""
page = int(request.args["page"])
notes = db.session.query(Note).paginate(page, 5).items
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
def post(self):
"""添加一条新的待办事项"""
args = self.reqparse.parse_args()
note = Note(
title=args["title"],
body=args["body"],
state=bool(args["state"]),
start_time=datetime.strptime(args["start_time"], '%m/%d/%y %H:%M:%S'),
deadline=datetime.strptime(args["deadline"], '%m/%d/%y %H:%M:%S'),
)
# 加入到数据库
db.session.add(note)
db.session.commit()
return jsonify({
"notes": marshal(data=note, fields=resource_fields),
"status": 200,
"message": "Created"
})
def put(self):
"""修改多条记录"""
completed = int(request.args["completed"])
if completed:
if completed == 1:
notes = db.session.query(Note).all()
if notes:
for note in notes:
note.state = True
db.session.commit()
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
elif completed == 2:
notes = db.session.query(Note).all()
if notes:
for note in notes:
note.state = False
db.session.commit()
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
else:
return jsonify({"status": 404, "message": "Not Found"})
else:
return jsonify({"status": 404, "message": "Not Found"})
def delete(self):
"""删除多条记录"""
type = int(request.args["type"])
if type == 0:
notes = db.session.query(Note).all()
for note in notes:
db.session.delete(note)
db.session.commit()
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "No Content"
})
elif type == 1:
notes = db.session.query(Note).filter(Note.state == True).all()
for note in notes:
db.session.delete(note)
db.session.commit()
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "No Content"
})
elif type == 2:
notes = db.session.query(Note).filter(Note.state == False).all()
for note in notes:
db.session.delete(note)
db.session.commit()
return jsonify({
"notes": marshal(data=notes, fields=resource_fields),
"status": 200,
"message": "No Content"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
class TaskApi(Resource):
def get(self, note_id):
"""查看一条记录"""
note = db.session.query(Note).filter(Note.id == note_id).first()
if note:
return jsonify({
"notes": marshal(data=note, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
def put(self, note_id):
"""修改一条记录"""
completed = int(request.args["completed"])
if completed:
if completed == 1:
note = db.session.query(Note).filter(Note.id == note_id).first()
if note:
note.state = True
db.session.commit()
return jsonify({
"notes": marshal(data=note, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
elif completed == 2:
note = db.session.query(Note).filter(Note.id == note_id).first()
if note:
note.state = False
db.session.commit()
return jsonify({
"notes": marshal(data=note, fields=resource_fields),
"status": 200,
"message": "OK"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
else:
return jsonify({"status": 404, "message": "Not Found"})
else:
return jsonify({"status": 404, "message": "Not Found"})
def delete(self, note_id):
"""删除一条记录"""
note = db.session.query(Note).filter(Note.id == note_id).first()
if note:
db.session.delete(note)
db.session.commit()
return jsonify({
"notes": marshal(data=note, fields=resource_fields),
"status": 200,
"message": "No Content"
})
else:
return jsonify({"status": 404, "message": "Not Found"})
# 使用指定的 endpoint 注册路由到框架上
api.add_resource(TaskListApi, '/todo/api/v1.0/tasks')
api.add_resource(TaskApi, '/todo/api/v1.0/tasks/<int:note_id>')
0x05 数据库模型 models.py
用来 映射到数据库表 的Python类通常被称为数据库模型(model),一个数据库模型类对应数据库中的一个表。定义模型就是 使用Python类 定义 表模式,并声明 映射关系。所有的模型类都需要继承
Flask-SQLAlchemy
提供的db.Model
基类。
from todo_api import db
from datetime import datetime
class Note(db.Model):
id = db.Column(db.INTEGER, primary_key=True, autoincrement=True) # 主键ID
title = db.Column(db.String(100), nullable=False)
body = db.Column(db.String(300))
state = db.Column(db.Boolean, default=False)
start_time = db.Column(db.DateTime)
deadline = db.Column(db.DateTime)
字段的类型通过
Column
类构造方法的第一个参数传入
字段类型 说明 Integer
一个整数 String
(size)有长度限制的字符串 Text
一些较长的 unicode 文本 DateTime
表示为 Python datetime
对象的 时间和日期Float
存储浮点值 Boolean
存储布尔值 PickleType
存储为一个持久化的 Python 对象 LargeBinary
存储一个任意大的二进制数据
字段参数名 说明 primar_key 如果设为True,则该字段为主键 unique 如果设为True,则该字段不允许出现重复值 index 如果设为True,则为该字段创建索引,以提高查询效率 nullable 该字段是否可为空,默认为True default 为该字段设置默认值 name 设置字段名,默认为类属性名
0x06 命令函数 commands.py
import click
from todo_api import app, db
from todo_api.models import Note
@app.cli.command() # 注册一个flask命令
@click.option('--drop', is_flag=True, help='Create after drop.')
def initdb(drop):
"""
初始化数据库
is_flag=True 声明drop为布尔值
--drop选项,加上则为True
"""
if drop:
click.confirm('This operation will delete the database, do you want to continue?', abort=True)
db.drop_all() # 删除数据库
click.echo('Drop tables.')
db.create_all() # 创建数据库
click.echo('初始化数据库')
@app.cli.command()
@click.option('--count', default=20, help='Quantity of messages, default is 20.')
def forge(count):
"""
生成虚拟数据
--count选项,可选择数据量,默认为20
"""
from faker import Faker
db.drop_all()
db.create_all()
fake = Faker("zh_CN") # 简体中文
click.echo('Working...')
for i in range(count):
note = Note(
title=fake.sentence(), # 随机生成一句话
body=fake.sentence(),
state=fake.boolean(),
start_time=fake.past_datetime(), # 随机生成已经过去的时间
deadline=fake.future_datetime() # 未来时间
)
db.session.add(note)
db.session.commit()
click.echo('Created %d fake messages.' % count)
0x07 配置文件 config.py
import os
import sys
from todo_api import app
import pymysql
"""
# 使用SQLit
WIN = sys.platform.startswith('win') # 获取当前系统平台
if WIN:
prefix = 'sqlite:///' # Windows
else:
prefix = 'sqlite:' # UNIX
dev_db = prefix + os.path.join(os.path.dirname(app.root_path), 'data.db')
# os.path.dirname(app.root_path)获取上层目录
# app.root_path属性存储程序实例所在路径
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI', dev_db)
# 用于连接数据的数据库,优先从环境变量获取
"""
# 使用mysql
pymysql.install_as_MySQLdb()
USERNAME = 'root'
PASSWORD = 'root'
HOSTNAME = "127.0.0.1"
PORT = '3306'
DATABASE = 'todoapi'
SQLALCHEMY_DATABASE_URI = "mysql://{}:{}@{}:{}/{}?charset=utf8".format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 这个配置变量决定是否追踪对象的修改
连接 URI 格式
SQLAlchemy 把一个引擎的源表示为一个连同设定引擎选项的可选字符串参数的 URI。URI 的形式是:
dialect+driver://username:[email protected]:port/database
Postgres:
postgresql://scott:[email protected]/mydatabase
MySQL:
mysql://scott:[email protected]/mydatabase
Oracle:
oracle://scott:[email protected]:1521/sidname
SQLite:
sqlite:////absolute/path/to/foo.db
参考文章:
Flask Web 开发实战:入门、进阶与原理解析(李辉著)
教你 10 分钟构建一套 RESTful API 服务( Flask篇 ) - 云+社区 - 腾讯云 (tencent.com)
flask中使用flask-sqlalchemy连接mysql数据库并创建数据表_徳-CSDN博客_flask-sqlalchemy mysql