首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么我会在第二个WSGI请求时从SQLAlchemy得到一个间歇性的SQLAlchemy?

为什么我会在第二个WSGI请求时从SQLAlchemy得到一个间歇性的SQLAlchemy?
EN

Stack Overflow用户
提问于 2010-01-13 19:25:22
回答 1查看 905关注 0票数 0

我正在构建一个小型的WSGI应用程序,我在SQLAlchemy抛出一个UnboundExceptionError时遇到了一个间歇性的问题。

当发生这种情况时,它似乎只发生在浏览器发出的第二个请求上。刷新页面(以及以下所有页面视图尝试),运行良好。这似乎只发生在第二次请求。

我正在使用许多对我来说是新的技术,所以我不完全清楚我应该看什么来尝试解决这个问题。

  • CherryPyWSGIServer
  • Routes
  • AuthKit
  • WebOb
  • SQLAlchemy
  • jinja2

下面是与我的SQLAlchemy相关的设置:

代码语言:javascript
复制
product_table = Table('product', metadata,
    Column('productId', Integer, primary_key=True),
    Column('name', String(255)),
    Column('created', DateTime, default=func.now()),
    Column('updated', DateTime, default=func.now(),
        onupdate=func.current_timestamp()),
)

productversion_table = Table('productVersion', metadata,
    Column('productVersionId', Integer, primary_key=True),
    Column('productId', Integer, ForeignKey("product.productId"),
        nullable=False),
    Column('name', String(255)),
    Column('created', DateTime, default=func.now()),
    Column('updated', DateTime, default=func.now(),
        onupdate=func.current_timestamp()),
)

sqlalchemy.orm.mapper(Product,
    product_table,
    properties={
        'product_versions':
            sqlalchemy.orm.relation(
               ProductVersion,
                backref='product',
                cascade='all,delete-orphan')})

sqlalchemy.orm.mapper(ProductVersion,
    productversion_table,
    properties={ })

这是我的控制器:

代码语言:javascript
复制
class Base(object):

    def __init__(self, projenv, configuration, protected=True):
        self.protected = protected
        self.projenv = projenv
        self.configuration = configuration
        self.jinja2_env = Environment(
            loader=PackageLoader('my.webapp', 'views'))

    def __call__(self, environ, start_response):
        if self.protected:
            authkit.authorize.authorize_request(environ,
                authkit.permissions.RemoteUser())

        return self.handle_request_wrapper(environ, start_response)

    def handle_request_wrapper(self, environ, start_response):
        request = Request(environ)
        response = Response()
        model_and_view = self.handle_request(request, response)
        if model_and_view['redirect']:
            response = HTTPTemporaryRedirect(
                location=model_and_view['redirect_url'])
        else:
            response.content_type = model_and_view['content_type']
            template = self.jinja2_env.get_template(model_and_view['view'])
            content = template.render(**model_and_view['model'])
            response.body = str(content)
        return response(environ, start_response)


class Product(Base):

    def handle_request(self, request, response):
        model_and_view = Base.handle_request(self, request, response)

        url, match = request.environ['wsgiorg.routing_args']

        product_repository = product_repository_from_config(self.configuration)

        model_and_view['view'] = 'products/product.html'
        model_and_view['model']['product'] = \
            product_repository.get_product(match['product_id'])
        return model_and_view

以下是我的产品存储库代码:

代码语言:javascript
复制
def product_repository_from_config(configuration):
    session = session_from_config(configuration)
    return SqlAlchemyProductRepository(session, configuration)

class SqlAlchemyProductRepository(object):
    """SQLAlchemey Based ProductRepository."""

    def __init__(self, session, configuration = None):
        self.configuration = configuration
        self.session = session

    def get_product(self, product_id):
        return self.session.query(Product).filter_by(
            productId=product_id).first()

以下是我的ORM实用程序:

代码语言:javascript
复制
engines = {}

def setup_session(engine, **kwargs):
    session = sqlalchemy.orm.sessionmaker(bind=engine, **kwargs)
    return session()

def session_from_config(configuration, init=False, **kwargs):
    engine = engine_from_config(configuration, init)
    return setup_session(engine, **kwargs)

def engine_from_config(configuration, init=False):
    """Create an SQLAlchemy engine from a configuration object."""

    config = configuration.to_dict()

    key = pickle.dumps(config)

    if key not in engines:

        engine = sqlalchemy.engine_from_config(configuration.to_dict(),
                prefix = 'db.')

        configure_mapping_for_engine(engine, init)

        engines[key] = engine

    return engines[key]

以下是我的观点(jinja2):

代码语言:javascript
复制
{% extends "shell.html" %}
{% set title = "Product - " + product.name %}
{% block content %}
<h1>Product</h1>
<ul>
<li><a href="{{ url_for('products') }}">Product List</a></li>
</ul>
<form method="post" action="{{ url_for('products/update', product_id=product.productId) }}">
Name <input type="text" name="name" value="{{ product.name|e }}" /><br />
<input type="submit" value="Update" />
</form>
<form enctype="multipart/form-data" method="post" action="{{ url_for('products/versions/add', product_id=product.productId) }}">
Version Name <input type="text" name="name" />
<input type="submit" value="Add Version" />
</form>


<ul>
{% for product_version in product.product_versions %}
<li>{{ product_version.name }}
<ul>
<li><a href="{{ url_for('products/versions/delete', product_id=product.productId, product_version_id=product_version.productVersionId) }}">delete</a></li>
</ul>
</li>
{% endfor %}
</ul>
{% endblock %}

我得到的错误是:

代码语言:javascript
复制
UnboundExecutionError: Parent instance <Product at 0x9150c8c> is not bound to a Session; lazy load operation of attribute 'product_versions' cannot proceed

堆栈跟踪显示这是从以下位置抛出的:

代码语言:javascript
复制
{% for product_version in product.product_versions %}

是什么原因使我的实例在从存储库获得它到模板中的jinja2计算它的时间之间从会话中解除绑定?

我倾向于认为这可能是authkit的电话,妨碍了事情的发展,但我不知道它可能在做什么,因为它实际上发生在会话创建之前,应该不会影响以后发生的任何事情?

EN

回答 1

Stack Overflow用户

发布于 2010-01-14 06:33:29

通常,您应该有一个方案,根据该方案,每个请求都存在一个会话,该会话位于当前线程的本地,并且只在请求的末尾被拆卸(如果有的话,也可以在下一个请求中重新使用)。通常,您会在总体请求包装器中看到会话的创建和清理代码,在这种情况下,它可能在handle_request_wrapper()中。像authkit这样的WSGI中间件不应该真正地在您自己的应用程序中访问会话,除非您以某种方式将它们连接在一起。在上面的所有代码中,没有(编辑:好,我可以模糊地看到它的设置位置,在每个请求上创建一个新引擎?)永远不要做that....also --在请求完成之前,会话可能会被gc‘编辑),指示何时实际调用setup_session(),或者请求进行时返回的会话发生了什么。在任何情况下,由于您没有使用上下文管理器,所以有必要将这个单一会话传递给请求中涉及的所有函数--因为如果它是一个全局对象,那么您将得到并发线程同时访问它,这将导致立即出现问题--这是一个非常麻烦的模式。

提供scoped_session是为了使“每个线程会话”模式变得非常简单,例如,在使用幽门时,默认情况下会设置“安装/请求边界上的拆卸”模式。http://www.sqlalchemy.org/docs/05/session.html#lifespan-of-a-contextual-session上展示了这个生命周期的可视化(尽管是ASCII)。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2059563

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档