web-dev-qa-db-fra.com

L'utilisation de la session SQLAlchemy à partir de Flask soulève "Les objets SQLite créés dans un thread ne peuvent être utilisés que dans ce même thread"

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 [{}]
21
J. Pawelczyk

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.

21
davidism

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 est True et seul le thread de création peut utiliser la connexion. Si défini False, 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.

30
reubano