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.

333 lines
9.6KB

  1. # sqlite/aiosqlite.py
  2. # Copyright (C) 2005-2021 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: http://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. """ # noqa
  29. from .base import SQLiteExecutionContext
  30. from .pysqlite import SQLiteDialect_pysqlite
  31. from ... import pool
  32. from ... import util
  33. from ...util.concurrency import await_fallback
  34. from ...util.concurrency import await_only
  35. class AsyncAdapt_aiosqlite_cursor:
  36. __slots__ = (
  37. "_adapt_connection",
  38. "_connection",
  39. "description",
  40. "await_",
  41. "_rows",
  42. "arraysize",
  43. "rowcount",
  44. "lastrowid",
  45. )
  46. server_side = False
  47. def __init__(self, adapt_connection):
  48. self._adapt_connection = adapt_connection
  49. self._connection = adapt_connection._connection
  50. self.await_ = adapt_connection.await_
  51. self.arraysize = 1
  52. self.rowcount = -1
  53. self.description = None
  54. self._rows = []
  55. def close(self):
  56. self._rows[:] = []
  57. def execute(self, operation, parameters=None):
  58. try:
  59. _cursor = self.await_(self._connection.cursor())
  60. if parameters is None:
  61. self.await_(_cursor.execute(operation))
  62. else:
  63. self.await_(_cursor.execute(operation, parameters))
  64. if _cursor.description:
  65. self.description = _cursor.description
  66. self.lastrowid = self.rowcount = -1
  67. if not self.server_side:
  68. self._rows = self.await_(_cursor.fetchall())
  69. else:
  70. self.description = None
  71. self.lastrowid = _cursor.lastrowid
  72. self.rowcount = _cursor.rowcount
  73. if not self.server_side:
  74. self.await_(_cursor.close())
  75. else:
  76. self._cursor = _cursor
  77. except Exception as error:
  78. self._adapt_connection._handle_exception(error)
  79. def executemany(self, operation, seq_of_parameters):
  80. try:
  81. _cursor = self.await_(self._connection.cursor())
  82. self.await_(_cursor.executemany(operation, seq_of_parameters))
  83. self.description = None
  84. self.lastrowid = _cursor.lastrowid
  85. self.rowcount = _cursor.rowcount
  86. self.await_(_cursor.close())
  87. except Exception as error:
  88. self._adapt_connection._handle_exception(error)
  89. def setinputsizes(self, *inputsizes):
  90. pass
  91. def __iter__(self):
  92. while self._rows:
  93. yield self._rows.pop(0)
  94. def fetchone(self):
  95. if self._rows:
  96. return self._rows.pop(0)
  97. else:
  98. return None
  99. def fetchmany(self, size=None):
  100. if size is None:
  101. size = self.arraysize
  102. retval = self._rows[0:size]
  103. self._rows[:] = self._rows[size:]
  104. return retval
  105. def fetchall(self):
  106. retval = self._rows[:]
  107. self._rows[:] = []
  108. return retval
  109. class AsyncAdapt_aiosqlite_ss_cursor(AsyncAdapt_aiosqlite_cursor):
  110. __slots__ = "_cursor"
  111. server_side = True
  112. def __init__(self, *arg, **kw):
  113. super().__init__(*arg, **kw)
  114. self._cursor = None
  115. def close(self):
  116. if self._cursor is not None:
  117. self.await_(self._cursor.close())
  118. self._cursor = None
  119. def fetchone(self):
  120. return self.await_(self._cursor.fetchone())
  121. def fetchmany(self, size=None):
  122. if size is None:
  123. size = self.arraysize
  124. return self.await_(self._cursor.fetchmany(size=size))
  125. def fetchall(self):
  126. return self.await_(self._cursor.fetchall())
  127. class AsyncAdapt_aiosqlite_connection:
  128. await_ = staticmethod(await_only)
  129. __slots__ = ("dbapi", "_connection")
  130. def __init__(self, dbapi, connection):
  131. self.dbapi = dbapi
  132. self._connection = connection
  133. @property
  134. def isolation_level(self):
  135. return self._connection.isolation_level
  136. @isolation_level.setter
  137. def isolation_level(self, value):
  138. try:
  139. self._connection.isolation_level = value
  140. except Exception as error:
  141. self._handle_exception(error)
  142. def create_function(self, *args, **kw):
  143. try:
  144. self.await_(self._connection.create_function(*args, **kw))
  145. except Exception as error:
  146. self._handle_exception(error)
  147. def cursor(self, server_side=False):
  148. if server_side:
  149. return AsyncAdapt_aiosqlite_ss_cursor(self)
  150. else:
  151. return AsyncAdapt_aiosqlite_cursor(self)
  152. def execute(self, *args, **kw):
  153. return self.await_(self._connection.execute(*args, **kw))
  154. def rollback(self):
  155. try:
  156. self.await_(self._connection.rollback())
  157. except Exception as error:
  158. self._handle_exception(error)
  159. def commit(self):
  160. try:
  161. self.await_(self._connection.commit())
  162. except Exception as error:
  163. self._handle_exception(error)
  164. def close(self):
  165. # print(">close", self)
  166. try:
  167. self.await_(self._connection.close())
  168. except Exception as error:
  169. self._handle_exception(error)
  170. def _handle_exception(self, error):
  171. if (
  172. isinstance(error, ValueError)
  173. and error.args[0] == "no active connection"
  174. ):
  175. util.raise_(
  176. self.dbapi.sqlite.OperationalError("no active connection"),
  177. from_=error,
  178. )
  179. else:
  180. raise error
  181. class AsyncAdaptFallback_aiosqlite_connection(AsyncAdapt_aiosqlite_connection):
  182. __slots__ = ()
  183. await_ = staticmethod(await_fallback)
  184. class AsyncAdapt_aiosqlite_dbapi:
  185. def __init__(self, aiosqlite, sqlite):
  186. self.aiosqlite = aiosqlite
  187. self.sqlite = sqlite
  188. self.paramstyle = "qmark"
  189. self._init_dbapi_attributes()
  190. def _init_dbapi_attributes(self):
  191. for name in (
  192. "DatabaseError",
  193. "Error",
  194. "IntegrityError",
  195. "NotSupportedError",
  196. "OperationalError",
  197. "ProgrammingError",
  198. "sqlite_version",
  199. "sqlite_version_info",
  200. ):
  201. setattr(self, name, getattr(self.aiosqlite, name))
  202. for name in ("PARSE_COLNAMES", "PARSE_DECLTYPES"):
  203. setattr(self, name, getattr(self.sqlite, name))
  204. for name in ("Binary",):
  205. setattr(self, name, getattr(self.sqlite, name))
  206. def connect(self, *arg, **kw):
  207. async_fallback = kw.pop("async_fallback", False)
  208. # Q. WHY do we need this?
  209. # A. Because there is no way to set connection.isolation_level
  210. # otherwise
  211. # Q. BUT HOW do you know it is SAFE ?????
  212. # A. The only operation that isn't safe is the isolation level set
  213. # operation which aiosqlite appears to have let slip through even
  214. # though pysqlite appears to do check_same_thread for this.
  215. # All execute operations etc. should be safe because they all
  216. # go through the single executor thread.
  217. kw["check_same_thread"] = False
  218. connection = self.aiosqlite.connect(*arg, **kw)
  219. # it's a Thread. you'll thank us later
  220. connection.daemon = True
  221. if util.asbool(async_fallback):
  222. return AsyncAdaptFallback_aiosqlite_connection(
  223. self,
  224. await_fallback(connection),
  225. )
  226. else:
  227. return AsyncAdapt_aiosqlite_connection(
  228. self,
  229. await_only(connection),
  230. )
  231. class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext):
  232. def create_server_side_cursor(self):
  233. return self._dbapi_connection.cursor(server_side=True)
  234. class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite):
  235. driver = "aiosqlite"
  236. supports_statement_cache = True
  237. is_async = True
  238. supports_server_side_cursors = True
  239. execution_ctx_cls = SQLiteExecutionContext_aiosqlite
  240. @classmethod
  241. def dbapi(cls):
  242. return AsyncAdapt_aiosqlite_dbapi(
  243. __import__("aiosqlite"), __import__("sqlite3")
  244. )
  245. @classmethod
  246. def get_pool_class(cls, url):
  247. if cls._is_url_file_db(url):
  248. return pool.NullPool
  249. else:
  250. return pool.StaticPool
  251. def is_disconnect(self, e, connection, cursor):
  252. if isinstance(
  253. e, self.dbapi.OperationalError
  254. ) and "no active connection" in str(e):
  255. return True
  256. return super().is_disconnect(e, connection, cursor)
  257. dialect = SQLiteDialect_aiosqlite