SQL炼金术

2020-06-17 16:08:22 浏览数 (1)

使用非全局会话

有时最好不要使用SQLAlchemy的线程作用域会话(例如,当您需要在异步系统中使用Pyramid时)。幸运的是,这样做很容易。您可以将会话工厂存储在应用程序的注册表中,并调用会话工厂作为向请求对象询问属性的副作用。然后,会话对象的生存期将与请求的生存期匹配。

我们将使用Configurator.add_request_method添加SQLAlchemy会话来请求对象并Request.add_finished_callback关闭该会话。

我们假设您有一个.ini文件,其sqlalchemy.设置可以正确指定数据库:

现在,SQLAlchemy会话在视图代码中以request.db或 可用config.registry.dbmaker()。

代码语言:javascript复制
# __init__.py

from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from sqlalchemy.orm import sessionmaker

def db(request):
    maker = request.registry.dbmaker
    session = maker()

def cleanup(request):
        if request.exception is not None:
            session.rollback()
        else:
            session.commit()
        session.close()
    request.add_finished_callback(cleanup)

    return session

def main(global_config, **settings):
    config = Configurator(settings=settings)
    engine = engine_from_config(settings, prefix='sqlalchemy.')
    config.registry.dbmaker = sessionmaker(bind=engine)
    config.add_request_method(db, reify=True)

# .. rest of configuration ...

导入所有SQLAlchemy模型

如果您使用粘贴程序模板创建了Pyramid项目,则默认情况下,SQLAlchemy模型将驻留在单个文件中。这只是按照惯例。如果您希望有一个用于SQLAlchemy模型的目录而不是一个文件,那么您当然可以创建一个充满模型模块的Python包,将models.py文件替换models为Python包的目录(其中包含的目录__init__.py),例如每个 修改包的结构。但是,由于SQLAlchemy的“声明式”配置模式的行为,必须先导入所有保存活动SQLAlchemy模型的模块,然后才能成功使用它们。因此,如果您使用具有声明性基础的模型类,则需要找出一种方法来导入所有模型模块,以便能够在应用程序中使用它们。

您可能首先创建一个models目录,替换该models.py 文件,然后在其中创建一个名为的文件models/__init__.py。此时,您可以添加名为的子模块models/mymodel.py,其中包含一个 MyModel模型类。在models/__init__.py将定义声明性基类和全局DBSession对象,其中每个子模块模型(如models/mymodel.py)将需要导入。然后,您所需要做的就是在中添加每个子模块的导入models/__init__.py。

但是,当您将models包子模块import语句添加到时 models/__init__.py,这将导致循环导入依赖关系。该 models/__init__.py模块的进口mymodel和models/mymodel.py 进口models包。下次尝试启动您的应用程序时,由于这种循环依赖性,它会因导入错误而失败。

Pylons 1通过创建一个models/meta.py模块来解决此问题,在该模块中创建DBSession和声明性基础对象。该 models/__init__.py文件的每个子模块models的进口 DBSession和declarative_base从它。每当您.py 在models包中创建文件时,都希望为其添加导入 models/__init__.py。主程序将导入models包,这具有确保已导入所有模型类的副作用。您也可以执行此操作,效果很好。

但是,您可以交替使用config.scan()它的副作用。使用config.scan()可以避免之间的麻烦, models/__init__.py而models/themodel.py无需创建特殊的 models/meta.py。

例如,如果您在中执行此操作myapp/models/__init__.py:

代码语言:javascript复制
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker

DBSession = scoped_session(sessionmaker())
Base = declarative_base()

def initialize_sql(engine):
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)

而在myapp/models/mymodel.py

代码语言:javascript复制
from myapp.models import Base
from sqlalchemy import Column
from sqlalchemy import Unicode
from sqlalchemy import Integer

class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Unicode(255), unique=True)
    value = Column(Integer)

def __init__(self, name, value):
        self.name = name
        self.value = value

而在myapp/__init__.py

代码语言:javascript复制
from sqlalchemy import engine_from_config
from myapp.models import initialize_sql

def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.scan('myapp.models') # the "important" line
    engine = engine_from_config(settings, 'sqlalchemy.')
    initialize_sql(engine)
# other statements here
    config.add_handler('main', '/{action}',
                     'myapp.handlers:MyHandler')
    return config.make_wsgi_app()

上面的重要行是config.scan('myapp.models')。config.scan 具有对给定的程序包名称进行递归导入的副作用。此副作用可确保myapp.models导入其中的每个文件,而无需您在其中“手工”导入每个 文件models/__init__.py。但是,它不会导入任何不在myapp.models包装内的模型 。

0 人点赞