基于 Flask 的 RESTful API 实现

写在前面:

本文章只是一个蒟蒻的一个小尝试,存在很多问题,请谨慎观看,欢迎批评指正!

0x01 接口文档

使用 showdoc 实现:https://www.showdoc.com.cn/marx

image-20220117174808850

0x02 项目结构

未命名绘图 (1)

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
jsonify({
    "notes": marshal(data=notes, fields=resource_fields),
    "status": 200,
    "message": "OK"
})

flask 自带的 jsonify() 函数功能类似 json.dump() ,但是会把 Content-Typetext/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)

image-20220118153707342

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

Faker库说明_Z_Sam-CSDN博客_faker库

flask用paginate实现分页_xiao-CSDN博客_flask paginate

Flask-SQLALchemy基本使用_笑笑生的博客-CSDN博客_flask_sqlalchemy查询语句

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赤城封雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值