sqlalchemy是很強大的orm框架,一般的教程都是從新建一個表開始教,這種情況下我們能直接拿到表對象然后方便的操作,但是對于已經存在的表的操作,網上教程并不多,而實際上,對于測試同學來說,這種情況才是最普遍的。我在集成測試框架中數據庫操作便使用了sqlalchemy,這里分享一下,順便記錄踩過的一些不那么明顯卻很致命的坑。
首先,我們可以創建一個自定義對象,它可以去組合sqlalchemy提供的種種功能。
from sqlalchemy.orm.exc import UnmappedClassError
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import sessionmaker, class_mapper, Query
class DbRoot(object):
def __init__(self, **kwargs):
"""
orm基礎db對象,通過實例化該對象得到db實例,然后創建類對象繼承自db.Model,便可以對相應表進行操作
:param kwargs: dialect 數據庫類型
driver 數據庫驅動
user 用戶名
password 用戶密碼
host 數據庫地址
port 端口
database 數據庫名
"""
url = '{dialect}+{driver}://{user}:{password}@{host}:{port}/{database}?charset=utf8'.format(**kwargs)
engine = create_engine(url, echo=False)
class Base(object):
@declared_attr
def __table__(cls):
return Table(cls.__tablename__, MetaData(), autoload=True, autoload_with=engine)
self._base = Base
self.Model = self.make_declarative_base()
self.session = sessionmaker(bind=engine)
def make_declarative_base(self):
"""
:return:
"""
base = declarative_base(cls=self._base)
base.query = _QueryProperty(self)
base.query_class = Query
return base
如代碼所示,DbRoot對象接收數據庫url的各項參數,然后創建引擎,組合declarative_base方法生成的Model對象(Model對象是Base對象的子類),以及通過sessionmaker方法生成的session數據庫會話對象(實例化它便可以產生一個數據庫會話,相當于操作句柄)。
然后在make_declarative_base方法中,Model對象還組合了_QueryProperty,也就是query屬性,用于查詢操作,_QueryProperty是一個描述符對象,見下面代碼。這樣,我們在做比如query.filter_by等操作時,都是轉給了get方法去執行,該方法最終調用了t也就是Model對象query_class方法(其實就是sqlalchemy.orm提供的Query方法)做查詢,這里尤其注意,session參數傳的是DbRoot對象的session屬性,該屬性一定要在這里實例化,這樣每次操作才能重新生成一個session會話。
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, t):
"""
這里一定要注意,session要每次重新生成,不然session會話會自動關閉,導致下一次操作句柄為空
:param obj:
:param t:
:return:
"""
try:
mapper = class_mapper(t)
if mapper:
return t.query_class(mapper, session=self.sa.session())
except UnmappedClassError:
return None
現在,我們等于有了db.Model對象,然后我們要去映射表生成表對象。其實就是創建一個類繼承自DbRoot.Model,然后將__tablename__
屬性設置為想要生成的表對象表名,這樣便大功告成了。我這里采用了動態生成類的方式。
def gen_orm_class( db=None, table_name=None):
"""
動態生成數據庫表映射Model類
:param db: db對象
:param table_name: 表名稱
:return:
"""
return type(
table_name.title(),
(db.Model,),
{
'__tablename__': table_name
}
)
有了這個基礎,如果結合pytest使用的話會更方便,pytest有個很牛的功能叫fixture,我們可以把初始化數據庫的操作做出一個scope為module的fixture
@pytest.fixture(scope='module')
def mysql(request, config_init):
"""
mysql數據庫操作實例
:param request:
:param config_init:
:return:
"""
db_roots = {}
mysql_conf = config_init.get('mysql')
databases = mysql_conf.pop('databases')
dbs = request.module.config.get('mysql_dbs', {})
for db, table in dbs.items():
db_conf = databases.get(db)
mysql_conf.update(db_conf)
db_roots.update({table: gen_orm_class(db_name=db, db=DbRoot(**mysql_conf), table_name=table)})
return db_roots
然后在測試用例中配置數據庫連接信息。
# test_case.py
config = {
'mysql_dbs': {
'dev': 'user'
}
}
這樣在進入該模塊(test_case.py)開始測試之前,pytest會根據配置自動初始化數據庫連接生成{‘表名’: ‘DbRoot表對象’}
結構的字典,之后在測試代碼中,就可以通過mysql.get('user')拿到User表對象,從而對dev.User表進行查詢操作。
def test_user(self, mysql, user_id):
user_tbl = mysql.get('user')
# id是主鍵,所以可以通過get方法直接查詢
assert user_tbl.query.get(user_id),'該用戶{}不存在'.format(user_id)
有緣瀏覽到的人,希望可以幫助到你