【9】Flask 脚本-MySQL数据库迁移

1 集成Python shell

在实际的开发中,不免有一些任务需要在shell下完成。比如为cms后台添加超级管理员的需求,又比如迁移数据库的需求,定时任务等等,诸如这类需求更适合在shell中去操作(大部分需要在shell中去操作的都是权限比较高的任务)。

提示:迁移数据库就是用来解决数据库更新问题,解决上一篇db.create_all()db.drop_all()更新数据库的时候丢失数据的问题。

flask官方提供了一个扩展组件flask-script可以实现在shell下操作我们的Flask项目。

1.1 flask-script的用法:

1 由于flask-script是Flask的一个扩展组件,同往常一样首先在虚拟环境中安装我们的flask_script包。

pip install flask-script

1.1.1 实例:flask-script的简单实现

提示:实例下面有讲解

项目目录

│  manage.py
│  server.py
│
├─static  # 文件夹
├─templates # 文件夹

server.py

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


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

manage.py

from flask_script import Manager
from server import app

manager = Manager(app)

@manager.command
def hello():
    print('hello world')


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

解读:manage.py

(1)flask_script模块中导入flask_script的核心类Manager

from flask_script import Manager 

(2)server.py模块中把app对象导入

from server import app

(3)Manager()类传入app对象实例化出manager对象,manager对象用于以后所有添加命令相关操作

manager = Manager(app)

(4)利用@manager.command装饰器添加以被装饰函数的名字命名的一条命令被装饰函数的映射

@manager.command   # 相当于添加了一条hello命令,可以调用到hello函数
def hello():
    print('hello world')

(5)manager调用run方法之前定义的命令才会生效

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

shell下操作命令

shell中切入到该manage.py的目录下,并且进入虚拟环境。输入命令python manage.py hello

>>python manage.py hello

命令中的hello@manager.command装饰器装饰的函数名

执行命令后会调用hello函数

如图所示实现了调用hello函数

1.1.1命令添加方式:

第一种(无参命令):

使用manager.commad方式添加命令:

...
@manager.command
def demo():
    print('无参命令')
...

切入到manage.py所在的目录中,切入到虚拟环境,执行如下命令

>>python manage.py demo

第二种(有参命令):

使用manager.option('-简写的命令',‘--全写的命令’,dest=‘传给函数的形参’)添加命令:

...
@manager.option("-u","--username",dest="username")
@manager.option("-p","--password",dest="password")
def login(username, password):
    print("用户名:{}  密码: {}".format(username,password))
...

切入到manage.py所在的目录中,切入到虚拟环境,执行如下命令.

>>python manage.py login -u mark -p 123

第三种(子命令):

比如一个功能对应着很多个命令,这个时候就可以用子命令来实现,可以将这些命令的映射单独放到一个文件方便管理。在这个放着很多命令映射的文件中实例化Manager类出一个新的对象,并在manage.py文件中通过manager.add_command("子命令",Manager对象)来添加子命令

实例:

在之前的1.1.1实例的项目目录中新建文件db_script.py

│  manage.py
│  server.py
│  db_script.py
│
├─static  # 文件夹
├─templates # 文件夹

db_script.py

from flask_script import Manager

db_Manager = Manager()

@db_Manager.command
def init():
    print('初始迁移仓库')

@db_Manager.command
def migrate():
    print('生成迁移脚本')

@db_Manager.command
def upgrade():
    print("迁移脚本映射到数据库")

manage.py

from flask_script import Manager
from server import app
from db_script import db_Manager # 导入子命令文件的Manager类实例化出的对象
manager = Manager(app)

manager.add_command("db",db_Manager) # 添加子命令

...

切入到manage.py所在的目录中,切入到虚拟环境,执行如下命令.

python manage.py db init
python manage.py db migrate
python manage.py db upgrade

2 项目重构

2.1 解耦配置信息以及模型文件信息触发循环导入问题

随着项目代码的增多 我们再把连接数据库的信息放到主app文件当中会应影响代码的可读性,那么我们相关数据库配置的信息应该放到一个config文件当中去,像当时加载debug配置一样使用app.config.from_object(config)一样加载数据库连接信息。

新建config.py文件,把连接数据库相关的信息放到config.py中去

然后在主app文件中加载配置信息app.config.from_object(config)

config.py

HOST = '127.0.0.1'
PORT = '3306'
DATABASE_NAME = '01_db'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DATABASE_NAME}?charset=utf8mb4"

SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False

那么主app中的模型的文件也十分影响代码易读性,也应该新开一个modles文件夹,把模型表放到modles中去

models.py

from app import db

