Python 在Cherrypy和SQLAlchemy中,如何在同一请求中使用多个数据库?

Python 在Cherrypy和SQLAlchemy中,如何在同一请求中使用多个数据库?,python,sqlalchemy,cherrypy,Python,Sqlalchemy,Cherrypy,我的应用程序使用类似于的技术连接到多个数据库。只要我不尝试在同一个请求中访问不同的数据库,它就可以工作。回顾上述脚本,我发现他们为此写了一篇评论: SQLAlchemy integration for CherryPy, such that you can access multiple databases, but only one of these databases per request or thread. 我的应用程序现在要求我从数据库A和数据库B获取数据。是否可以在单个请求中完成

我的应用程序使用类似于的技术连接到多个数据库。只要我不尝试在同一个请求中访问不同的数据库,它就可以工作。回顾上述脚本,我发现他们为此写了一篇评论:

SQLAlchemy integration for CherryPy,
such that you can access multiple databases,
but only one of these databases per request or thread.
我的应用程序现在要求我从数据库A和数据库B获取数据。是否可以在单个请求中完成此操作

有关来源和示例,请参见下文:

工作示例1:

from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()
from model import meta

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
# meta.py
import cherrypy
import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# Return an Engine
def create_engine(defaultschema = True, schema = "", **kwargs):

    # A blank DB is the same as no DB so to specify a non-schema-specific connection just override with defaultschema = False
    connectionString = 'mysql://%s:%s@%s/%s?charset=utf8' % (
        store['application'].config['main']['database-server-config-username'],
        store['application'].config['main']['database-server-config-password'],
        store['application'].config['main']['database-server-config-host'],
        store['application'].config['main']['database-server-config-defaultschema'] if defaultschema else schema
    )
    # Create engine object. we pass **kwargs through so this call can be extended
    return sqlalchemy.create_engine(connectionString, echo=True, pool_recycle=10, echo_pool=True, encoding='utf-8', **kwargs)

# Engines
main_engine = create_engine()
user_engine = None

# Sessions
_main_session = None
_user_session = None

# Metadata
main_metadata = MetaData()
main_metadata.bind = main_engine
user_metadata = MetaData()

# No idea what bases are/do but nothing works without them
main_base = declarative_base(metadata = main_metadata)
user_base = declarative_base(metadata = user_metadata)

# An easy collection of user database connections
engines = {}

# Each thread gets a session based on this object
GlobalSession = scoped_session(sessionmaker(autoflush=True, autocommit=False, expire_on_commit=False))

def main_session():
    _main_session = cherrypy.request.main_dbsession
    _main_session.configure(bind=main_engine)

    return _main_session

def user_session():
    _user_session = cherrypy.request.user_dbsession
    _user_session.configure(bind = get_user_engine())

    return _user_session

def get_user_engine():

    # Get dburi from the users instance
    dburi = cherrypy.session['auth']['user'].instance.database

    # Store this engine for future use
    if dburi in engines:
        engine = engines.get(dburi)
    else:
        engine = engines[dburi] = create_engine(defaultschema = False, schema = dburi)

    # Return Engine
    return engine


def get_user_metadata():
    user_metadata.bind = get_user_engine()
    return user_metadata

# open a new session for the life of the request
def open_dbsession():
    cherrypy.request.user_dbsession = cherrypy.thread_data.scoped_session_class
    cherrypy.request.main_dbsession = cherrypy.thread_data.scoped_session_class
    return

# close the session for this request
def close_dbsession():
    if hasattr(cherrypy.request, "user_dbsession"):
        try:
            cherrypy.request.user_dbsession.flush()
            cherrypy.request.user_dbsession.remove()
            del cherrypy.request.user_dbsession
        except:
            pass
    if hasattr(cherrypy.request, "main_dbsession"):
        try:
            cherrypy.request.main_dbsession.flush()
            cherrypy.request.main_dbsession.remove()
            del cherrypy.request.main_dbsession
        except:
            pass

    return

# initialize the session factory class for the selected thread
def connect(thread_index):
    cherrypy.thread_data.scoped_session_class = scoped_session(sessionmaker(autoflush=True, autocommit=False))
    return

# add the hooks to cherrypy
cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession)
cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession)
cherrypy.engine.subscribe('start_thread', connect)
工作示例2:

from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()
from model import meta

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
# meta.py
import cherrypy
import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# Return an Engine
def create_engine(defaultschema = True, schema = "", **kwargs):

    # A blank DB is the same as no DB so to specify a non-schema-specific connection just override with defaultschema = False
    connectionString = 'mysql://%s:%s@%s/%s?charset=utf8' % (
        store['application'].config['main']['database-server-config-username'],
        store['application'].config['main']['database-server-config-password'],
        store['application'].config['main']['database-server-config-host'],
        store['application'].config['main']['database-server-config-defaultschema'] if defaultschema else schema
    )
    # Create engine object. we pass **kwargs through so this call can be extended
    return sqlalchemy.create_engine(connectionString, echo=True, pool_recycle=10, echo_pool=True, encoding='utf-8', **kwargs)

