Quart: teardown_appcontext and SQlite

65 views Asked by At

I was interested in converting the existing Flask "flaskr" tutorial to a Quart project. One issue I have encountered is when I go to register teardown method for closing the database, I run into a threading error sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. Is there a way to run a teardown method (I.e. post route return) such that close() is called in the same thread?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sqlite3 import connect
from sqlite3 import PARSE_DECLTYPES
from sqlite3 import Row

from click import command
from click import echo
from quart import current_app
from quart import g
from quart.cli import with_appcontext


def get_db():
    """
    Connect to the application's configured database. The connection
    is unique for each request and will be reused if this is called
    again.
    """

    if not hasattr(g, "db"):
        g.db = connect(
            current_app.config["DATABASE"],
            detect_types=PARSE_DECLTYPES,
        )
        g.db.row_factory = Row
    return g.db


def close_db(exception=None):
    db = g.pop("db", None)
    if db is not None:
        db.close()


@command("init-db")
@with_appcontext
def init_db_command() -> None:
    db = get_db()
    with open(current_app.root_path + "/schema.sql") as file:
        db.executescript(file.read())
    echo("Initialized the database.")


def init_app(app) -> None:
    """
    Register database functions with the Quart app. This is called by
    the application factory.
    """

    app.teardown_appcontext(close_db)
    app.cli.add_command(init_db_command)
    return app

Running the above code command "init-db" produces an error similar to what is shown below:

           ^^^^^^^^^^^^^^^
  File "/root/qcv/venv/lib/python3.11/site-packages/quart/cli.py", line 278, in _inner
    async with __ctx.ensure_object(ScriptInfo).load_app().app_context():
  File "/root/qcv/venv/lib/python3.11/site-packages/quart/ctx.py", line 266, in __aexit__
    await self.pop(exc_value)
  File "/root/qcv/venv/lib/python3.11/site-packages/quart/ctx.py", line 251, in pop
    await self.app.do_teardown_appcontext(exc)
  File "/root/qcv/venv/lib/python3.11/site-packages/quart/app.py", line 1169, in do_teardown_appcontext
    await self.ensure_async(function)(exc)
  File "/root/qcv/venv/lib/python3.11/site-packages/quart/utils.py", line 57, in _wrapper
    result = await loop.run_in_executor(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/qcv/qcv/db.py", line 35, in close_db
    db.close()
sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 140328473665600 and this is thread id 140328443631296.
1

There are 1 answers

0
mcpcpc On

After taking a closer look at the Quart documentation. I believe the main issue is that teardown_app context should be calling a coroutine. Thus, changing the close_db from a synchronous method to an asynchronous method should fix the threading error.

async def close_db(exception=None):
    db = g.pop("db", None)
    if db is not None:
        db.close()