第六章 Flask数据库(二)

Flask-SQLALchemy 

Flask-SQLALchemy 是一个给你的应用添加 SQLALchemy 支持的 Flask 扩展。

它需要 SQLAlchemy 0.6 或更高的版本。它致力于简化在 Flask 中 SQLAlchemy 的 使用,提供了有用的默认值和额外的助手来更简单地完成日常任务。

我的conda源没有,我就直接pip3

 

数据库连接:

1. 与sqlalchemy一样,定义好数据库连接字符串DB_URI。
2. 将这个定义好的数据库连接字符串DB_URI,通过SQLALCHEMY_DATABASE_URI这个键放到app.config中。示例代码:app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI.
3. 使用flask_sqlalchemy.SQLAlchemy类定义一个对象,并将app传入进去。示例代码:db = SQLAlchemy(app)。

创建ORM模型:

1. 与使用sqlalchemy一样,定义模型。现在不需要使用delarative_base来创建一个基类,而是使用db.Model来作为基类。
2. 在模型类中,Column、String、Integer以及relationship等,都不需要导入了,直接使用db下面相应的属性名就可以了。
3. 在定义模型的时候,可以不写__tablename__,那么flask_sqlalchemy会默认使用如果不设置该属性,类名小写作表名,
并且如果这个模型的名字使用多个单词且是驼峰命名法,则多个单词之间使用下划线来进行连接。

将ORM模型映射到数据库:

1. db.drop_all()
2. db.create_all()

使用session:

session也不需要使用sessionmaker来创建了。直接使用db.session且操作方式与SQLAl相同

查询数据:

如果查找数据只是查找一个模型上的数据,可以直接通过模型.query的方式进行查找。query就跟之前的sqlalchemy中的query方法是一样用的。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'flask_sqlalchemy'
USERNAME = 'root'
PASSWORD = 'root'

# dialect+driver://username:password@host:port/database
DB_URI = "mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset=utf8mb4".format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)

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

db = SQLAlchemy(app)

# 明言胜于暗喻

class User(db.Model):
    #__tablename__ = 'user' 如果不设置该属性,类名小写作表名
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(50),nullable=False)

    def __repr__(self):
        return "<User(username: %s)>" % self.username

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(50),nullable=False)
    uid = db.Column(db.Integer,db.ForeignKey("user.id"))

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

db.drop_all()
db.create_all()

user = User(username='wqbin')
article = Article(title='title one')
sess=db.session

article.author = user
sess.add(article)

sess.commit()

users = User.query.order_by(User.id.desc()).all()
print(users)

user = User.query.filter(User.username=='wqbin').first()
user.username='wqbin1'

sess.commit()


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


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

alembic 

SQLAlchemy该ORM框架本身没有带数据库版本控制功能,但是进行开发过程中难免修改数据模型,添加表,修改字段,都需要手动修改。于是两个解决该缺陷的两个工具应运而生,alembic与SQLAlchemy-Migrate。

alembic是sqlalchemy的作者开发的,Alembic 使用 SQLAlchemy 作为数据库引擎,为关系型数据提供创建、管理、更改和调用的管理脚本,协助开发和运维人员在系统上线后对数据库进行在线管理,主要用来做ORM模型与数据库的迁移与映射。

alembic使用方式跟git类似,表现在两个方面

  1. alembic的所有命令都是以alembic开头;
  2. alembic的迁移文件也是通过版本进行控制的。

以下将解释alembic的用法:

  1. 初始化alembic仓库:在终端中cd到项目目录中,然后执行命令alembic init alembic,创建alembic的仓库。
  2. 创建模型类:创建一个models.py模块,然后在里面定义模型类。
  3. 修改配置文件alembic.init
  4. 修改env.py
  5. 自动生成迁移文件:使用alembic revision --autogenerate-m "message"将当前模型中的状态生成迁移文件。
  6. 更新数据库:使用alembic upgrade head将刚刚生成的迁移文件,真正映射到数据库中。同理,如果要降级,那么使用alembic downgrade head。
  1. 修改代码后,重复4~5的步骤。

细节如下:

1.alembic init alembic

2.alembic.init

from sqlalchemy import Column,String,Integer,create_engine
from sqlalchemy.ext.declarative import declarative_base

DB_USERNAME = 'root'
DB_PASSWORD = 'root'
DB_HOST = '127.0.0.1'
DB_PORT = '3306'
DB_NAME = 'alembic'

DB_URI = 'mysql+pymysql://%s:%s@%s:%s/%s?charset=utf8' % (DB_USERNAME,DB_PASSWORD,DB_HOST,DB_PORT,DB_NAME)

engine = create_engine(DB_URI)

Base = declarative_base(engine)

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)
    country = Column(String(50))

3.修改配置文件alembic.init

【alembic】
 sqlalchemy.url = driver://user:pass@localhost/dbname
改成:
【alembic】
 sqlalchemy.url = mysql+pymysql://root:root@localhost/alembic?charset=utf8