class UserInfo(db.Model):
    id = db.Column(db.Integer,primary_key=True,autoincrement=True,nullable=False)
    name = db.Column(db.String(30),server_default='',comment="姓名")
    tel = db.Column(db.String(16),server_default='',comment="电话")

app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config
from models import UserInfo

app = Flask(__name__)
app.config.from_object(config)

db = SQLAlchemy(app)

@app.route('/')
def hello_world():
    return 'Hello World!'


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

这是代码易读性提高了,但是新的问题随之出现了,出现了一个循环导入的问题。

app.py 文件导入了modelspython中而导入文件必然会把需要导入的文件从上到下执行一遍,那么就触发了models的执行,而models执行的时候需要从app导入db,出现了一个死循环如下图,这就是python循环导入的问题。

2.2 重构项目解决循环导入问题

为了解耦配置信息以及模型表信息,导致了models.pyapp.py出现了循环导入问题,解决方案是新开启一个文件exts.py,在exts.py中生成db对象,解决循环导入问题。

实例2.2.1:解决循环导入问题之后重构项目

项目目录:

│  app.py
│  config.py
│  exts.py
│  models.py
│
├─static 	# 文件夹
├─templates  # 文件夹

config.py

HOST = '127.0.0.1'
PORT = '3306'
DATABASE_NAME = '01_db'
USERNAME = 'root'
PASSWORD = 'root'

DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DATABASE_NAME}?charset=utf8mb4"


SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False

exts.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

models.py

from exts import db

class UserInfo(db.Model):
    __tablename__ = 'user_info'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(20),nullable=False,server_default='')

app.py

from flask import Flask
from exts import db
import config
from models import UserInfo

app = Flask(__name__)
app.config.from_object(config)

db.init_app(app)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

3 使用Flask-Migrate迁移数据库

之前更新数据库的方式是先删除表然后再创建表简单粗暴,但是会丢失掉所有原来表中的数据。做web开发的应该深知数据无价,所以这个时候需要数据库迁移工具来完成这个工作,SQLAlcheme的开发者Michael Bayer开发了一个数据库迁移工具---Alembic来实现数据库的迁移,SQLAlchemy翻译成汉语是炼金术,而蒸馏器(Alembic)正是炼金术士最需要的工具。

flask-sqlalchmy扩展组件正是基于SQLAlchemy,当然Flask也有专门做数据库迁移的扩展组件Flask-Migrate,同样Flask-Migrate正是基于Alembic

3.1 Flask-Migrate的用法:

1 由于flask-migrate是Flask的一个扩展组件,同往常一样首先在虚拟环境中安装我们的flask_migrate包。

pip install flask-migrate

为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可附加到Flask-Script的manager对象上。在这个例子中,MigrateCommand类使用db命令附加。

Flask_Migrate的操作是在shell下完成的,所以要基于Flask-scriptFlask-Migrate提供了一个MigrateCommand类,需要附加到Flask-Scriptmanager对象上,完成命令的创建,并且Flask_Migrate同时体统了Migrate类,需要加载核心对象app和数据库对象db。完成迁移工具的配置。

实例3.1.1:配置Flask_Migrate

首先在实例2.2.1中创建manage.py

manage.py代码如下

from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from exts import db
from app import app
manager = Manager(app)


Migrate(app,db)

manager.add_command('db',MigrateCommand)

解读:

(1) 首先从flask_migrate中导入 Migrate,MigrateCommand

from flask_migrate import Migrate,MigrateCommand

(2)Migrate加载app对象和db对象获取数据库的配置信息以及模型表信息。

Migrate(app,db)

(3)MigrateCommand附加到manager创建迁移数据库的子命令

manager.add_command('db',MigrateCommand)

迁移脚本命令

(1) 创建迁移仓库

首先切换到项目目录下并且切入到虚拟环境中输入命令python manage.py db init

>> python manage.py db init

该命令初始化迁移仓库,并且在项目目录中创建迁移仓库文件

(2) 创建迁移脚本

依然在shell中输入命令python manage.py db migrate

>> python manage.py db migrate

该命令会在数据库创建一张 alembic_version 表,存放着数据库迁移脚本的版本信息,该命令会搜集到需要迁移的模型表信息,写入到脚本中,但是并没有真正的映射到数据库中。

(3)更新数据库

依然在我们的shell中输入命令python manage.py db upgrade

python manage.py db upgrade

对于第一次迁移来说,其作用和db.create_all()方法一样,但是在随后的迁移中,upgrade命令可以把模型表改动的部分映射到数据库中,实现了一个更新的效果,并且不影响之前保存的数据。

提示:在首次执行这个命令之前如果该数据库的库内已经有了一些表,并且这些表没有与模型映射,会自动删除掉这些表。

原文地址:https://www.cnblogs.com/remixnameless/p/13290551.html