小さな WSGI アプリケーションを構築していますが、SQLAlchemy がUnboundExceptionError
.
それが発生すると、ブラウザが行う 2 番目のリクエストでのみ発生するようです。ページの更新 (およびその後のすべてのページ ビューの試行) は正常に実行されます。2番目のリクエストでのみ発生するようです。
私は自分にとって初めての多くのテクノロジーを扱っているため、これを理解するために何を調べればよいか完全に明確ではありません。
- CherryPyWSGIサーバー
- ルート
- AuthKit
- WebOb
- SQL錬金術
- ジンジャ2
これが私のSQLAlchemy関連のセットアップです:
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={ })
これが私のコントローラーです:
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
ここに私の製品リポジトリコードがあります:
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ユーティリティがあります:
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):
{% 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 %}
私が得るエラーは次のとおりです。
UnboundExecutionError: Parent instance <Product at 0x9150c8c> is not bound to a Session; lazy load operation of attribute 'product_versions' cannot proceed
スタック トレースは、これが次からスローされたことを示しています。
{% for product_version in product.product_versions %}
インスタンスをリポジトリから取得してから、テンプレートで jinja2 によって評価されるまでの間にインスタンスがセッションからバインド解除される原因は何ですか?
私は、authkit 呼び出しが邪魔をしている可能性があると考える方向に傾いていますが、セッションが作成される前に実際に発生し、後で発生するものに影響を与えないはずなので、それが何をしているのかわかりませんか?