Flask之Flask-SQLAlchemy

之前文章大概介绍了 SQLAlchemy ,我们今天讲下flask中的Flask-SQLAlchemy,大概用法跟原生的很像,但是配置使用起来更简单了。

环境准备

项目结构

我们在这里仿照Django的目录结构来创建一个项目,这种项目结构也是Flask官方建议的

我们的项目结构大概如下:

1
2
3
4
5
6
└──	app					# 应用目录
├ ├── static # 静态文件目录
├ ├── templates # 模板文件目录
├ ├── views # 蓝图目录(视图文件)
├ ├── __init__.py # 应用导入时执行文件
└──manage.py # 项目入口文件

views目录中我们写一个蓝图user.py,代码如下:

1
2
3
4
5
6
7
8
9
10
# 导入蓝图
from flask import Blueprint

# 创建蓝图
user = Blueprint('user', __name__)

# 创建路由及视图函数
@user.route('/users')
def users():
return '这里是users视图'

app/__init__.py文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入Flask
from flask import Flask
# 导入user蓝图
from .views.user import user

# 创建应用
def cerate_app():
# 实例化一个Flask
my_app = Flask(__name__)
# 注册user蓝图到应用
my_app.register_blueprint(user)
# 返回应用
return my_app

manage.py文件代码如下:

1
2
3
4
5
6
7
8
9
# 导入应用
import app

# 创建应用
my_app = app.cerate_app()

if __name__ == '__main__':
# 运行应用
my_app.run(debug=True)

Flask应用创建

这里同样默认你已经安装好了 Flask-SQLAlchemy (没有安装请使用 pip install Flask-SQLAlchemy 进行安装)

修改app/__init__.py

引入Flask-SQLAlchemy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 导入视图
from .views.user import user

# 实例化SQLAlchemy
db = SQLAlchemy()

# 创建flask应用
def create_app():
# 实例化一个flask app
my_app = Flask(__name__)
# 初始化App配置 这个app配置就厉害了,专门针对 SQLAlchemy 进行配置
# SQLALCHEMY_DATABASE_URI 配置 SQLAlchemy 的链接字符串儿
my_app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:@127.0.0.1:3306/sqlalchemysss?charset=utf8"
# SQLALCHEMY_POOL_SIZE 配置 SQLAlchemy 的连接池大小
my_app.config["SQLALCHEMY_POOL_SIZE"] = 5
# SQLALCHEMY_POOL_TIMEOUT 配置 SQLAlchemy 的连接超时时间
my_app.config["SQLALCHEMY_POOL_TIMEOUT"] = 15
my_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 初始化SQLAlchemy
db.init_app(my_app)
# 注册路由视图蓝图
my_app.register_blueprint(user)
return my_app

创建models.py

创建orm模型文件,类似Django中的models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from app import db

# 创建User模型
class User(db.Model):
# 设置用户数据表名
__tablename__ = "app_users"
__table_args__ = {"useexisting": True}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), comment="姓名")

if __name__ == '__main__':
from app import create_app
# 创建app
my_app = create_app()
# 清空数据
db.drop_all(app=my_app)
# 执行db操作
db.create_all(app=my_app)

修改视图

app/views/user.py调用models模型,使用该模型进行增查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from flask import Blueprint,jsonify

# 引入User模型
from app.models import User
from app import db

# 实例化蓝图
user = Blueprint("user",__name__)

# 添加用户路由及视图函数
@user.route("/users/ass")
def add():
# 添加一条数据
db.session.add(User(name="张学友"))
# 执行操作
db.session.commit()
return "添加用户"

# 用户列表
@user.route("/users")
def users():
# 查询所有用户
res = User.query.all()
# 待格式化的用户列表
user = []
# 循环提取用户
for item in res:
# 添加用户至列表
user.append({"name":item.name, "id":item.id})

return jsonify(user)

此时运行flask项目,单独执行models.py进行数据表创建,此时访问路由程序正常运行

使用Flask-Script

我们继续仿照Django进行操作,Django给我们提供了命令来运行django程序,我们要实现类似功能需要借助第三方扩展包Flask-Script

1
2
3
4
5
6
7
8
9
10
11
12
# 导入 Flask-Script 中的 Manager
from flask_script import Manager

import app
my_app = app.create_app()

#让app支持 Manager
manager = Manager(my_app)

if __name__ == '__main__':
# 使用manager.run()代替my_app.run(),此时在项目根目录使用python manager.py runserver即可运行程序
manager.run()

使用flask_script命令行

使用command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask_script import Manager

import app
my_app = app.create_app()

#让app支持 Manager
manager = Manager(my_app)

@manager.command
def outName(name):
print(f"你好{name}")

if __name__ == '__main__':
# 使用manager.run()代替my_app.run(),此时在项目根目录使用python manager.py runserver即可运行程序
manager.run()

