【Flask扩展】Flask-Script、Flask-SQLAlchemy、Flask-Migrate、Flask-RESTful

好风凭借力,送我上青云。

Flask-SQLAlchemy

安装

pip install flask-sqlalchemy -i https://pypi.doubanio.com/simple

定义使用

Flask-SQLAlchemy和原生的SQLAlchemy基本一致,首先想让我们定义一下数据库并进行连接

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)  # type:Flask

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8"

app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app=app)


class User(db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(length=50), nullable=False)


class Article(db.Model):
    __tablename__ = 'article'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(length=50), nullable=False)

    uid = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    author = db.relationship("User", backref="articles")


# db.drop_all()
# db.create_all()

# user = User(username="ydy")
# article = Article(title="xxxxxxaaaa")
#
# article.author = user
#
# db.session.add(article)
# db.session.commit()

users = User.query.all()
print(users)

if __name__ == '__main__':
    app.run(debug=True)

使用Flask-SQLAlchemy需要配置一下几个部分:

  1. 导入from flask_sqlalchemy import SQLAlchemy
  2. 配置相关数据库参数用于连接,最终形成一个URI:DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8"
  3. 把URI添加到app的配置,app.config['SQLALCHEMY_DATABASE_URI'] = DB_URIapp.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False为了防止结合alembic生成迁移数据库提示错误信息
  4. 创建SQLAlchemy对象,db = SQLAlchemy(app=app),这一步必须放在配置之后否则无效。

结合alembic迁移

在对应文件目录执行命令,初始化一个alembic的文件夹(名称随便取,一般为alembic)

alembic init alembic

文件目录如下:

.
├── alembic
│   ├── env.py
│   ├── README
│   ├── script.py.mako
│   └── versions
├── alembic.ini
└── demo.py

修改alembic/env.py文件配置

目的是导入我们app所在文件的模块路径。需要根据个人情况,每个人的目录结构不同,所以查找路径不同。例如:我的文件就是上面的demo.py,结合目录结构。之后还需要导入app所在的文件,并且修改target_metadata的值

import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))

import demo

target_metadata = demo.db.Model.metadata

修改alembic.ini配置

sqlalchemy.url = mysql+pymysql://root:root@localhost/demo?charset=utf8

生成迁移脚本

alembic revision --autogenerate -m 'message'

成功之后会在versions文件夹下创建一个py文件。

基本的Flask-SQLALchemy配置完成,其他的操作基本和SQLAlchemy一致。

Flask-Script

安装

pip install flask-script -i https://pypi.doubanio.com/simple

定义使用

新建一个文件manager.py,输入一下格式代码

from flask_script import Manager
from xxx import app # 从其他文件导入app

manager = Manager(app) # 接受用一个app

if __name__ == '__main__':
    manager.run()

添加脚本

@manager.option("-u", "--name", dest="name")
def add_user(name):
    print("hello %s" % name)

添加一个函数,接受一个个参数,并且添加了一个个装饰器,接着我们通过命令执行文件,其中dest是接受传递的值:

python manager.py add_user -u "ydy"
或者
python manager.py add_user --name "ydy"

使用子命令

我们可以新建一个db_script.py来管理我们操作数据库的命令文件

from flask_script import Manager

db_manager = Manager()

@db_manager.command
def init():
    pass

@db_manager.command
def makemigrations():
    pass

@db_manager.command
def migrate():
    pass

之后我们在script主文件注册子命令:

from flask_script import Manager
from xxx import app # 从其他文件导入app
from db_script import db_manager

manager = Manager(app) # 接受用一个app
manager.add_command("db",db_manager) # 添加子命令,名称为db,随意取

if __name__ == '__main__':
    manager.run()

使用子命令:

python manager.py db init
python manager.py db makemigrations
python manager.py db migrate

使用类定义

举一个非常简单的例子,我们想创建一个hello仅打印出“ hello world” 的命令。它不需要任何参数,因此非常简单:

from flask_script import Command

class Hello(Command):
    "prints hello world"

    def run(self):
        print "hello world"

manager.add_command('hello', Hello())

使用类定义:

  • 必须继承Command
  • 必须实现run方法
  • 必须通过add_command方法添加命令

通过类定义接受参数的形式:

from flask_script import Command,Option

class Hello(Command):
    option_list = (
        Option("-u", "--name", dest="name"),
    )

    def run(self, name):
        print("hello {}".format(name))

