You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					337 lines
				
				9.8 KiB
			
		
		
			
		
	
	
					337 lines
				
				9.8 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# sqlite/aiosqlite.py
							 | 
						||
| 
								 | 
							
								# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
							 | 
						||
| 
								 | 
							
								# <see AUTHORS file>
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This module is part of SQLAlchemy and is released under
							 | 
						||
| 
								 | 
							
								# the MIT License: https://www.opensource.org/licenses/mit-license.php
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								r"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. dialect:: sqlite+aiosqlite
							 | 
						||
| 
								 | 
							
								    :name: aiosqlite
							 | 
						||
| 
								 | 
							
								    :dbapi: aiosqlite
							 | 
						||
| 
								 | 
							
								    :connectstring: sqlite+aiosqlite:///file_path
							 | 
						||
| 
								 | 
							
								    :url: https://pypi.org/project/aiosqlite/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The aiosqlite dialect provides support for the SQLAlchemy asyncio interface
							 | 
						||
| 
								 | 
							
								running on top of pysqlite.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								aiosqlite is a wrapper around pysqlite that uses a background thread for
							 | 
						||
| 
								 | 
							
								each connection.   It does not actually use non-blocking IO, as SQLite
							 | 
						||
| 
								 | 
							
								databases are not socket-based.  However it does provide a working asyncio
							 | 
						||
| 
								 | 
							
								interface that's useful for testing and prototyping purposes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Using a special asyncio mediation layer, the aiosqlite dialect is usable
							 | 
						||
| 
								 | 
							
								as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
							 | 
						||
| 
								 | 
							
								extension package.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This dialect should normally be used only with the
							 | 
						||
| 
								 | 
							
								:func:`_asyncio.create_async_engine` engine creation function::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    from sqlalchemy.ext.asyncio import create_async_engine
							 | 
						||
| 
								 | 
							
								    engine = create_async_engine("sqlite+aiosqlite:///filename")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The URL passes through all arguments to the ``pysqlite`` driver, so all
							 | 
						||
