我在使用Flask-SQLAlchemy的多个绑定时遇到问题。特别是,查询对象上的count()
方法似乎不起作用。
My Flask应用程序适用于PostgreSQL数据库。此外,它还从在MySQL上运行的旧版Simple Machines Forum安装中检索数据。
为方便使用,我对MySQL数据库使用第二个Flask-SQLAlchemy绑定,并通过反射设置类。查询一般工作正常,但使用count()
会引发sqlalchemy.exc.ProgrammingError
相应的表格不存在。
myapp/app.py
:
from flask import Flask
class Config(object):
SQLALCHEMY_DATABASE_URI = 'postgres://localhost/igor'
SQLALCHEMY_BINDS = {
'smf': 'mysql://myuser:mypass@localhost/my_db',
}
app = Flask('myapp')
app.config.from_object(Config)
myapp/model.py
:
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
from .app import app
__all__ = []
def _add_global(key, value):
globals()[key] = value
__all__.append(key)
bind_key = 'smf'
table_prefix = 'smf_'
table_prefix_len = len(table_prefix)
db = SQLAlchemy(app)
engine = db.get_engine(app, bind=bind_key)
meta = MetaData(bind=engine)
# Reflect SMF database
meta.reflect()
for tablename, table in meta.tables.items():
# Skip non-SMF tables
if not tablename.startswith(table_prefix):
continue
# Strip table name prefix
tablename = tablename[table_prefix_len:]
# Do not create a class for tables without primary key
if not table.primary_key:
_add_global(tablename, table)
continue
# Derive class name from table name by camel-casing,
# e.g. `smf_personal_messages` -> `PersonalMessages`
classname = ''.join(x.capitalize() for x in str(tablename).split('_'))
# Create class
class_ = type(classname, (db.Model,), {
'__table__': table,
'__bind_key__': bind_key,
})
_add_global(classname, class_)
示例(错误堆栈跟踪中的文件路径缩短了易读性):
% python
Python 2.7.6 (default, Dec 2 2013, 11:07:48)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from myapp.model import Topics
>>> len(Topics.query.all())
10162
>>> Topics.query.count()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "…/sqlalchemy/orm/query.py", line 2573, in count
return self.from_self(col).scalar()
File "…/sqlalchemy/orm/query.py", line 2379, in scalar
ret = self.one()
File "…/sqlalchemy/orm/query.py", line 2348, in one
ret = list(self)
File "…/sqlalchemy/orm/query.py", line 2391, in __iter__
return self._execute_and_instances(context)
File "…/sqlalchemy/orm/query.py", line 2406, in _execute_and_instances
result = conn.execute(querycontext.statement, self._params)
File "…/sqlalchemy/engine/base.py", line 717, in execute
return meth(self, multiparams, params)
File "…/sqlalchemy/sql/elements.py", line 317, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
File "…/sqlalchemy/engine/base.py", line 814, in _execute_clauseelement
compiled_sql, distilled_params
File "…/sqlalchemy/engine/base.py", line 927, in _execute_context
context)
File "…/sqlalchemy/engine/base.py", line 1076, in _handle_dbapi_exception
exc_info
File "…/sqlalchemy/util/compat.py", line 185, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb)
File "…/sqlalchemy/engine/base.py", line 920, in _execute_context
context)
File "…/sqlalchemy/engine/default.py", line 425, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (ProgrammingError) relation "smf_topics" does not exist
LINE 3: FROM smf_topics) AS anon_1
^
'SELECT count(*) AS count_1 \nFROM (SELECT smf_topics.id_topic AS smf_topics_id_topic, […] \nFROM smf_topics) AS anon_1' {}
在后一种情况下,语句显然是针对主PostgreSQL绑定而不是MySQL绑定运行的。 (这可以通过在连接的PostgreSQL数据库中创建smf_topics
表来轻松证明。)
在创建类时,我还尝试提供__tablename__
属性(以及代替)__table__
,但无济于事。
我想我错过了一些关键的东西。不幸的是,时间限制禁止将论坛迁移到PostgreSQL。任何帮助表示赞赏。
这是重现错误的最小示例。使用count()
与另一个绑定上的传统模型类 - 请参阅class Topic(db.Model)
- 但是可以正常工作。
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
# Application setup
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://localhost/igor'
app.config['SQLALCHEMY_BINDS'] = {
'smf': 'mysql://myuser:mypass@localhost/my_db',
}
db = SQLAlchemy(app)
# Database reflection
smf_meta = MetaData(bind=db.get_engine(app, bind='smf'))
smf_meta.reflect()
topic_class = type(db.Model)('Topic', (db.Model,), {
'__bind_key__': 'smf',
'__tablename__': 'smf_topics',
'__table__': smf_meta.tables['smf_topics'],
})
# Conventional model class
class Topic(db.Model):
__bind_key__ = 'smf'
__tablename__ = 'smf_topics'
id_topic = db.Column(db.Integer, primary_key=True)
num_replies = db.Column(db.Integer, nullable=False, default=0)
# Run it
if __name__ == '__main__':
print('1. {}'.format(Topic.query.count()))
print('2. {}'.format(len(topic_class.query.all())))
print('3. {}'.format(topic_class.query.count()))
运行脚本时的输出:
1. 10400
2. 10400
Traceback (most recent call last):
File "./test.py", line 35, in <module>
print('3. {}'.format(topic_class.query.count()))
File "…/sqlalchemy/orm/query.py", line 2640, in count
return self.from_self(col).scalar()
File "…/sqlalchemy/orm/query.py", line 2426, in scalar
ret = self.one()
File "…/sqlalchemy/orm/query.py", line 2395, in one
ret = list(self)
File "…/sqlalchemy/orm/query.py", line 2438, in __iter__
return self._execute_and_instances(context)
File "…/sqlalchemy/orm/query.py", line 2453, in _execute_and_instances
result = conn.execute(querycontext.statement, self._params)
File "…/sqlalchemy/engine/base.py", line 729, in execute
return meth(self, multiparams, params)
File "…/sqlalchemy/sql/elements.py", line 322, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
File "…/sqlalchemy/engine/base.py", line 826, in _execute_clauseelement
compiled_sql, distilled_params
File "…/sqlalchemy/engine/base.py", line 958, in _execute_context
context)
File "…/sqlalchemy/engine/base.py", line 1159, in _handle_dbapi_exception
exc_info
File "…/sqlalchemy/util/compat.py", line 199, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb)
File "…/sqlalchemy/engine/base.py", line 951, in _execute_context
context)
File "…/sqlalchemy/engine/default.py", line 436, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (ProgrammingError) relation "smf_topics" does not exist
LINE 3: FROM smf_topics) AS anon_1
^
'SELECT count(*) AS count_1 \nFROM (SELECT smf_topics.id_topic AS smf_topics_id_topic, […] \nFROM smf_topics) AS anon_1' {}
答案 0 :(得分:1)
以下是它的工作原理。 ^^关键是使用db.reflect()
,然后提供db.Model.metadata
作为新创建的类'元数据:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://localhost/igor'
app.config['SQLALCHEMY_BINDS'] = {
'smf': 'mysql://myuser:mypass@localhost/my_db',
}
db = SQLAlchemy(app)
db.reflect(bind='smf', app=app)
topic_class = type(db.Model)('Topic', (db.Model,), {
'__bind_key__': 'smf',
'__table__': db.Model.metadata.tables['smf_topics'],
'__tablename__': 'smf_topics',
'__module__': __name__,
'metadata': db.Model.metadata,
})
if __name__ == '__main__':
print('1. {}'.format(len(topic_class.query.all())))
print('2. {}'.format(topic_class.query.count()))