manager.add_command("hello", Hello())

#========================方式二=======================
class Hello2(Command):
    def __init__(self):
        # 一般用来设置默认值
        pass

    def get_options(self):
        return [
            Option("-u", "--name", dest="name"),
        ]

    def run(self, name):
        print("hello {}".format(name))

manager.add_command("hello2", Hello2())

Flask-Migrate

安装

pip install flask-migrate -i https://pypi.doubanio.com/simple

在manage.py代码中使用

from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from xxx import app
from xxx import models # 需要导入模型,产生映射关系,否则无法生成迁移文件

manager = Manager(app)

# 用来绑定app和db到flask_migrate的
Migrate(app,db)

# 添加Migrate的所有子命令到db下
manager.add_command("db",MigrateCommand)

if __name__ == '__main__':
    manager.run()

flask_migrate常用命令

  1. 初始化一个环境:python manage.py db init
  2. 自动检测模型,生成迁移脚本:python manage.py db migrate
  3. 将迁移脚本映射到数据库中:python manage.py db upgrade
  4. 更多命令:python manage.py db --help

Flask-RESTful

Flask-RESTful适用于前后端分离项目的接口设计,设计符合RESTful风格的代码,使用简单方便。Flask-RESTful要求Python版本为 2.6, 2.7, 或者 3.3及以上。

安装

pip install flask-restful -i https://pypi.doubanio.com/simple

定义使用

下面结合Flask-RESTful设计一个接口,新建一个manage.py的文件:

from flask import Flask
from flask_restful import Api, Resource

app = Flask(__name__)

api = Api(app=app)

class Index(Resource):
    def get(self):
        return {'username': "ydongy"}

api.add_resource(Index, "/", endpoint="index")

if __name__ == '__main__':
    app.run(debug=True)

分析:

  1. 导入模块from flask_restful import Api, ResourceApi接受一个app生成一个对象,相当于把app包装。Resource是需要继承的类,替换以前的views.MethodView
  2. api = Api(app=app),把app包装
  3. class Index(Resource)::定义的类视图继承Resource,此时返回的响应内容就是Json格式
  4. api.add_resource(Index, "/", endpoint="index"):添加路由和端点,endpoint用于url_for反转url使用,如果不写endpoint,那么将会使用视图的名字的小写来作为endpoint。

参数解析

Flask-RESTful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。以下是基本用法:

from flask_restful import reqparse

class Login(Resource):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('username', type=str, help="用户名", required=True)
        parser.add_argument('password', type=str, help="密码", required=True)
        args = parser.parse_args()
        
        ......

add_argument可以指定这个字段的名字,这个字段的数据类型等,也就是接受前台传递过来的请求体数据。以下将对这个方法的一些参数做详细讲解:

常用参数:

  • default:默认值,如果这个参数没有值,那么将使用这个参数指定的值。
  • required:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。
  • type:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
  • choices:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
  • help:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
  • trim:是否要去掉前后的空格。

扩展type

其中的type,可以使用python自带的一些数据类型,也可以使用flask_restful.inputs下的一些特定的数据类型来强制转换。比如一些常用的:

  • url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
from flask_restful import inputs

parser.add_argument('urlpath', type=inputs.url, help="url地址", required=True)
  • regex:正则表达式。
from flask_restful import inputs

parser.add_argument('phone', type=inputs.regex(r'1[3-9]d{9}'), help="手机号")
  • date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常。
from flask_restful import inputs

parser.add_argument('birthday', type=inputs.date, help="生日", required=True)

数据格式化

对于一个视图函数,你可以指定好一些字段用于返回。以后可以使用ORM模型或者自定义的模型的时候,他会自动的获取模型中的相应的字段,生成json数据,然后再返回给客户端。这其中需要导入flask_restful.marshal_with装饰器。并且需要写一个字典,来指示需要返回的字段,以及该字段的数据类型。示例代码如下:

from flask_restful import marshal_with,fields

class ProfileView(Resource):
    resource_fields = {
        'username': fields.String,
        'age': fields.Integer,
        'gender': fields.Integer
    }

    @marshal_with(resource_fields)
    def get(self,user_id):
        user = User.query.get(user_id)
        return user

分析:

  1. resource_fields:定义一个需要返回的json格式的数据变量,名称随便取
  2. @marshal_with(resource_fields):给方法增加一个装饰器,并把定义的变量传入