| 
								 | 
							
								connection arguments are the same as they are for that of :ref:`pysqlite`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								"""  # noqa
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .base import SQLiteExecutionContext
							 | 
						||
| 
								 | 
							
								from .pysqlite import SQLiteDialect_pysqlite
							 | 
						||
| 
								 | 
							
								from ... import pool
							 | 
						||
| 
								 | 
							
								from ... import util
							 | 
						||
| 
								 | 
							
								from ...engine import AdaptedConnection
							 | 
						||
| 
								 | 
							
								from ...util.concurrency import await_fallback
							 | 
						||
| 
								 | 
							
								from ...util.concurrency import await_only
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AsyncAdapt_aiosqlite_cursor:
							 | 
						||
| 
								 | 
							
								    __slots__ = (
							 | 
						||
| 
								 | 
							
								        "_adapt_connection",
							 | 
						||
| 
								 | 
							
								        "_connection",
							 | 
						||
| 
								 | 
							
								        "description",
							 | 
						||
| 
								 | 
							
								        "await_",
							 | 
						||
| 
								 | 
							
								        "_rows",
							 | 
						||
| 
								 | 
							
								        "arraysize",
							 | 
						||
| 
								 | 
							
								        "rowcount",
							 | 
						||
| 
								 | 
							
								        "lastrowid",
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    server_side = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, adapt_connection):
							 | 
						||
| 
								 | 
							
								        self._adapt_connection = adapt_connection
							 | 
						||
| 
								 | 
							
								        self._connection = adapt_connection._connection
							 | 
						||
| 
								 | 
							
								        self.await_ = adapt_connection.await_
							 | 
						||
| 
								 | 
							
								        self.arraysize = 1
							 | 
						||
| 
								 | 
							
								        self.rowcount = -1
							 | 
						||
| 
								 | 
							
								        self.description = None
							 | 
						||
| 
								 | 
							
								        self._rows = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def close(self):
							 | 
						||
| 
								 | 
							
								        self._rows[:] = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def execute(self, operation, parameters=None):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _cursor = self.await_(self._connection.cursor())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if parameters is None:
							 | 
						||
| 
								 | 
							
								                self.await_(_cursor.execute(operation))
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                self.await_(_cursor.execute(operation, parameters))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if _cursor.description:
							 | 
						||
| 
								 | 
							
								                self.description = _cursor.description
							 | 
						||
| 
								 | 
							
								                self.lastrowid = self.rowcount = -1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if not self.server_side:
							 | 
						||
| 
								 | 
							
								                    self._rows = self.await_(_cursor.fetchall())
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                self.description = None
							 | 
						||
| 
								 | 
							
								                self.lastrowid = _cursor.lastrowid
							 | 
						||
| 
								 | 
							
								                self.rowcount = _cursor.rowcount
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if not self.server_side:
							 | 
						||
| 
								 | 
							
								                self.await_(_cursor.close())
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                self._cursor = _cursor
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._adapt_connection._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def executemany(self, operation, seq_of_parameters):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _cursor = self.await_(self._connection.cursor())
							 | 
						||
| 
								 | 
							
								            self.await_(_cursor.executemany(operation, seq_of_parameters))
							 | 
						||
| 
								 | 
							
								            self.description = None
							 | 
						||
| 
								 | 
							
								            self.lastrowid = _cursor.lastrowid
							 | 
						||
| 
								 | 
							
								            self.rowcount = _cursor.rowcount
							 | 
						||
| 
								 | 
							
								            self.await_(_cursor.close())
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._adapt_connection._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def setinputsizes(self, *inputsizes):
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        while self._rows:
							 | 
						||
| 
								 | 
							
								            yield self._rows.pop(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchone(self):
							 | 
						||
| 
								 | 
							
								        if self._rows:
							 | 
						||
| 
								 | 
							
								            return self._rows.pop(0)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchmany(self, size=None):
							 | 
						||
| 
								 | 
							
								        if size is None:
							 | 
						||
| 
								 | 
							
								            size = self.arraysize
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        retval = self._rows[0:size]
							 | 
						||
| 
								 | 
							
								        self._rows[:] = self._rows[size:]
							 | 
						||
| 
								 | 
							
								        return retval
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchall(self):
							 | 
						||
| 
								 | 
							
								        retval = self._rows[:]
							 | 
						||
| 
								 | 
							
								        self._rows[:] = []
							 | 
						||
| 
								 | 
							
								        return retval
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AsyncAdapt_aiosqlite_ss_cursor(AsyncAdapt_aiosqlite_cursor):
							 | 
						||
| 
								 | 
							
								    __slots__ = "_cursor"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    server_side = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *arg, **kw):
							 | 
						||
| 
								 | 
							
								        super().__init__(*arg, **kw)
							 | 
						||
| 
								 | 
							
								        self._cursor = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def close(self):
							 | 
						||
| 
								 | 
							
								        if self._cursor is not None:
							 | 
						||
| 
								 | 
							
								            self.await_(self._cursor.close())
							 | 
						||
| 
								 | 
							
								            self._cursor = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchone(self):
							 | 
						||
| 
								 | 
							
								        return self.await_(self._cursor.fetchone())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchmany(self, size=None):
							 | 
						||
| 
								 | 
							
								        if size is None:
							 | 
						||
| 
								 | 
							
								            size = self.arraysize
							 | 
						||
| 
								 | 
							
								        return self.await_(self._cursor.fetchmany(size=size))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fetchall(self):
							 | 
						||
| 
								 | 
							
								        return self.await_(self._cursor.fetchall())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AsyncAdapt_aiosqlite_connection(AdaptedConnection):
							 | 
						||
| 
								 | 
							
								    await_ = staticmethod(await_only)
							 | 
						||
| 
								 | 
							
								    __slots__ = ("dbapi", "_connection")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, dbapi, connection):
							 | 
						||
| 
								 | 
							
								        self.dbapi = dbapi
							 | 
						||
| 
								 | 
							
								        self._connection = connection
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def isolation_level(self):
							 | 
						||
| 
								 | 
							
								        return self._connection.isolation_level
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @isolation_level.setter
							 | 
						||
| 
								 | 
							
								    def isolation_level(self, value):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self._connection.isolation_level = value
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_function(self, *args, **kw):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.await_(self._connection.create_function(*args, **kw))
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def cursor(self, server_side=False):
							 | 
						||
| 
								 | 
							
								        if server_side:
							 | 
						||
| 
								 | 
							
								            return AsyncAdapt_aiosqlite_ss_cursor(self)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return AsyncAdapt_aiosqlite_cursor(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def execute(self, *args, **kw):
							 | 
						||
| 
								 | 
							
								        return self.await_(self._connection.execute(*args, **kw))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def rollback(self):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.await_(self._connection.rollback())
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def commit(self):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.await_(self._connection.commit())
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def close(self):
							 | 
						||
| 
								 | 
							
								        # print(">close", self)
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.await_(self._connection.close())
							 | 
						||
| 
								 | 
							
								        except Exception as error:
							 | 
						||
| 
								 | 
							
								            self._handle_exception(error)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _handle_exception(self, error):
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								            isinstance(error, ValueError)
							 | 
						||
| 
								 | 
							
								            and error.args[0] == "no active connection"
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            util.raise_(
							 | 
						||
| 
								 | 
							
								                self.dbapi.sqlite.OperationalError("no active connection"),
							 | 
						||
| 
								 | 
							
								                from_=error,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AsyncAdaptFallback_aiosqlite_connection(AsyncAdapt_aiosqlite_connection):
							 | 
						||
| 
								 | 
							
								    __slots__ = ()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    await_ = staticmethod(await_fallback)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AsyncAdapt_aiosqlite_dbapi:
							 | 
						||
| 
								 | 
							
								    def __init__(self, aiosqlite, sqlite):
							 | 
						||
| 
								 | 
							
								        self.aiosqlite = aiosqlite
							 | 
						||
| 
								 | 
							
								        self.sqlite = sqlite
							 | 
						||
| 
								 | 
							
								        self.paramstyle = "qmark"
							 | 
						||
| 
								 | 
							
								        self._init_dbapi_attributes()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _init_dbapi_attributes(self):
							 | 
						||
| 
								 | 
							
								        for name in (
							 | 
						||
| 
								 | 
							
								            "DatabaseError",
							 | 
						||
| 
								 | 
							
								            "Error",
							 | 
						||
| 
								 | 
							
								            "IntegrityError",
							 | 
						||
| 
								 | 
							
								            "NotSupportedError",
							 | 
						||
| 
								 | 
							
								            "OperationalError",
							 | 
						||
| 
								 | 
							
								            "ProgrammingError",
							 | 
						||
| 
								 | 
							
								            "sqlite_version",
							 | 
						||
| 
								 | 
							
								            "sqlite_version_info",
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            setattr(self, name, getattr(self.aiosqlite, name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for name in ("PARSE_COLNAMES", "PARSE_DECLTYPES"):
							 | 
						||
| 
								 | 
							
								            setattr(self, name, getattr(self.sqlite, name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for name in ("Binary",):
							 | 
						||
| 
								 | 
							
								            setattr(self, name, getattr(self.sqlite, name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def connect(self, *arg, **kw):
							 | 
						||
| 
								 | 
							
								        async_fallback = kw.pop("async_fallback", False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Q. WHY do we need this?
							 | 
						||
| 
								 | 
							
								        # A. Because there is no way to set connection.isolation_level
							 | 
						||
| 
								 | 
							
								        #    otherwise
							 | 
						||
| 
								 | 
							
								        # Q. BUT HOW do you know it is SAFE ?????
							 | 
						||
| 
								 | 
							
								        # A. The only operation that isn't safe is the isolation level set
							 | 
						||
| 
								 | 
							
								        #    operation which aiosqlite appears to have let slip through even
							 | 
						||
| 
								 | 
							
								        #    though pysqlite appears to do check_same_thread for this.
							 | 
						||
| 
								 | 
							
								        #    All execute operations etc. should be safe because they all
							 | 
						||
| 
								 | 
							
								        #    go through the single executor thread.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        kw["check_same_thread"] = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        connection = self.aiosqlite.connect(*arg, **kw)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # it's a Thread.   you'll thank us later
							 | 
						||
| 
								 | 
							
								        connection.daemon = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if util.asbool(async_fallback):
							 | 
						||
| 
								 | 
							
								            return AsyncAdaptFallback_aiosqlite_connection(
							 | 
						||
| 
								 | 
							
								                self,
							 | 
						||
| 
								 | 
							
								                await_fallback(connection),
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return AsyncAdapt_aiosqlite_connection(
							 | 
						||
| 
								 | 
							
								                self,
							 | 
						||
| 
								 | 
							
								                await_only(connection),
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext):
							 | 
						||
| 
								 | 
							
								    def create_server_side_cursor(self):
							 | 
						||
| 
								 | 
							
								        return self._dbapi_connection.cursor(server_side=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
							 | 
						||
| 
								 | 
							
								    driver = "aiosqlite"
							 | 
						||
| 
								 | 
							
								    supports_statement_cache = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    is_async = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    supports_server_side_cursors = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    execution_ctx_cls = SQLiteExecutionContext_aiosqlite
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def dbapi(cls):
							 | 
						||
| 
								 | 
							
								        return AsyncAdapt_aiosqlite_dbapi(
							 | 
						||
| 
								 | 
							
								            __import__("aiosqlite"), __import__("sqlite3")
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def get_pool_class(cls, url):
							 | 
						||
| 
								 | 
							
								        if cls._is_url_file_db(url):
							 | 
						||
| 
								 | 
							
								            return pool.NullPool
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return pool.StaticPool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_disconnect(self, e, connection, cursor):
							 | 
						||
| 
								 | 
							
								        if isinstance(
							 | 
						||
| 
								 | 
							
								            e, self.dbapi.OperationalError
							 | 
						||
| 
								 | 
							
								        ) and "no active connection" in str(e):
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return super().is_disconnect(e, connection, cursor)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_driver_connection(self, connection):
							 | 
						||
| 
								 | 
							
								        return connection._connection
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								dialect = SQLiteDialect_aiosqlite
							 |