测试我们控制台使用python manager.py outName 刘德华,控制台就会打印“你好刘德华”

使用option

option命令语法如下: @manager.opation("-短指令","--长指令",dest="变量名")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask_script import Manager

import app
my_app = app.create_app()

#让app支持 Manager
manager = Manager(my_app)

@manager.command
def outName(name):
print(f"你好{name}")

@manager.option('-ip', '--ipaddress', dest = "ip")
@manager.option('-p', '--port', dest = "port")
def runApp(ip, port):
print(f"程序运行在:{ip}上,且端口为:{port}")

if __name__ == '__main__':
# 使用manager.run()代替my_app.run(),此时在项目根目录使用python manager.py runserver即可运行程序
manager.run()

测试我们测试一下命令,无论长指令还是短指令都正确输出了

1
2
3
4
5
6
E:\site>python manager.py runApp -ip 10.10.10.222 -p 9999
程序运行在:10.10.10.222上,且端口为:9999

E:\site>python manager.py runApp --ipaddress 10.10.10.111 --port 8888
程序运行在:10.10.10.111上,且端口为:8888

使用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask_script import Manager

import app
my_app = app.create_app()

#让app支持 Manager
manager = Manager(my_app)


@manager.option('-ip', '--ipaddress', dest = "ip")
@manager.option('-p', '--port', dest = "port")
def runApp(ip, port):
my_app.run("0.0.0.0", int(port),debug=True)

if __name__ == '__main__':
# 使用manager.run()代替my_app.run(),此时在项目根目录使用python manager.py runserver即可运行程序
manager.run()

测试

经测试,跟Django一样的效果,完成了

1
2
3
4
5
6
7
8
9
10
11
E:\site>python manager.py runApp -ip 0.0.0.0 -p 9588
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 339-556-363
* Running on http://0.0.0.0:9588/ (Press CTRL+C to quit)

Flask-Migrate

上方代码执行的前提还是要先手动执行models.py文件,让其创建数据表,我们既然知道了创建命令是否可以把类似Django的数据库迁移也做出来?这就用到了另一个第三方库Flask-Migrate,该扩展库必须是要基于flask_script

安装 Flask-Migrate

1
pip install Flask-Migrate

引入Flask-Migrate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from flask_script import Manager
# 导入 Flask-Migrate 中的 Migrate 和 MigrateCommand
# 这两个东西说白了就是想在 Flask-Script 中添加几个命令和指令而已
from flask_migrate import Migrate, MigrateCommand

import app
my_app = app.create_app()

#让app支持 Manager
manager = Manager(my_app)

# Migrate 既然是数据库迁移,那么就得告诉他数据库在哪里
# 并且告诉他要支持那个app
Migrate(my_app, app.db)
# 现在就要告诉manager 有新的指令了,这个新指令在MigrateCommand 中存着呢
# 当你的命令中出现 db 指令,则去MigrateCommand中寻找对应关系
manager.add_command("db", MigrateCommand)
"""
数据库迁移指令:
python manager.py db init
python manager.py db migrate # Django中的 makemigration
python manager.py db upgrade # Django中的 migrate
"""


@manager.option('-ip', '--ipaddress', dest = "ip")
@manager.option('-p', '--port', dest = "port")
def runApp(ip, port):
my_app.run("0.0.0.0", int(port),debug=True)
return "程序运行成功!!!"

if __name__ == '__main__':
# 使用manager.run()代替my_app.run(),此时在项目根目录使用python manager.py runserver即可运行程序
manager.run()

执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
E:\site>python manager.py db init
Creating directory E:\site\migrations ... done
Creating directory E:\site\migrations\versions ... done
Generating E:\site\migrations\alembic.ini ... done
Generating E:\site\migrations\env.py ... done
Generating E:\site\migrations\README ... done
Generating E:\site\script.py.mako ... done
Please edit configuration/connection/logging settings in 'E:\\site\\migratio
ns\\alembic.ini' before proceeding.

E:\site>python manager.py db migrate
D:\Program Files\Python37\lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string valu
e: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 484")
result = self._query(query)
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected removed table 'teachers'
INFO [alembic.autogenerate.compare] Detected removed table 'app_users'
INFO [alembic.autogenerate.compare] Detected removed table 'students'
INFO [alembic.autogenerate.compare] Detected removed table 'citys'
INFO [alembic.autogenerate.compare] Detected removed table 'user'
INFO [alembic.autogenerate.compare] Detected removed table 'studentteacher'
Generating E:\site\migrations\versions\ed69f0263233_.py ... done

E:\site>python manager.py db upgrade
D:\Program Files\Python37\lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string valu
e: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 484")
result = self._query(query)
INFO [alembic.runtime.migration] Context impl MySQLImpl.

执行成功,此时查看数据表正常,且文件夹目录多了一个migrations文件夹