文章目录编排如下:
1 引言
2 使用sqlalchemy实现数据库增删改查
3 使用alembic进行数据库升级
4 总结
1 引言
sqlalchemy是python项目采用的ORM(对象关系映射),主要用于数据库相关的操作。
而alembic是与sqlalchemy搭配使用的数据库升级工具,主要用于数据库相关表结构的修改升级。
基于这篇文章:
python 64式: 第18式、python项目通用rest api框架搭建与编写
的基础上,介绍编写sqlalchemy与alembic部分。
前提:已经创建了项目myproject
2 使用sqlalchemy实现数据库增删改查
2.1 在myproject下面创建db目录
2.2 db目录下创建__init__.py文件,文件内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import retrying
import six.moves.urllib.parse as urlparse
from stevedore import driver
G_NAMESPACE = "myproject.storage"
def getConnectionFromConfig(conf):
url = conf.database.connection
connectionScheme = urlparse.urlparse(url).scheme
mgr = driver.DriverManager(G_NAMESPACE, connectionScheme)
retries = conf.database.max_retries
@retrying.retry(wait_fixed=conf.database.retry_interval * 1000,
stop_max_attempt_number=retries if retries >= 0 else None)
def getConnection():
return mgr.driver(conf)
connection = getConnection()
return connection
分析:
1) 这里最重要的代码如下:
mgr = driver.DriverManager(G_NAMESPACE, connectionScheme)
主要是采用stevedore.driver.DriverManager来获取对应的数据库插件。
stevedore.driver.DriverManager :一个名字对应一个entry point。根据插件命名空间和名字,定位到单独插件
stevedore.driver.DriverManager(namespace, name, invoke_on_load, invoke_args=(), invoke_kwds={})
namespace: 命名空间
name: 插件名称
invoke_on_load:如果为True,表示会实例化该插件的类
invoke_args:调用插件对象时传入的位置参数
invoke_kwds:传入的字典参数
2.3 设定数据库插件
修改myproject/setup.cfg中的内容为如下内容:
[metadata]
name = myproject
version = 1.0
summary = myproject
description-file =
README.rst
author = me
author-email =
classifier =
Intended Audience :: Developers
Programming Language :: Python :: 2.7
[global]
setup-hooks =
pbr.hooks.setup_hook
[files]
packages =
myproject
data_files =
/etc/myproject = etc/myproject/*
/var/www/myproject = etc/apache2/app.wsgi
/etc/httpd/conf.d = etc/apache2/myproject.conf
[entry_points]
wsgi_scripts =
myproject-api = myproject.api.app:build_wsgi_app
console_scripts =
myproject-dbsync = myproject.cmd.storage:dbsync
oslo.config.opts =
myproject = myproject.opts:list_opts
myproject.storage =
mysql = myproject.db.mariadb.impl_mariadb:Connection
mysql+pymysql = myproject.db.mariadb.impl_mariadb:Connection
分析:
1) 其中最重要的内容是在[entry_points]下面新增了:
myproject.storage =
mysql = myproject.db.mariadb.impl_mariadb:Connection
mysql+pymysql = myproject.db.mariadb.impl_mariadb:Connection
来表示myproject.storage的命名空间下,mysql或者mysql+pymysql这个插件名称对应的
处理对象是: myproject.db.mariadb.impl_mariadb下面的Connection类
2) 插件格式
命名空间 =
插件名称=模块:可导入对象
2.4 定义数据库模型
在db目录下新建models.py,具体内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from sqlalchemy import Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Index
from sqlalchemy import Integer
from sqlalchemy import PrimaryKeyConstraint
from sqlalchemy import String
Base = declarative_base()
class Student(Base):
__tablename__ = 'student'
id = Column(Integer(), nullable=False)
userId = Column(String(256), nullable=False)
name = Column(String(256), nullable=False)
age = Column(Integer(), nullable=True)
email = Column(String(256), nullable=True)
__table_args__ = (
Index('ix_student_userId', 'userId'),
PrimaryKeyConstraint('id')
)
TRANSFORMED_FIELDS = ['id', 'userId', 'name', 'age', 'email']
# from database model to dict
def as_dict(self):
result = dict()
for field in Student.TRANSFORMED_FIELDS:
result[field] = getattr(self, field, '')
return result
分析:
1) as_dict方法主要是用于将models.Student对象转换为字典
2.5 为请求绑定database hook, config hook
2.5.1 在myproject的api目录下新建hooks.py
具体内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pecan import hooks
class ConfigHook(hooks.PecanHook):
"""Attach config information to the request
"""
def __init__(self, conf):
self.conf = conf
def before(self, state):
state.request.cfg = self.conf
class DatabaseHook(hooks.PecanHook):
"""Attach database information to the request
"""
def __init__(self, conn):
self.storage = conn
def before(self, state):
state.request.storage = self.storage
分析:
1) 这个hook在每个请求进来的时候实例化一个db的Connection对象,然后在controller代码中我们可以直接使用这个Connection实例
具体参考:
https://pecan.readthedocs.io/en/latest/hooks.html
hook的作用:
pecan hooks是一种可以与框架交互的方式,不需要编写单独的中间件。
Hooks允许你在请求生命周期的关键时间点去执行代码:
on_route(): called before Pecan attempts to route a request to a controller
before(): called after routing, but before controller code is run
after(): called after controller code has been run
on_error(): called when a request generates an exception
参考:
https://www.sqlalchemy.org/library.html#tutorials
https://segmentfault.com/a/1190000004466246
2)修改myproject的api目录下app.py
内容为如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from oslo_config import cfg
from oslo_log import log
from paste import deploy
import pecan
from myproject.api import hooks
from myproject import service
from myproject import db
PECAN_CONFIG = {
'app': {
'root': 'myproject.api.controllers.root.RootController',
'modules': ['myproject.api'],
},
}
LOG = log.getLogger(__name__)
def app_factory(global_config, **local_config):
print "######### enter app_factory"
conf = service.prepareService()
# NOTE, add config and databse information to the request
# by using pecan.hooks.PecanHook
configHook = hooks.ConfigHook(conf)
conn = db.getConnectionFromConfig(conf)
databaseHook = hooks.DatabaseHook(conn)
appHooks = [configHook, databaseHook]
# NOTE, it needs add the line below
pecan.configuration.set_config(dict(PECAN_CONFIG), overwrite=True)
app = pecan.make_app(
PECAN_CONFIG['app']['root'],
hooks=appHooks
)
return app
def getUri(conf):
# TODO(), it needs to get real path of api-paste.ini
# the path is setted by the data_files under [files] in setup.config
# configPath = "/etc/myproject/api-paste.ini"
# find the absolute path of api-paste.ini
cfgFile = None
apiPastePath = conf.api.paste_config
if not os.path.isabs(apiPastePath):
cfgFile = conf.find_file(apiPastePath)
elif os.path.exists(apiPastePath):
cfgFile = apiPastePath
if not cfgFile:
raise cfg.ConfigFilesNotFoundError([conf.api.paste_config])
LOG.info("The wsgi config file path is: %s" % (cfgFile))
result = "config:" + cfgFile
return result
def getAppName():
return "main"
def build_wsgi_app():
print "######### enter build_wsgi_app"
conf = service.prepareService()
uri = getUri(conf)
appName = getAppName()
app = deploy.loadapp(uri, name=appName)
return app
分析:
1) 在app_factory方法中,设置了
configHook = hooks.ConfigHook(conf)
conn = db.getConnectionFromConfig(conf)
databaseHook = hooks.DatabaseHook(conn)
appHooks = [configHook, databaseHook]
# NOTE, it needs add the line below
pecan.configuration.set_config(dict(PECAN_CONFIG), overwrite=True)
app = pecan.make_app(
PECAN_CONFIG['app']['root'],
hooks=appHooks
)
主要的目的就是让请求进入的时候可以绑定数据库连接对象和配置对象,方便后面进行处理
2.6 编写数据库连接的基类
在db目录下新建base.py,具体内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class Connection(object):
def __init__(self, conf, url):
pass
def upgrade(self):
""" Migrate database """
def getStudents(self):
""" Get student list """
def createStudent(self):
""" Create a student"""
def updateStudent(self):
""" Update a student"""
def deleteStudent(self):
""" Delere a student """
2.7 实现数据库连接的子类
在db目录下新建mariadb目录,
2.7.1 在mariadb目录下新建__init__.py
内容为空
2.7.2 在mariadb目录下新建impl_mariadb.py
内容具体如下所示:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from alembic import command
from alembic import config
from alembic import migration
from oslo_db.sqlalchemy import session as dbSession
from myproject.db import base
from myproject.db import models
class Connection(base.Connection):
def __init__(self, conf):
self.conf = conf
options = dict(conf.database)
options['max_retries'] = 0
self.engineFacade = dbSession.EngineFacade(
conf.database.connection,
**options)
def getAlembicConfig(self):
dirName = os.path.dirname(__file__)
realDirName = os.path.dirname(dirName)
path = "%s/migrate/alembic.ini" % (realDirName)
cfg = config.Config(path)
cfg.set_main_option(
'sqlalchemy.url', self.conf.database.connection)
return cfg
def upgrade(self, noCreate=False):
# import pdb;pdb.set_trace()
cfg = self.getAlembicConfig()
cfg.conf = self.conf
if noCreate:
command.upgrade(cfg, "head")
else:
engine = self.engineFacade.get_engine()
ctxt = migration.MigrationContext.configure(engine.connect())
currentVersion = ctxt.get_current_revision()
if currentVersion is None:
models.Base.metadata.create_all(engine, checkfirst=False)
command.stamp(cfg, "head")
else:
command.upgrade(cfg, "head")
@staticmethod
def rowToStudentModel(row):
result = models.Student(
id=row.id,
userId=row.userId,
name=row.name,
age=row.age,
email=row.email
)
return result
def retrieveStudents(self, query):
return (self.rowToStudentModel(obj) for obj in query.all())
def getStudents(self, id=None, userId=None, name=None,
age=None, email=None, pagination=None):
# NOTE, pagination is needed
pagination = pagination or {}
session = self.engineFacade.get_session()
query = session.query(models.Student)
if id is not None:
query = query.filter(models.Student.id == id)
if userId is not None:
query = query.filter(models.Student.userId == userId)
if name is not None:
query = query.filter(models.Student.name == name)
if age is not None:
query = query.filter(models.Student.age == age)
if email is not None:
query = query.filter(models.Student.email == email)
# TODO(), add pagination query
students = self.retrieveStudents(query)
return students
def createStudent(self, obj):
session = self.engineFacade.get_session()
row = models.Student(
userId=obj.userId,
name=obj.name,
age=obj.age,
email=obj.email
)
with session.begin():
session.add(row)
return row
def updateStudent(self, obj):
session = self.engineFacade.get_session()
with session.begin():
count = session.query(models.Student).filter(
models.Student.id == obj.id).update(
obj.as_dict()
)
if not count:
raise "Not found student with id: %s" % obj.id
return obj
def deleteStudent(self, id):
session = self.engineFacade.get_session()
with session.begin():
session.query(models.Student).filter(
models.Student.id == id).delete()
分析:
1) upgrade(self, nocreate=False):
作用: 通过alembic判断是否有版本,如果没有版本就创建所有的表,并升级到最新版本;
如果有版本就直接升级到最新版本
处理过程:
步骤1: 获取alembic的配置对象,设置alembic.ini中sqlalchemy.url为当前数据库连接串
步骤2: 如果不创建表,就直接升级到最新版本;否则,执行步骤3
步骤3: 升级数据库版本,具体如下:
步骤3.1: 根据 oslo_db.sqlalchemy.session.EngineFacade对象获取engine
步骤3.2: 获取enine.connect()做为参数传递给alembic.migration.MigrationContext.configure来得到
数据库迁移上下文对象
步骤3.3: 获取数据库迁移上下文对象的当前版本
步骤3.3.1: 如果当前版本不存在,就通过models.Base.metadata.create_all(engine)创建所有表,
并通过alembic.command.stamp(cfg, 'head')给当前版本打上标记
否则,执行3.3.2
步骤3.3.2: 直接执行alembic.command.upgrade(cfg, 'head')升级到最新版本
2) _get_alembic_config(self):
作用: 初始化alembic.ini的配置对象,并设置alembic.ini中sqlalchemy.url的值为配置文件中[database]下面
的connection对应的值,返回该配置对象
3) oslo_db.sqlalchemy.session.EngineFacade
作用:
1 类似SQLAlchemy Engine和Session对象的网关(外观模式,类似一个代理)。
得到该对象后
2 管理数据库连接,会话,事务
本质: 单例,具体参见C++如何获取一个单例的写法
用法:
1 init函数初始化获得
_FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade(
CONF.database.connection,
**dict(CONF.database.iteritems())
)
return _FACADE
2 从配置文件初始化获得
_ENGINE_FACADE = None
_LOCK = threading.Lock()
def _create_facade_lazily():
global _LOCK, _ENGINE_FACADE
if _ENGINE_FACADE is None:
with _LOCK:
if _ENGINE_FACADE is None:
_ENGINE_FACADE = db_session.EngineFacade.from_config(CONF)
return _ENGINE_FACADE
参考:
https://specs.openstack.org/openstack/oslo-specs/specs/kilo/make-enginefacade-a-facade.html
https://blog.csdn.net/bill_xiang_/article/details/78592389
3 使用alembic进行数据库升级
3.1 在myproject下新建cmd目录
cmd目录存放各个脚本或者服务的启动代码。
3.1.1 在cmd目录下新建__init__.py文件
内容设置为空
3.1.2 在cmd目录下新建storage.py文件
内容设置为如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from myproject import service
from myproject import db
def dbsync():
conf = service.prepareService()
db.getConnectionFromConfig(conf).upgrade()
分析:
1) service.prepareService()是获取了oslo.config对象
2) db.getConnectionFromConfig(conf).upgrade()
这句话就是读取数据库连接串根据调用对应的数据库插件对象的upgrade方法来
进行数据库升级
3) 数据库升级的代码实际就是myproject/db/mariadb/impl_mariadb.py
的Connection类的upgrade方法,具体如下:
def upgrade(self, noCreate=False):
# import pdb;pdb.set_trace()
cfg = self.getAlembicConfig()
cfg.conf = self.conf
if noCreate:
command.upgrade(cfg, "head")
else:
engine = self.engineFacade.get_engine()
ctxt = migration.MigrationContext.configure(engine.connect())
currentVersion = ctxt.get_current_revision()
if currentVersion is None:
models.Base.metadata.create_all(engine, checkfirst=False)
command.stamp(cfg, "head")
else:
command.upgrade(cfg, "head")
3.2 设定数据库升级脚本
具体参见myproject下的setup.cfg中
[entry_points]
下有如下一行内容
console_scripts =
myproject-dbsync = myproject.cmd.storage:dbsync
这表示会生成一个myproject-dbsync的脚本,调用
myproject/cmd/storage.py中的dbsync方法,
该方法的具体定义请参见3.1.2
3.3 创建alembic配置
3.3.1 alembic生成数据库迁移环境
进入到db/sqlalchemy目录下执行
alembic init alembic
3.3.2 进入到db/sqlalchemy目录下执行
alembic revision -m "Create student table"
在db/sqlalchemy/alembic/versions 目录下生成一个文件
xxx_create_student_table.py
然后向该文件中填入创建表操作的信息,具体样例信息内容如下:
"""Create student table
Revision ID: 677de41920e9
Revises:
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '677de41920e9'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'student',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('userId', sa.String(256), nullable=False),
sa.Column('name', sa.String(128), nullable=False),
sa.Column('age', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(
'ix_student_userId', 'student', ['userId'], unique=True)
def downgrade():
op.drop_table('student')
解释:
alembic init :创建一个alembic仓库
alembic revision -m '提交信息': 创建一个新的版本文件
alembic upgrade: 升级命令
参考:
https://www.baidu.com/link?url=Aad8bvURqUkaCJxNXjmIRKdzKE5XZrP02j_m9nndmJdhW6NE-p5jRAWYU5xY3HW5gQu9LlI52mSwJCFvniNWwq&wd=&eqid=da2ccd990001d02d000000055bfe26a4
3.4 生成myproject-dbsync文件
具体在myproject目录下执行:
python setup.py install
如果是centos环境,那么会在/usr/bin目录下生成myproject-dbsync文件
3.5 修改配置文件中的数据库连接串
3.5.1 生成配置文件(如果有,就不要执行这一步)
找到myproject-config-generator.conf文件,该文件内容如下:
[DEFAULT]
output_file = /etc/myproject/myproject.conf
wrap_width = 79
namespace = myproject
namespace = oslo.db
namespace = oslo.log
namespace = oslo.messaging
namespace = oslo.policy
执行命令:
oslo-config-generator --config-file=myproject-config-generator.conf
生成myproject.conf文件,
3.5.2 找到myproject.conf里面的
[database]
下面的
# The SQLAlchemy connection string to use to connect to the database. (string
# value)
# Deprecated group/name - [DEFAULT]/sql_connection
# Deprecated group/name - [DATABASE]/sql_connection
# Deprecated group/name - [sql]/connection
#connection = <None>
添加如下一行内容:
connection = mysql+pymysql://myproject:[email protected]/myproject?charset=utf
3.6 修改alembic配置文件
修改alembic相关文件如下:
3.6.1 alembic/env.py中的内容:
添加:
from myproject.db import models
修改
target_metadata = None
为
target_metadata = models.Base.metadata
解释:
这样做的原因是数据库迁移需要知道具体对应数据库的信息,
这里是设置数据库的元信息为当前应用的数据库信息
参考:
https://alembic.sqlalchemy.org/en/latest/autogenerate.html
3.6.2 alembic.ini中内容:
将
[alembic]
# path to migration scripts
# TODO(), edit it as real path
script_location = alembic
修改为:
[alembic]
# path to migration scripts
# TODO(), edit it as real path
script_location = myproject.db.migrate:alembic
解释:
这里主要是指定当前项目迁移脚本的路径,需要根据具体的项目路径进行替换
参考:
https://alembic.sqlalchemy.org/en/latest/tutorial.html
将默认的sqlalchemy.url修改为如下内容
sqlalchemy.url =
解释:
这里原本主要是配置数据库连接串的,因为在代码中配置了升级方法upgrade()
中通过如下方法中的set_main_option重新设置了sqlalchemy.url,所以
这里替换成空值。
3.6.3 请确保alembic目录和alembic.ini在同级目录
样例如下:
[root@localhost migrate]# tree
.
|-- alembic
| |-- env.py
| |-- env.pyc
| |-- __init__.py
| |-- README
| |-- script.py.mako
| `-- versions
| |-- 677de41920e9_create_student_table.py
| |-- 677de41920e9_create_student_table.pyc
| |-- 77cca51ca78e_add_email_to_student.py
| `-- __init__.py
|-- alembic.ini
`-- __init__.py
然后确保:
myproject/db/mariadb/impl_mariadb.py中Connection类的getAlembicConfig
方法中内容如下:
def getAlembicConfig(self):
dirName = os.path.dirname(__file__)
realDirName = os.path.dirname(dirName)
path = "%s/migrate/alembic.ini" % (realDirName)
cfg = config.Config(path)
cfg.set_main_option(
'sqlalchemy.url', self.conf.database.connection)
return cfg
注意:
请确保:
path = "%s/migrate/alembic.ini" % (realDirName)
这个路径是正确的,请根据实际路径进行修改
3.7 创建数据库
执行如下命令来创建数据库:
mysql -e "create database IF NOT EXISTS myproject"
mysql -e "GRANT ALL PRIVILEGES ON myproject.* TO 'myproject'@'localhost' IDENTIFIED BY 'password';"
mysql -e "GRANT ALL PRIVILEGES ON myproject.* TO 'myproject'@'%' IDENTIFIED BY 'password';"
3.8 第一次数据库同步
3.8.1 修改myproject/db/models.py中内容为如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from sqlalchemy import Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Index
from sqlalchemy import Integer
from sqlalchemy import PrimaryKeyConstraint
from sqlalchemy import String
Base = declarative_base()
class Student(Base):
__tablename__ = 'student'
id = Column(Integer(), nullable=False)
userId = Column(String(256), nullable=False)
name = Column(String(256), nullable=False)
age = Column(Integer(), nullable=True)
# email = Column(String(256), nullable=True)
__table_args__ = (
Index('ix_student_userId', 'userId'),
PrimaryKeyConstraint('id')
)
TRANSFORMED_FIELDS = ['id', 'userId', 'name', 'age', 'email']
# from database model to dict
def as_dict(self):
result = dict()
for field in Student.TRANSFORMED_FIELDS:
result[field] = getattr(self, field, '')
return result
注意: 这里先注释了email字段,后续以升级形式添加email字段到student表中
3.8.2 数据库同步
执行:
/usr/bin/myproject-dbsync
注意: 不同环境下生成的yproject-dbsync路径不同,请根据实际情况进行处理,这里是centos环境的
进入mysql,执行如下命令:
use myproject;
show tables;
可以看到:
MariaDB [myproject]> select * from alembic_version;
+--------------+
| version_num |
+--------------+
| 677de41920e9 |
MariaDB [myproject]> desc student;
+--------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| userId | varchar(256) | NO | MUL | NULL | |
| name | varchar(256) | NO | | NULL | |
| age | int(11) | YES | | NULL | |
+--------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
3.9 数据库升级
3.9.1 修改myproject/db/models.py中内容为如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from sqlalchemy import Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Index
from sqlalchemy import Integer
from sqlalchemy import PrimaryKeyConstraint
from sqlalchemy import String
Base = declarative_base()
class Student(Base):
__tablename__ = 'student'
id = Column(Integer(), nullable=False)
userId = Column(String(256), nullable=False)
name = Column(String(256), nullable=False)
age = Column(Integer(), nullable=True)
email = Column(String(256), nullable=True)
__table_args__ = (
Index('ix_student_userId', 'userId'),
PrimaryKeyConstraint('id')
)
TRANSFORMED_FIELDS = ['id', 'userId', 'name', 'age', 'email']
# from database model to dict
def as_dict(self):
result = dict()
for field in Student.TRANSFORMED_FIELDS:
result[field] = getattr(self, field, '')
return result
注意: 这里取消注释email字段,验证升级后email字段会添加到student表中
3.9.2 生成版本迁移文件
这里在myproject/db/migrate/alembic/versions目录下生成 77cca51ca78e_add_email_to_student.py 文件
其中: 77cca51ca78e 就是版本号
具体内容如下:
"""add email to student
Revision ID: 77cca51ca78e
Revises: 677de41920e9
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '77cca51ca78e'
down_revision = '677de41920e9'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('student', sa.Column('email', sa.String(256), nullable=True))
def downgrade():
op.drop_column('student', 'email')
分析:
1) down_revision 就是上一个版本的版本号,请根据实际情况修改
2) upgrade()定义了数据库升级的操作,这里是添加email字段到student表中
3) upgrade()定义了数据库降级的操作,这里是从student表中删除email字段
3.9.3 再次执行/usr/bin/myproject-dbsync
结果如下:
MariaDB [myproject]> desc student;
+--------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| userId | varchar(256) | NO | MUL | NULL | |
| name | varchar(256) | NO | | NULL | |
| age | int(11) | YES | | NULL | |
| email | varchar(256) | YES | | NULL | |
+--------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
MariaDB [myproject]> select * from alembic_version;
+--------------+
| version_num |
+--------------+
| 77cca51ca78e |
+--------------+
验证升级成功,且版本更新到最新
3.10 alembic常用api
alembic命令解释
3.10.1
alembic.op.create_table(table_name, *columns, **kws)
作用: 使用当前迁移上下文来执行一个创建表的命令
参数:
table_name: 表名
columns: 数组, 每个元素都是一个sqlalchemy.Column列对象,
可能还包括sqlalchemy.Index对象和sqlalchemy.PrimaryKeyConstraint对象等
kws: 字典参数,例如: mysql_engine='InnoDB', mysql_charset='utf8'等
3.10.2
alembic.op.create_index(index_name, table_name, columns, schema=None, unique=False, **kw)
作用: 使用当前迁移上下文来创建一个索引
参数:
index_name: 索引名称
table_name: 表名
columns: 列名的数组
unique: 如果为True,创建一个唯一的索引
3.10.3
sqlalchemy.PrimaryKeyConstraint(*columns, **kw)
作用: 设置表的主键
参数:
columns: 列名数组,即要设置为主键的一个或多个列名组成的数组
kw: 字典参数
3.10.4
sqlalchemy.UniqueConstraint(*columns, **kw)
作用: 设置表的唯一性索引。即确保某个列或某几个列的值在表中是唯一的。
往往用于列去重。
参数:
columns: 列名数组,即要设置为主键的一个或多个列名组成的数组
kw: 字典参数
用法示例:
sqlalchemy.UniqueConstraint('name','addr',name='unique_mytable_name_addr')
3.10.5
sqlalchemy.Column(*args, **kwargs)
作用: 表的列名及相关属性设置
参数:
args: 数组参数,例如: 列名,列字段类型
kwargs: 字典参数,例如: 是否可以为空等。
nullable: 为True表示该列可为空;为False表示该列不可以为空
3.10.6
alembic.op.add_column(table_name, column, schema=None)
作用: 根据当前迁移上下文环境添加某个列
参数:
table_name:表名
column: sqlalchemy.Column列对象
alembic.op.drop_column(table_name, column_name, schema=None, **kw)
作用: 根据当前迁移上下文环境删除某列
参数:
table_name: 表名
column_name: 列名,字符串类型
alembic.op.alter_column(table_name, column_name, nullable=None,
server_default=False, new_column_name=None, type_=None,
existing_type=None, existing_server_default=False,
existing_nullable=None, schema=None, **kw)
参考:
https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.create_table
http://alembic.zzzcomputing.com/en/latest/ops.html
https://alembic.sqlalchemy.org/en/latest/tutorial.html
3.11 其他问题
3.11.1 丢失alembic错误
执行:
/usr/bin/myproject-dbsync
报错:
2018-11-28 14:52:26.357 31559 ERROR myproject File "/usr/lib/python2.7/site-packages/myproject/db/mariadb/impl_mariadb.py", line 12, in <module>
2018-11-28 14:52:26.357 31559 ERROR myproject from myproject.db import models
2018-11-28 14:52:26.357 31559 ERROR myproject File "/usr/lib/python2.7/site-packages/myproject/db/models.py", line 4, in <module>
2018-11-28 14:52:26.357 31559 ERROR myproject from sqlalchemy import Column
2018-11-28 14:52:26.357 31559 ERROR myproject ImportError: cannot import name Column
分析:
丢失amebic文件
尝试:
package_data={
'package': ['./path/*.xsd'],
},
字典的键必须是你的真实包名,值必须是要包含的模式的列表。要包含Package 2/http/api/api.yaml:
package_data={
'package2': ['http/api/*.yaml'],
},
列出所有非python文件和模式。
另一种方法是创建MANIFEST.in(通常用于源分发)和https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files
include_package_data=True,
setup()。
参考:
https://cloud.tencent.com/developer/ask/143280
https://blog.csdn.net/s1234567_89/article/details/53008444
https://docs.python.org/2/distutils/sourcedist.html#the-manifest-in-template
setup.cfg设置package_data
最终解决办法:
在setup.cfg中修改为如下内容:
import setuptools
setuptools.setup(
# FIXME(), if not add package_data and include_package_data
# some files which ends with .ini or other will be missed
package_data={
# If any package contains *.ini or *.txt files, include them:
'': ['*.ini', '*.mako', '*.yaml', '*.txt', '*.py', 'README', '*.json', '*.wsgi'],
},
include_package_data=True,
setup_requires=['pbr'],
pbr=True)
3.11.2 数据库连接失败
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2 "No such file or directory")
解决方法:
[root@localhost alembic]# systemctl status mariadb
● mariadb.service - MariaDB 10.1 database server
Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
Active: inactive (dead)
[root@localhost alembic]# systemctl restart mariadb
解决
4 总结
python项目中通过sqlalchemy来操作数据库,进行增删改查等;通过alembic进行数据库的升级