# Engines
main_engine = create_engine()
user_engine = None

# Sessions
_main_session = None
_user_session = None

# Metadata
main_metadata = MetaData()
main_metadata.bind = main_engine
user_metadata = MetaData()

# No idea what bases are/do but nothing works without them
main_base = declarative_base(metadata = main_metadata)
user_base = declarative_base(metadata = user_metadata)

# An easy collection of user database connections
engines = {}

# Each thread gets a session based on this object
GlobalSession = scoped_session(sessionmaker(autoflush=True, autocommit=False, expire_on_commit=False))

def main_session():
    _main_session = cherrypy.request.main_dbsession
    _main_session.configure(bind=main_engine)

    return _main_session

def user_session():
    _user_session = cherrypy.request.user_dbsession
    _user_session.configure(bind = get_user_engine())

    return _user_session

def get_user_engine():

    # Get dburi from the users instance
    dburi = cherrypy.session['auth']['user'].instance.database

    # Store this engine for future use
    if dburi in engines:
        engine = engines.get(dburi)
    else:
        engine = engines[dburi] = create_engine(defaultschema = False, schema = dburi)

    # Return Engine
    return engine


def get_user_metadata():
    user_metadata.bind = get_user_engine()
    return user_metadata

# open a new session for the life of the request
def open_dbsession():
    cherrypy.request.user_dbsession = cherrypy.thread_data.scoped_session_class
    cherrypy.request.main_dbsession = cherrypy.thread_data.scoped_session_class
    return

# close the session for this request
def close_dbsession():
    if hasattr(cherrypy.request, "user_dbsession"):
        try:
            cherrypy.request.user_dbsession.flush()
            cherrypy.request.user_dbsession.remove()
            del cherrypy.request.user_dbsession
        except:
            pass
    if hasattr(cherrypy.request, "main_dbsession"):
        try:
            cherrypy.request.main_dbsession.flush()
            cherrypy.request.main_dbsession.remove()
            del cherrypy.request.main_dbsession
        except:
            pass

    return

# initialize the session factory class for the selected thread
def connect(thread_index):
    cherrypy.thread_data.scoped_session_class = scoped_session(sessionmaker(autoflush=True, autocommit=False))
    return

# add the hooks to cherrypy
cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession)
cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession)
cherrypy.engine.subscribe('start_thread', connect)
错误示例:

from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()
from model import meta

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
# meta.py
import cherrypy
import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# Return an Engine
def create_engine(defaultschema = True, schema = "", **kwargs):

    # A blank DB is the same as no DB so to specify a non-schema-specific connection just override with defaultschema = False
    connectionString = 'mysql://%s:%s@%s/%s?charset=utf8' % (
        store['application'].config['main']['database-server-config-username'],
        store['application'].config['main']['database-server-config-password'],
        store['application'].config['main']['database-server-config-host'],
        store['application'].config['main']['database-server-config-defaultschema'] if defaultschema else schema
    )
    # Create engine object. we pass **kwargs through so this call can be extended
    return sqlalchemy.create_engine(connectionString, echo=True, pool_recycle=10, echo_pool=True, encoding='utf-8', **kwargs)

# Engines
main_engine = create_engine()
user_engine = None

# Sessions
_main_session = None
_user_session = None

# Metadata
main_metadata = MetaData()
main_metadata.bind = main_engine
user_metadata = MetaData()

# No idea what bases are/do but nothing works without them
main_base = declarative_base(metadata = main_metadata)
user_base = declarative_base(metadata = user_metadata)

# An easy collection of user database connections
engines = {}

# Each thread gets a session based on this object
GlobalSession = scoped_session(sessionmaker(autoflush=True, autocommit=False, expire_on_commit=False))

def main_session():
    _main_session = cherrypy.request.main_dbsession
    _main_session.configure(bind=main_engine)

    return _main_session

def user_session():
    _user_session = cherrypy.request.user_dbsession
    _user_session.configure(bind = get_user_engine())

    return _user_session

def get_user_engine():

    # Get dburi from the users instance
    dburi = cherrypy.session['auth']['user'].instance.database

    # Store this engine for future use
    if dburi in engines:
        engine = engines.get(dburi)
    else:
        engine = engines[dburi] = create_engine(defaultschema = False, schema = dburi)

    # Return Engine
    return engine


def get_user_metadata():
    user_metadata.bind = get_user_engine()
    return user_metadata

# open a new session for the life of the request
def open_dbsession():
    cherrypy.request.user_dbsession = cherrypy.thread_data.scoped_session_class
    cherrypy.request.main_dbsession = cherrypy.thread_data.scoped_session_class
    return

# close the session for this request
def close_dbsession():
    if hasattr(cherrypy.request, "user_dbsession"):
        try:
            cherrypy.request.user_dbsession.flush()
            cherrypy.request.user_dbsession.remove()
            del cherrypy.request.user_dbsession
        except:
            pass
    if hasattr(cherrypy.request, "main_dbsession"):
        try:
            cherrypy.request.main_dbsession.flush()
            cherrypy.request.main_dbsession.remove()
            del cherrypy.request.main_dbsession
        except:
            pass

    return