4.修改env.py

from __future__ import with_statement

from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

import sys,os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import models
# this is the Alembic Config object, which provides access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging. This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here for 'autogenerate' support from myapp import mymodel target_metadata = mymodel.Base.metadata
target_metadata = models.Base.metadata

 5.使用alembic revision --autogenerate-m "aessage"将当前模型中的状态生成迁移文件

在flask中进行数据库迁移时报错,报错信息为"Target database is not up",解决方案如下:

方法一:(网上看的没找到对应)

  1. 找到alembic(数据库中的数据表)的最新版本号,找到文件夹verisons下的最新版本,文件名即为最新版本号(去掉末尾的_)。
  2. 然后更新数据库表alembic_version里version_num的字段,将该字段的值改为最新版本号
  3. 再次迁移即可成功

方法二:

  1.  删除数据库表alembic_version表和versions下面所有版本文件
  2.  重新输入revision命令

 

 message.py文件内容:

 5.更新数据库:使用alembic upgrade head将刚刚生成的迁移文件,真正映射到数据库中

 6.重复:修改文件增加字段,更新数据库

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)
    country = Column(String(50))
    city = Column(String(50))

 

 

几点补充:

PS C:UsersWQBinDesktoppython_flask> alembic heads
dda532d698cb (head)
PS C:UsersWQBinDesktoppython_flask> alembic upgrade 7ddd5756b83f
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
ERROR [alembic.util.messaging] Destination 7ddd5756b83f is not a valid upgrade target from current head(s)
FAILED: Destination 7ddd5756b83f is not a valid upgrade target from current head(s)

PS C:UsersWQBinDesktoppython_flask> alembic downgrade dda532d698cb
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.

当head指针和current指针不对齐,不及时做upgrade 和downgrade,再做一些其他操作则会报错。

经典错误

1. FAILED: Target database is not up to date.
     原因:主要是heads和current不相同。current落后于heads的版本。
     解决办法:将current移动到head上。alembic upgrade head
2. FAILED: Can't locate revision identified by 'xxxxxx'
     原因:数据库中存的版本号不在迁移脚本文件中
     解决办法:删除数据库的alembic_version表中的数据,重新执行alembic upgrade head
3. 执行`upgrade head`时报某个表已经存在的错误:
     原因:执行这个命令的时候,会执行所有的迁移脚本,因为数据库中已经存在了这个表。然后迁移脚本中又包含了创建表的代码。
     解决办法:(1)删除versions中所有的迁移文件。(2)修改迁移脚本中创建表的代码

常用参数介绍:

init:创建一个alembic仓库
revision:创建一个新的版本文件
--autogenerate:自动将当前模型的修改,生成迁移脚本
-m:本次迁移做了哪些修改,用户可以指定这个参数,方便回顾
upgrade:将指定版本的迁移文件映射到数据库中,会执行版本文件中的upgrade函数。如果有多个迁移脚本没有被映射到数据库中,那么会执行多个迁移脚本
[head]:代表最新的迁移脚本的版本号
downgrade:会执行指定版本的迁移文件中的 down grade函数
heads:展示head指向的脚本文件版本号
history:列出所有的迁移版本及其信息
curent:展示当前数据库中的版本号

 Flask-Script:

[flask-script 不再维护了,官网推荐使用flask自带cli]

简介与安装

 Flask-Script的作用是可以通过命令行的形式来操作Flask,Flask-Script的作用从某种意义上来说是为了更好的管理项目,它通过一个manager来作为脚本控制整个项目的各个小部分点。

例如通过命令跑开发版本的服务器、设置数据库,定时任务等。  Flask-Script和Flask本身的工作方式类似,只需要定义和添加能从命令行中被Manager实例调用的命令即可。

Flask的开发Web服务器支持很多启动设置选项,但只能在脚本中作为参数传给app.run()函数。这种方式很不方便,传递设置选项的理想方式是使用命令行参数。

Flask-Scrip就是这么一个Flask扩展,为Flask程序添加一个命令行解析器。Flask-Script自带了一组常用选项,而且还支持自定义命令。

定义命令的三种方法

  • 使用@command 装饰器
  • 使用类继承自Command类
  • 使用option装饰器

1.使用@command 装饰器

把脚本命令代码放在一个叫做manage.py文件中,然后在终端运行python manage.py hello命令,就可以看到输出hello。

from flask_script import Manager
from flask_script_app import app


manager = Manager(app)

@manager.command
def greet():
    print('你好')
if __name__ == '__main__':
    manager.run()

2.使用类继承自Command类

      使用类的方式,有三点需要注意:

  1. 必须继承自Command基类
  2. 必须实现run方法
  3. 必须通过add_command 方法添加命令
from flask_script import Manager,Command

manager = Manager(app)
class SayHi(Command):

    def run(self):
        print('你好from [继承command的类方法]')

manager.add_command("sayhi", SayHi)

3.使用option装饰器:如果想要在使用命令的时候还传递参数进去,那么使用@option 装饰器更加的方便:

