J'ai une vue Flask qui utilise SQLAlchemy pour interroger et afficher des articles de blog. J'exécute mon application à l'aide de mod_wsgi. Cette vue fonctionne la première fois que je vais sur la page, mais renvoie une erreur 500 la prochaine fois. Le traceback montre l'erreur ProgrammingError: SQLite objects created in a thread can only be used in that same thread.
Pourquoi est-ce que j'obtiens cette erreur et comment la corriger?
views.py
engine = create_engine('sqlite:////var/www/homepage/blog.db')
Base.metadata.bind = engine
DBSession = sessionmaker(bind = engine)
session = DBSession()
@app.route('/blog')
@app.route('/blog.html')
def blog():
entrys = session.query(Entry).order_by(desc(Entry.timestamp)).all()
return render_template('blog.html', blog_entrys = entrys)
models.py
:
class Entry(Base):
__tablename__ = 'entry'
id = Column(Integer, primary_key = True)
title = Column(String(100), nullable = False)
body = Column(String, nullable = False)
timestamp = Column(DateTime, nullable = False)
featured = Column(Boolean, nullable = False)
comments = relationship('Comment')
def is_featured(self):
return self.featured
class Comment(Base):
__tablename__ = 'comment'
id = Column(Integer, primary_key = True)
entry_id = Column(Integer, ForeignKey('entry.id'))
text = Column(String(500), nullable = False)
name = Column(String(80))
engine = create_engine('sqlite:////var/www/homepage/blog.db')
Base.metadata.create_all(engine)
Exception on /blog.html [GET]
Traceback (most recent call last):
File "/usr/lib/python2.6/dist-packages/flask/app.py", line 861, in wsgi_app
rv = self.dispatch_request()
File "/usr/lib/python2.6/dist-packages/flask/app.py", line 696, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/var/www/homepage/webserver.py", line 38, in blog
entrys = session.query(Entry).order_by(desc(Entry.timestamp)).all()
File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1453, in all
return list(self)
File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1565, in __iter__
return self._execute_and_instances(context)
File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 1570, in _execute_and_instances
mapper=self._mapper_zero_or_none())
File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/session.py", line 735, in execute
clause, params or {})
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1157, in execute
params)
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1235, in _execute_clauseelement
parameters=params
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1348, in __create_execution_context
None, None)
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/base.py", line 1343, in __create_execution_context
connection=self, **kwargs)
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 381, in __init__
self.cursor = self.create_cursor()
File "/usr/lib/python2.6/dist-packages/sqlalchemy/engine/default.py", line 523, in create_cursor
return self._connection.connection.cursor()
File "/usr/lib/python2.6/dist-packages/sqlalchemy/pool.py", line 383, in cursor
c = self.connection.cursor(*args, **kwargs)
ProgrammingError: (ProgrammingError) SQLite objects created in a thread can only be used in that same thread.The object was created in thread id 140244498364160 and this is thread id 140244523542272 None [{}]
SQLAlchemy (et dans ce cas aussi SQLite) ne fonctionne pas si vous partagez une session sur des threads. Vous n'utilisez peut-être pas explicitement les threads, mais mod_wsgi
est, et vous avez défini un objet global session
. Utilisez soit scoped_session
pour gérer la création d'une session unique pour chaque thread.
session = scoped_session(sessionmaker(bind=engine))
@app.teardown_request
def remove_session(ex=None):
session.remove()
@app.route('/')
def example():
item = session.query(MyModel).filter(...).all()
...
De préférence, utilisez Flask-SQLAlchemy qui gère cela et d'autres choses pour vous. Les documents SQLAlchemy vous recommandent d'utiliser la bibliothèque d'intégration plutôt que de le faire vous-même.
db = SQLAlchemy(app)
@app.route('/')
def example():
item = db.session.query(MyModel).filter(...).all()
...
Notez également que vous ne devez définir le moteur, la session, etc. qu'une seule fois et l'importer ailleurs, plutôt que de le redéfinir dans chaque fichier comme le fait votre code actuel.
En prenant un indice de this SO answer J'ai cherché SA docs et j'ai découvert que vous pouvez le faire:
engine = create_engine('sqlite:////var/www/homepage/blog.db?check_same_thread=False')
scoped_session
ne convenait pas vraiment dans mon cas puisque Flask-SQLAlchemy ne prend qu'un argument de chaîne de connexion:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
class Config(object):
SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db?check_same_thread=False'
db = SQLAlchemy()
def create_app():
app.config.from_object(Config)
app = Flask(__name__)
db.init_app(app)
...
Selon sqlite3.connect
:
Par défaut,
check_same_thread
estTrue
et seul le thread de création peut utiliser la connexion. Si définiFalse
, la connexion renvoyée peut être partagée sur plusieurs threads. Lors de l'utilisation de plusieurs threads avec la même connexion, les opérations d'écriture doivent être sérialisées par l'utilisateur pour éviter la corruption des données.