# initialize the session factory class for the selected thread
def connect(thread_index):
    cherrypy.thread_data.scoped_session_class = scoped_session(sessionmaker(autoflush=True, autocommit=False))
    return

# add the hooks to cherrypy
cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession)
cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession)
cherrypy.engine.subscribe('start_thread', connect)
这将导致以下错误:

(sqlalchemy.exc.ProgrammingError) (1146, "Table 'main_db.user' doesn't exist")
来源:

from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()
from model import meta

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
from model import meta

my_object_instance = meta.main_session().query(MyObject).filter(
    MyObject.id == 1
).one()

my_user = meta.user_session().query(User).filter(
    User.id == 1
).one()
# meta.py
import cherrypy
import sqlalchemy
from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# Return an Engine
def create_engine(defaultschema = True, schema = "", **kwargs):

    # A blank DB is the same as no DB so to specify a non-schema-specific connection just override with defaultschema = False
    connectionString = 'mysql://%s:%s@%s/%s?charset=utf8' % (
        store['application'].config['main']['database-server-config-username'],
        store['application'].config['main']['database-server-config-password'],
        store['application'].config['main']['database-server-config-host'],
        store['application'].config['main']['database-server-config-defaultschema'] if defaultschema else schema
    )
    # Create engine object. we pass **kwargs through so this call can be extended
    return sqlalchemy.create_engine(connectionString, echo=True, pool_recycle=10, echo_pool=True, encoding='utf-8', **kwargs)

# Engines
main_engine = create_engine()
user_engine = None

# Sessions
_main_session = None
_user_session = None

# Metadata
main_metadata = MetaData()
main_metadata.bind = main_engine
user_metadata = MetaData()

# No idea what bases are/do but nothing works without them
main_base = declarative_base(metadata = main_metadata)
user_base = declarative_base(metadata = user_metadata)

# An easy collection of user database connections
engines = {}

# Each thread gets a session based on this object
GlobalSession = scoped_session(sessionmaker(autoflush=True, autocommit=False, expire_on_commit=False))

def main_session():
    _main_session = cherrypy.request.main_dbsession
    _main_session.configure(bind=main_engine)

    return _main_session

def user_session():
    _user_session = cherrypy.request.user_dbsession
    _user_session.configure(bind = get_user_engine())

    return _user_session

def get_user_engine():

    # Get dburi from the users instance
    dburi = cherrypy.session['auth']['user'].instance.database

    # Store this engine for future use
    if dburi in engines:
        engine = engines.get(dburi)
    else:
        engine = engines[dburi] = create_engine(defaultschema = False, schema = dburi)

    # Return Engine
    return engine


def get_user_metadata():
    user_metadata.bind = get_user_engine()
    return user_metadata

# open a new session for the life of the request
def open_dbsession():
    cherrypy.request.user_dbsession = cherrypy.thread_data.scoped_session_class
    cherrypy.request.main_dbsession = cherrypy.thread_data.scoped_session_class
    return

# close the session for this request
def close_dbsession():
    if hasattr(cherrypy.request, "user_dbsession"):
        try:
            cherrypy.request.user_dbsession.flush()
            cherrypy.request.user_dbsession.remove()
            del cherrypy.request.user_dbsession
        except:
            pass
    if hasattr(cherrypy.request, "main_dbsession"):
        try:
            cherrypy.request.main_dbsession.flush()
            cherrypy.request.main_dbsession.remove()
            del cherrypy.request.main_dbsession
        except:
            pass

    return

# initialize the session factory class for the selected thread
def connect(thread_index):
    cherrypy.thread_data.scoped_session_class = scoped_session(sessionmaker(autoflush=True, autocommit=False))
    return

# add the hooks to cherrypy
cherrypy.tools.dbsession_open = cherrypy.Tool('on_start_resource', open_dbsession)
cherrypy.tools.dbsession_close = cherrypy.Tool('on_end_resource', close_dbsession)
cherrypy.engine.subscribe('start_thread', connect)

您还可以选择一种从头开始为多个数据库设计的ORM,如。

看看这个:

基本上,它建议对每个连接使用bind参数。这就是说,这似乎有点像黑客

这个问题的答案更详细:


这就是说,这个问题和引用的问题都不是最新的,sqlalchemy可能会从那时起继续前进。

我不想“回答”我自己的问题,但解决方案似乎是在
connect
中为另一个数据库添加一个额外的作用域会话,因此它看起来像
def connect(线程索引):cherrypy.thread\u data.user\u scoped\u session\u class=scoped\u session(sessionmaker(autoflush=True,autocommit=False))cherrypy.thread\u data.main\u scoped\u session\u class=scoped\u session(sessionmaker(autoflush=True,autocommit=False))返回
,然后在
open\u dbsession
中分别引用它们,看起来很不错。我最终解决了我在这个问题上面临的直接问题(见问题附件中我的评论),但最终又遇到了几个问题;现在一切正常,但是代码和解释对于StackOverflow来说太大了。多线程+每个用户多个动态数据库连接+每个请求设置/拆除=痛苦