@manager.option("-u","--username",dest="username")
@manager.option("-e","--email",dest="email")
def add_user(username,email):
    print(username,email)

小案例:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

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

class BackendUser(db.Model):
    __tablename__ = 'backend_user'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(50),nullable=False)
    email = db.Column(db.String(50),nullable=False)

db.create_all()

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


if __name__ == '__main__':
    app.run()
flask_script_app.py
DB_USERNAME = 'root'
DB_PASSWORD = 'root'
DB_HOST = '127.0.0.1'
DB_PORT = '3306'
DB_NAME = 'flask_script'

DB_URI = 'mysql+pymysql://%s:%s@%s:%s/%s?charset=utf8' % (DB_USERNAME,DB_PASSWORD,DB_HOST,DB_PORT,DB_NAME)

SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
config.py
from flask_script import Manager

db_manager = Manager()

@db_manager.command
def init():
    print('迁移仓库创建完毕!')

@db_manager.command
def revision():
    print('迁移脚本生成成功!')

@db_manager.command
def upgrade():
    print('脚本映射到数据库成功!')
db_script.py
from flask_script import Manager,Command
from flask_script_app import app,BackendUser,db
from db_script import db_manager

manager = Manager(app)
manager.add_command("db",db_manager)

@manager.command
def greet():
    print('你好')



manager = Manager(app)
class SayHi(Command):

    def run(self):
        print('你好from [继承command的类方法]')

manager.add_command("sayhi", SayHi)


@manager.option("-u","--username",dest="username")
@manager.option("-e","--email",dest="email")
def add_user(username,email):
    user = BackendUser(username=username,email=email)
    db.session.add(user)
    db.session.commit()


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

 python manage.py db --help
usage: Perform database migrations

Perform database migrations

positional arguments:
  {init,revision,migrate,edit,merge,upgrade,downgrade,show,history,heads,branches,current,stamp}
    init                Creates a new migration repository
    revision            Create a new revision file.
    migrate             Alias for 'revision --autogenerate'
    edit                Edit current revision.
    merge               Merge two revisions together. Creates a new migration
                        file
    upgrade             Upgrade to a later version
    downgrade           Revert to a previous version
    show                Show the revision denoted by the given symbol.
    history             List changeset scripts in chronological order.
    heads               Show current available heads in the script directory
    branches            Show current branch points
    current             Display the current revision for each database.
    stamp               'stamp' the revision table with the given revision;
                        don't run any migrations

optional arguments:
  -?, --help            show this help message and exit
View Code

 

Flask-Migrate

在实际的开发环境中,经常会发生数据库修改的行为。一般我们修改数据库不会直接手动的去修改,而是去修改ORM对应的模型,然后再把模型映射到数据库中。

这时候如果有一个工具能专门做这种事情,就显得非常有用了,而flask-migrate就是做这个事情的。

flask-migrate是基于Alembic进行的一个封装,并集成到Flask中,而所有的迁移操作其实都是Alembic做的,他能跟踪模型的变化,并将变化映射到数据库中。

what is Flask-Migrate

Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic.

The database operations are made available through the Flask command-line interface or through the Flask-Script extension.

Flask-Migrate是一个扩展,使用 Alembic 处理 Flask 程序的 SQLAlchemy 数据库迁移。数据库操作通过 Flask 命令行界面或 Flask-Script 扩展来提供。

Why Use Flask-Migrate vs. Alembic Directly?

Flask-Migrate is an extension that configures Alembic in the proper way to work with your Flask and Flask-SQLAlchemy application.

In terms of the actual database migrations, everything is handled by Alembic so you get exactly the same functionality.

 

 案例讲解:

1.flask db init

add a migrations folder to your application. The contents of this folder need to be added to version control along with your other source files.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

db = SQLAlchemy(app)
migrate = Migrate(app, db)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))

 2.flask db migrate

 生成app.db文件

3.最后添加将映射文件真正的映射到数据库中

4.假设model发生变化,我们重复2-3

flask db migrate  //生成version文件
flask db update   //更新到新版本

 

 经典案例:

 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

 发现错误:

我看的教程中使用的python manage.py db xxx

但实际上是应该使用flask db 其中我遇到了两个错误

1.did not provide the "FLASK_APP" environment variable

>flask db init
报错如下:
Usage: flask-script.py db init [OPTIONS]
Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.

两个方法一个是把flask入口声明 set  FLASK_APP=xxxx.py 另一个把flask入口重命名为app.py

2.KeyError: 'migrate'

>flask db init
报错如下:
File "D:appAnacondalibsite-packagesflask_migrate\__init__.py", line 125, in init directory = current_app.extensions['migrate'].directory KeyError: 'migrate'

网上的教程我都没成功:

方案一:https://blog.csdn.net/aimill/article/details/80867402

方案二:https://github.com/miguelgrinberg/Flask-Migrate/issues/196

原文地址:https://www.cnblogs.com/wqbin/p/11965888.html