aiosqlite.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. # dialects/sqlite/aiosqlite.py
  2. # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. r"""
  8. .. dialect:: sqlite+aiosqlite
  9. :name: aiosqlite
  10. :dbapi: aiosqlite
  11. :connectstring: sqlite+aiosqlite:///file_path
  12. :url: https://pypi.org/project/aiosqlite/
  13. The aiosqlite dialect provides support for the SQLAlchemy asyncio interface
  14. running on top of pysqlite.
  15. aiosqlite is a wrapper around pysqlite that uses a background thread for
  16. each connection. It does not actually use non-blocking IO, as SQLite
  17. databases are not socket-based. However it does provide a working asyncio
  18. interface that's useful for testing and prototyping purposes.
  19. Using a special asyncio mediation layer, the aiosqlite dialect is usable
  20. as the backend for the :ref:`SQLAlchemy asyncio <asyncio_toplevel>`
  21. extension package.
  22. This dialect should normally be used only with the
  23. :func:`_asyncio.create_async_engine` engine creation function::
  24. from sqlalchemy.ext.asyncio import create_async_engine
  25. engine = create_async_engine("sqlite+aiosqlite:///filename")
  26. The URL passes through all arguments to the ``pysqlite`` driver, so all
  27. connection arguments are the same as they are for that of :ref:`pysqlite`.
  28. .. _aiosqlite_udfs:
  29. User-Defined Functions
  30. ----------------------
  31. aiosqlite extends pysqlite to support async, so we can create our own user-defined functions (UDFs)
  32. in Python and use them directly in SQLite queries as described here: :ref:`pysqlite_udfs`.
  33. """ # noqa
  34. from .base import SQLiteExecutionContext
  35. from .pysqlite import SQLiteDialect_pysqlite
  36. from ... import pool
  37. from ... import util
  38. from ...engine import AdaptedConnection
  39. from ...util.concurrency import await_fallback
  40. from ...util.concurrency import await_only
  41. class AsyncAdapt_aiosqlite_cursor:
  42. __slots__ = (
  43. "_adapt_connection",
  44. "_connection",
  45. "description",
  46. "await_",
  47. "_rows",
  48. "arraysize",
  49. "rowcount",
  50. "lastrowid",
  51. )
  52. server_side = False
  53. def __init__(self, adapt_connection):
  54. self._adapt_connection = adapt_connection
  55. self._connection = adapt_connection._connection
  56. self.await_ = adapt_connection.await_
  57. self.arraysize = 1
  58. self.rowcount = -1
  59. self.description = None
  60. self._rows = []
  61. def close(self):
  62. self._rows[:] = []
  63. def execute(self, operation, parameters=None):
  64. try:
  65. _cursor = self.await_(self._connection.cursor())
  66. if parameters is None:
  67. self.await_(_cursor.execute(operation))
  68. else:
  69. self.await_(_cursor.execute(operation, parameters))
  70. if _cursor.description:
  71. self.description = _cursor.description
  72. self.lastrowid = self.rowcount = -1
  73. if not self.server_side:
  74. self._rows = self.await_(_cursor.fetchall())
  75. else:
  76. self.description = None
  77. self.lastrowid = _cursor.lastrowid
  78. self.rowcount = _cursor.rowcount
  79. if not self.server_side:
  80. self.await_(_cursor.close())
  81. else:
  82. self._cursor = _cursor
  83. except Exception as error:
  84. self._adapt_connection._handle_exception(error)
  85. def executemany(self, operation, seq_of_parameters):
  86. try:
  87. _cursor = self.await_(self._connection.cursor())
  88. self.await_(_cursor.executemany(operation, seq_of_parameters))
  89. self.description = None
  90. self.lastrowid = _cursor.lastrowid
  91. self.rowcount = _cursor.rowcount
  92. self.await_(_cursor.close())
  93. except Exception as error:
  94. self._adapt_connection._handle_exception(error)
  95. def setinputsizes(self, *inputsizes):
  96. pass
  97. def __iter__(self):
  98. while self._rows:
  99. yield self._rows.pop(0)
  100. def fetchone(self):
  101. if self._rows:
  102. return self._rows.pop(0)
  103. else:
  104. return None
  105. def fetchmany(self, size=None):
  106. if size is None:
  107. size = self.arraysize
  108. retval = self._rows[0:size]
  109. self._rows[:] = self._rows[size:]
  110. return retval
  111. def fetchall(self):
  112. retval = self._rows[:]
  113. self._rows[:] = []
  114. return retval
  115. class AsyncAdapt_aiosqlite_ss_cursor(AsyncAdapt_aiosqlite_cursor):
  116. __slots__ = "_cursor"
  117. server_side = True
  118. def __init__(self, *arg, **kw):
  119. super().__init__(*arg, **kw)
  120. self._cursor = None
  121. def close(self):
  122. if self._cursor is not None:
  123. self.await_(self._cursor.close())
  124. self._cursor = None
  125. def fetchone(self):
  126. return self.await_(self._cursor.fetchone())
  127. def fetchmany(self, size=None):
  128. if size is None:
  129. size = self.arraysize
  130. return self.await_(self._cursor.fetchmany(size=size))
  131. def fetchall(self):
  132. return self.await_(self._cursor.fetchall())
  133. class AsyncAdapt_aiosqlite_connection(AdaptedConnection):
  134. await_ = staticmethod(await_only)
  135. __slots__ = ("dbapi", "_connection")
  136. def __init__(self, dbapi, connection):
  137. self.dbapi = dbapi
  138. self._connection = connection
  139. @property
  140. def isolation_level(self):
  141. return self._connection.isolation_level
  142. @isolation_level.setter
  143. def isolation_level(self, value):
  144. try:
  145. self._connection.isolation_level = value
  146. except Exception as error:
  147. self._handle_exception(error)
  148. def create_function(self, *args, **kw):
  149. try:
  150. self.await_(self._connection.create_function(*args, **kw))
  151. except Exception as error:
  152. self._handle_exception(error)
  153. def cursor(self, server_side=False):
  154. if server_side:
  155. return AsyncAdapt_aiosqlite_ss_cursor(self)
  156. else:
  157. return AsyncAdapt_aiosqlite_cursor(self)
  158. def execute(self, *args, **kw):
  159. return self.await_(self._connection.execute(*args, **kw))
  160. def rollback(self):
  161. try:
  162. self.await_(self._connection.rollback())
  163. except Exception as error:
  164. self._handle_exception(error)
  165. def commit(self):
  166. try:
  167. self.await_(self._connection.commit())
  168. except Exception as error:
  169. self._handle_exception(error)
  170. def close(self):
  171. try:
  172. self.await_(self._connection.close())
  173. except Exception as error:
  174. self._handle_exception(error)
  175. def _handle_exception(self, error):
  176. if (
  177. isinstance(error, ValueError)
  178. and error.args[0] == "no active connection"
  179. ):
  180. util.raise_(
  181. self.dbapi.sqlite.OperationalError("no active connection"),
  182. from_=error,
  183. )
  184. else:
  185. raise error
  186. class AsyncAdaptFallback_aiosqlite_connection(AsyncAdapt_aiosqlite_connection):
  187. __slots__ = ()
  188. await_ = staticmethod(await_fallback)
  189. class AsyncAdapt_aiosqlite_dbapi:
  190. def __init__(self, aiosqlite, sqlite):
  191. self.aiosqlite = aiosqlite
  192. self.sqlite = sqlite
  193. self.paramstyle = "qmark"
  194. self._init_dbapi_attributes()
  195. def _init_dbapi_attributes(self):
  196. for name in (
  197. "DatabaseError",
  198. "Error",
  199. "IntegrityError",
  200. "NotSupportedError",
  201. "OperationalError",
  202. "ProgrammingError",
  203. "sqlite_version",
  204. "sqlite_version_info",
  205. ):
  206. setattr(self, name, getattr(self.aiosqlite, name))
  207. for name in ("PARSE_COLNAMES", "PARSE_DECLTYPES"):
  208. setattr(self, name, getattr(self.sqlite, name))
  209. for name in ("Binary",):
  210. setattr(self, name, getattr(self.sqlite, name))
  211. def connect(self, *arg, **kw):
  212. async_fallback = kw.pop("async_fallback", False)
  213. # Q. WHY do we need this?
  214. # A. Because there is no way to set connection.isolation_level
  215. # otherwise
  216. # Q. BUT HOW do you know it is SAFE ?????
  217. # A. The only operation that isn't safe is the isolation level set
  218. # operation which aiosqlite appears to have let slip through even
  219. # though pysqlite appears to do check_same_thread for this.
  220. # All execute operations etc. should be safe because they all
  221. # go through the single executor thread.
  222. kw["check_same_thread"] = False
  223. connection = self.aiosqlite.connect(*arg, **kw)
  224. # it's a Thread. you'll thank us later
  225. connection.daemon = True
  226. if util.asbool(async_fallback):
  227. return AsyncAdaptFallback_aiosqlite_connection(
  228. self,
  229. await_fallback(connection),
  230. )
  231. else:
  232. return AsyncAdapt_aiosqlite_connection(
  233. self,
  234. await_only(connection),
  235. )
  236. class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext):
  237. def create_server_side_cursor(self):
  238. return self._dbapi_connection.cursor(server_side=True)
  239. class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
  240. driver = "aiosqlite"
  241. supports_statement_cache = True
  242. is_async = True
  243. supports_server_side_cursors = True
  244. execution_ctx_cls = SQLiteExecutionContext_aiosqlite
  245. @classmethod
  246. def dbapi(cls):
  247. return AsyncAdapt_aiosqlite_dbapi(
  248. __import__("aiosqlite"), __import__("sqlite3")
  249. )
  250. @classmethod
  251. def get_pool_class(cls, url):
  252. if cls._is_url_file_db(url):
  253. return pool.NullPool
  254. else:
  255. return pool.StaticPool
  256. def is_disconnect(self, e, connection, cursor):
  257. if isinstance(
  258. e, self.dbapi.OperationalError
  259. ) and "no active connection" in str(e):
  260. return True
  261. return super().is_disconnect(e, connection, cursor)
  262. def get_driver_connection(self, connection):
  263. return connection._connection
  264. dialect = SQLiteDialect_aiosqlite