在get方法中,返回user的时候,flask_restful会自动的读取user模型上的username以及age还有gender属性。组装成一个json格式的字符串返回给客户端,因此不需要我们手动调用各个字段,组织成json数据返回。但是注意定义的字典中的键需要和模型字段名一致,如果想要不一样可以增加一个参数,如下重命名属性。

重命名属性

有时候返回给前台的的字段名称的不同于模型的属性名。此时可以使用attribute配置这种映射。比如现在想要返回user.username中的值,但是在返回给前台的时候,想要以name返回回去,那么可以这样写:

resource_fields = {
    'name': fields.String(attribute='username')
}

attribute:对应模型字段属性名称,name是返回给前端的字段名称。
还可以定义一个default默认值,当字段没有值的时候可以指定一个默认值。

关联关系

首先我们定义几个模型类,表示之间的关联关系:

class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50))
    email = db.Column(db.String(200))


article_tag_table = db.Table(
    'tb_tag_article',
    db.Column('article_id', db.Integer, db.ForeignKey("tb_article.id"), primary_key=True),
    db.Column('tag_id', db.Integer, db.ForeignKey("tb_tag.id"), primary_key=True)
)


class Article(db.Model):
    __tablename__ = "tb_article"
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50))
    content = db.Column(db.String(200), nullable=True)
    author_id = db.Column(db.Integer, db.ForeignKey('tb_user.id'))  # 表名.id

    author = db.relationship("User", backref="articles")

    tags = db.relationship("Tag", backref="articles", secondary=article_tag_table)


class Tag(db.Model):
    __tablename__ = "tb_tag"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
  • user-article:一对多的关系
  • article-tag:多对多的关系

此时我们想要查询一个文章,但是查询结果中还要包含该文章的作者、所属标签的信息,而不是单单的外键id。此时需要如下定义,一对多的关系可以使用fields.Nested,多对多的关系可以使用fields.List

class ArticleView(Resource):
    resource_fields = {
        'title': fields.String,
        'content': fields.String,
        'author': fields.Nested({
            "id": fields.Integer,
            "username": fields.String,
            "email": fields.String
        }),
        'tags': fields.List(fields.Nested({
            "id": fields.Integer,
            "name": fields.String
        }))
    }

    @marshal_with(resource_fields)
    def get(self, article_id):
        article = Article.query.get(article_id)
        return article
  • author:就是Article关联字段的属性,表示单个,所以使用fields.Nested,参数是想要获取的关联对象的属性
  • tags:也是Article关联字段的属性,表示多个,所以使用fields.List,而每一个tag又是一个关联对象,所以嵌套一层fields.Nested,参数是想要获取的关联对象的属性

返回结果如下:

{
    "title": "mysql",
    "content": "入门到删库",
    "author": {
        "id": 1,
        "username": "jack",
        "email": "123@163.com"
    },
    "tags": [
        {
            "id": 1,
            "name": "database"
        },
        {
            "id": 2,
            "name": "language"
        }
    ]
}

蓝图及模板使用

  • 在蓝图中,如果使用flask-restful,那么在创建Api对象的时候,就不要再使用app了,而是使用蓝图app。
  • 如果在flask-restful的视图中想要返回html代码,或者是模版,那么就应该使用api.representation这个装饰器来定义一个函数,在这个函数中,应该对html代码进行一个封装,再返回。
from flask import render_template, make_response
from flask import Blueprint
from flask_restful import Api

api_bp = Blueprint(name="v1", import_name=__name__, url_prefix="/v1")
api = Api(app=api_bp)

@api.representation('text/html')
def html(data, code, headers):
    resp = make_response(data)
    return resp


class ListView(Resource):
    def get(self):
        return render_template("index.html")

api.add_resource(ListView, '/article/', endpoint="list_view")

分析:

  1. api_bp = Blueprint(name="v1", import_name=__name__, url_prefix="/v1"):定义蓝图
  2. api = Api(app=api_bp):传入蓝图app创建Api对象
  3. @api.representation('text/html'):装饰一个函数,指明返回的content-type类型,该方法自动执行,所以需要返回一个response对象
  4. return render_template("index.html"):restful类视图返回模板,会自动执行上面被装饰器定义过的函数
原文地址:https://www.cnblogs.com/ydongy/p/13158310.html