SQLAlchemy 2.0.43__py3-none-any.whl → 2.0.44__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. sqlalchemy/__init__.py +1 -1
  2. sqlalchemy/connectors/asyncio.py +81 -3
  3. sqlalchemy/dialects/mssql/base.py +5 -0
  4. sqlalchemy/dialects/mysql/aiomysql.py +11 -5
  5. sqlalchemy/dialects/mysql/asyncmy.py +11 -5
  6. sqlalchemy/dialects/oracle/oracledb.py +2 -4
  7. sqlalchemy/dialects/postgresql/array.py +15 -5
  8. sqlalchemy/dialects/postgresql/asyncpg.py +18 -27
  9. sqlalchemy/dialects/postgresql/json.py +28 -0
  10. sqlalchemy/dialects/postgresql/psycopg.py +3 -0
  11. sqlalchemy/dialects/sqlite/aiosqlite.py +3 -0
  12. sqlalchemy/dialects/sqlite/base.py +36 -13
  13. sqlalchemy/dialects/sqlite/pysqlite.py +68 -38
  14. sqlalchemy/event/__init__.py +1 -0
  15. sqlalchemy/event/attr.py +35 -14
  16. sqlalchemy/event/legacy.py +15 -3
  17. sqlalchemy/ext/asyncio/engine.py +4 -2
  18. sqlalchemy/ext/asyncio/result.py +3 -0
  19. sqlalchemy/ext/asyncio/scoping.py +0 -14
  20. sqlalchemy/ext/asyncio/session.py +0 -14
  21. sqlalchemy/ext/hybrid.py +8 -6
  22. sqlalchemy/ext/indexable.py +37 -19
  23. sqlalchemy/ext/mutable.py +26 -37
  24. sqlalchemy/orm/__init__.py +1 -0
  25. sqlalchemy/orm/decl_api.py +95 -11
  26. sqlalchemy/orm/events.py +60 -77
  27. sqlalchemy/orm/properties.py +39 -11
  28. sqlalchemy/orm/query.py +16 -10
  29. sqlalchemy/orm/scoping.py +0 -14
  30. sqlalchemy/orm/session.py +0 -14
  31. sqlalchemy/orm/writeonly.py +2 -2
  32. sqlalchemy/sql/dml.py +19 -6
  33. sqlalchemy/sql/elements.py +36 -1
  34. sqlalchemy/sql/functions.py +1 -1
  35. sqlalchemy/sql/schema.py +7 -3
  36. sqlalchemy/sql/selectable.py +40 -24
  37. sqlalchemy/sql/sqltypes.py +3 -3
  38. sqlalchemy/testing/plugin/pytestplugin.py +9 -3
  39. sqlalchemy/testing/profiling.py +3 -0
  40. sqlalchemy/testing/requirements.py +12 -1
  41. sqlalchemy/testing/suite/test_reflection.py +20 -0
  42. sqlalchemy/util/__init__.py +2 -0
  43. sqlalchemy/util/compat.py +13 -0
  44. sqlalchemy/util/concurrency.py +2 -0
  45. sqlalchemy/util/langhelpers.py +8 -5
  46. sqlalchemy/util/typing.py +3 -2
  47. {sqlalchemy-2.0.43.dist-info → sqlalchemy-2.0.44.dist-info}/METADATA +2 -2
  48. {sqlalchemy-2.0.43.dist-info → sqlalchemy-2.0.44.dist-info}/RECORD +51 -51
  49. {sqlalchemy-2.0.43.dist-info → sqlalchemy-2.0.44.dist-info}/WHEEL +0 -0
  50. {sqlalchemy-2.0.43.dist-info → sqlalchemy-2.0.44.dist-info}/licenses/LICENSE +0 -0
  51. {sqlalchemy-2.0.43.dist-info → sqlalchemy-2.0.44.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py CHANGED
@@ -269,7 +269,7 @@ from .types import Uuid as Uuid
269
269
  from .types import VARBINARY as VARBINARY
270
270
  from .types import VARCHAR as VARCHAR
271
271
 
272
- __version__ = "2.0.43"
272
+ __version__ = "2.0.44"
273
273
 
274
274
 
275
275
  def __go(lcls: Any) -> None:
@@ -19,11 +19,15 @@ from typing import Iterator
19
19
  from typing import NoReturn
20
20
  from typing import Optional
21
21
  from typing import Sequence
22
+ from typing import Tuple
23
+ from typing import Type
22
24
  from typing import TYPE_CHECKING
23
25
 
24
26
  from ..engine import AdaptedConnection
27
+ from ..util import EMPTY_DICT
25
28
  from ..util.concurrency import await_fallback
26
29
  from ..util.concurrency import await_only
30
+ from ..util.concurrency import in_greenlet
27
31
  from ..util.typing import Protocol
28
32
 
29
33
  if TYPE_CHECKING:
@@ -129,8 +133,11 @@ class AsyncAdapt_dbapi_cursor:
129
133
  "await_",
130
134
  "_cursor",
131
135
  "_rows",
136
+ "_soft_closed_memoized",
132
137
  )
133
138
 
139
+ _awaitable_cursor_close: bool = True
140
+
134
141
  _cursor: AsyncIODBAPICursor
135
142
  _adapt_connection: AsyncAdapt_dbapi_connection
136
143
  _connection: AsyncIODBAPIConnection
@@ -144,7 +151,7 @@ class AsyncAdapt_dbapi_cursor:
144
151
 
145
152
  cursor = self._make_new_cursor(self._connection)
146
153
  self._cursor = self._aenter_cursor(cursor)
147
-
154
+ self._soft_closed_memoized = EMPTY_DICT
148
155
  if not self.server_side:
149
156
  self._rows = collections.deque()
150
157
 
@@ -158,6 +165,8 @@ class AsyncAdapt_dbapi_cursor:
158
165
 
159
166
  @property
160
167
  def description(self) -> Optional[_DBAPICursorDescription]:
168
+ if "description" in self._soft_closed_memoized:
169
+ return self._soft_closed_memoized["description"] # type: ignore[no-any-return] # noqa: E501
161
170
  return self._cursor.description
162
171
 
163
172
  @property
@@ -176,11 +185,40 @@ class AsyncAdapt_dbapi_cursor:
176
185
  def lastrowid(self) -> int:
177
186
  return self._cursor.lastrowid
178
187
 
188
+ async def _async_soft_close(self) -> None:
189
+ """close the cursor but keep the results pending, and memoize the
190
+ description.
191
+
192
+ .. versionadded:: 2.0.44
193
+
194
+ """
195
+
196
+ if not self._awaitable_cursor_close or self.server_side:
197
+ return
198
+
199
+ self._soft_closed_memoized = self._soft_closed_memoized.union(
200
+ {
201
+ "description": self._cursor.description,
202
+ }
203
+ )
204
+ await self._cursor.close()
205
+
179
206
  def close(self) -> None:
180
- # note we aren't actually closing the cursor here,
181
- # we are just letting GC do it. see notes in aiomysql dialect
182
207
  self._rows.clear()
183
208
 
209
+ # updated as of 2.0.44
210
+ # try to "close" the cursor based on what we know about the driver
211
+ # and if we are able to. otherwise, hope that the asyncio
212
+ # extension called _async_soft_close() if the cursor is going into
213
+ # a sync context
214
+ if self._cursor is None or bool(self._soft_closed_memoized):
215
+ return
216
+
217
+ if not self._awaitable_cursor_close:
218
+ self._cursor.close() # type: ignore[unused-coroutine]
219
+ elif in_greenlet():
220
+ self.await_(self._cursor.close())
221
+
184
222
  def execute(
185
223
  self,
186
224
  operation: Any,
@@ -349,3 +387,43 @@ class AsyncAdaptFallback_dbapi_connection(AsyncAdapt_dbapi_connection):
349
387
  __slots__ = ()
350
388
 
351
389
  await_ = staticmethod(await_fallback)
390
+
391
+
392
+ class AsyncAdapt_terminate:
393
+ """Mixin for a AsyncAdapt_dbapi_connection to add terminate support."""
394
+
395
+ __slots__ = ()
396
+
397
+ def terminate(self) -> None:
398
+ if in_greenlet():
399
+ # in a greenlet; this is the connection was invalidated case.
400
+ try:
401
+ # try to gracefully close; see #10717
402
+ self.await_(asyncio.shield(self._terminate_graceful_close())) # type: ignore[attr-defined] # noqa: E501
403
+ except self._terminate_handled_exceptions() as e:
404
+ # in the case where we are recycling an old connection
405
+ # that may have already been disconnected, close() will
406
+ # fail. In this case, terminate
407
+ # the connection without any further waiting.
408
+ # see issue #8419
409
+ self._terminate_force_close()
410
+ if isinstance(e, asyncio.CancelledError):
411
+ # re-raise CancelledError if we were cancelled
412
+ raise
413
+ else:
414
+ # not in a greenlet; this is the gc cleanup case
415
+ self._terminate_force_close()
416
+
417
+ def _terminate_handled_exceptions(self) -> Tuple[Type[BaseException], ...]:
418
+ """Returns the exceptions that should be handled when
419
+ calling _graceful_close.
420
+ """
421
+ return (asyncio.TimeoutError, asyncio.CancelledError, OSError)
422
+
423
+ async def _terminate_graceful_close(self) -> None:
424
+ """Try to close connection gracefully"""
425
+ raise NotImplementedError
426
+
427
+ def _terminate_force_close(self) -> None:
428
+ """Terminate the connection"""
429
+ raise NotImplementedError
@@ -3503,6 +3503,9 @@ join sys.schemas as sch on
3503
3503
  where
3504
3504
  tab.name = :tabname
3505
3505
  and sch.name = :schname
3506
+ order by
3507
+ ind_col.index_id,
3508
+ ind_col.key_ordinal
3506
3509
  """
3507
3510
  )
3508
3511
  .bindparams(
@@ -3998,6 +4001,8 @@ index_info AS (
3998
4001
  index_info.index_schema = fk_info.unique_constraint_schema
3999
4002
  AND index_info.index_name = fk_info.unique_constraint_name
4000
4003
  AND index_info.ordinal_position = fk_info.ordinal_position
4004
+ AND NOT (index_info.table_schema = fk_info.table_schema
4005
+ AND index_info.table_name = fk_info.table_name)
4001
4006
 
4002
4007
  ORDER BY fk_info.constraint_schema, fk_info.constraint_name,
4003
4008
  fk_info.ordinal_position
@@ -45,6 +45,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
45
45
  from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
46
46
  from ...connectors.asyncio import AsyncAdapt_dbapi_module
47
47
  from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
48
+ from ...connectors.asyncio import AsyncAdapt_terminate
48
49
  from ...util.concurrency import await_fallback
49
50
  from ...util.concurrency import await_only
50
51
 
@@ -82,7 +83,9 @@ class AsyncAdapt_aiomysql_ss_cursor(
82
83
  )
83
84
 
84
85
 
85
- class AsyncAdapt_aiomysql_connection(AsyncAdapt_dbapi_connection):
86
+ class AsyncAdapt_aiomysql_connection(
87
+ AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
88
+ ):
86
89
  __slots__ = ()
87
90
 
88
91
  _cursor_cls = AsyncAdapt_aiomysql_cursor
@@ -101,13 +104,16 @@ class AsyncAdapt_aiomysql_connection(AsyncAdapt_dbapi_connection):
101
104
  def get_autocommit(self) -> bool:
102
105
  return self._connection.get_autocommit() # type: ignore
103
106
 
104
- def terminate(self) -> None:
105
- # it's not awaitable.
106
- self._connection.close()
107
-
108
107
  def close(self) -> None:
109
108
  self.await_(self._connection.ensure_closed())
110
109
 
110
+ async def _terminate_graceful_close(self) -> None:
111
+ await self._connection.ensure_closed()
112
+
113
+ def _terminate_force_close(self) -> None:
114
+ # it's not awaitable.
115
+ self._connection.close()
116
+
111
117
 
112
118
  class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection):
113
119
  __slots__ = ()
@@ -42,6 +42,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
42
42
  from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
43
43
  from ...connectors.asyncio import AsyncAdapt_dbapi_module
44
44
  from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
45
+ from ...connectors.asyncio import AsyncAdapt_terminate
45
46
  from ...util.concurrency import await_fallback
46
47
  from ...util.concurrency import await_only
47
48
 
@@ -73,7 +74,9 @@ class AsyncAdapt_asyncmy_ss_cursor(
73
74
  )
74
75
 
75
76
 
76
- class AsyncAdapt_asyncmy_connection(AsyncAdapt_dbapi_connection):
77
+ class AsyncAdapt_asyncmy_connection(
78
+ AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
79
+ ):
77
80
  __slots__ = ()
78
81
 
79
82
  _cursor_cls = AsyncAdapt_asyncmy_cursor
@@ -107,13 +110,16 @@ class AsyncAdapt_asyncmy_connection(AsyncAdapt_dbapi_connection):
107
110
  def get_autocommit(self) -> bool:
108
111
  return self._connection.get_autocommit() # type: ignore
109
112
 
110
- def terminate(self) -> None:
111
- # it's not awaitable.
112
- self._connection.close()
113
-
114
113
  def close(self) -> None:
115
114
  self.await_(self._connection.ensure_closed())
116
115
 
116
+ async def _terminate_graceful_close(self) -> None:
117
+ await self._connection.ensure_closed()
118
+
119
+ def _terminate_force_close(self) -> None:
120
+ # it's not awaitable.
121
+ self._connection.close()
122
+
117
123
 
118
124
  class AsyncAdaptFallback_asyncmy_connection(AsyncAdapt_asyncmy_connection):
119
125
  __slots__ = ()
@@ -736,6 +736,8 @@ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
736
736
 
737
737
  class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
738
738
  _cursor: AsyncCursor
739
+ _awaitable_cursor_close: bool = False
740
+
739
741
  __slots__ = ()
740
742
 
741
743
  @property
@@ -749,10 +751,6 @@ class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
749
751
  def var(self, *args, **kwargs):
750
752
  return self._cursor.var(*args, **kwargs)
751
753
 
752
- def close(self):
753
- self._rows.clear()
754
- self._cursor.close()
755
-
756
754
  def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
757
755
  return self._cursor.setinputsizes(*args, **kwargs)
758
756
 
@@ -44,6 +44,7 @@ if TYPE_CHECKING:
44
44
 
45
45
 
46
46
  _T = TypeVar("_T", bound=typing_Any)
47
+ _CT = TypeVar("_CT", bound=typing_Any)
47
48
 
48
49
 
49
50
  def Any(
@@ -357,7 +358,7 @@ class ARRAY(sqltypes.ARRAY[_T]):
357
358
  self.dimensions = dimensions
358
359
  self.zero_indexes = zero_indexes
359
360
 
360
- class Comparator(sqltypes.ARRAY.Comparator[_T]):
361
+ class Comparator(sqltypes.ARRAY.Comparator[_CT]):
361
362
  """Define comparison operations for :class:`_types.ARRAY`.
362
363
 
363
364
  Note that these operations are in addition to those provided
@@ -464,7 +465,7 @@ class ARRAY(sqltypes.ARRAY[_T]):
464
465
  super_rp = process
465
466
  pattern = re.compile(r"^{(.*)}$")
466
467
 
467
- def handle_raw_string(value: str) -> list[str]:
468
+ def handle_raw_string(value: str) -> Sequence[Optional[str]]:
468
469
  inner = pattern.match(value).group(1) # type: ignore[union-attr] # noqa: E501
469
470
  return _split_enum_values(inner)
470
471
 
@@ -485,10 +486,13 @@ class ARRAY(sqltypes.ARRAY[_T]):
485
486
  return process
486
487
 
487
488
 
488
- def _split_enum_values(array_string: str) -> list[str]:
489
+ def _split_enum_values(array_string: str) -> Sequence[Optional[str]]:
489
490
  if '"' not in array_string:
490
491
  # no escape char is present so it can just split on the comma
491
- return array_string.split(",") if array_string else []
492
+ return [
493
+ r if r != "NULL" else None
494
+ for r in (array_string.split(",") if array_string else [])
495
+ ]
492
496
 
493
497
  # handles quoted strings from:
494
498
  # r'abc,"quoted","also\\\\quoted", "quoted, comma", "esc \" quot", qpr'
@@ -505,5 +509,11 @@ def _split_enum_values(array_string: str) -> list[str]:
505
509
  elif in_quotes:
506
510
  result.append(tok.replace("_$ESC_QUOTE$_", '"'))
507
511
  else:
508
- result.extend(re.findall(r"([^\s,]+),?", tok))
512
+ # interpret NULL (without quotes!) as None
513
+ result.extend(
514
+ [
515
+ r if r != "NULL" else None
516
+ for r in re.findall(r"([^\s,]+),?", tok)
517
+ ]
518
+ )
509
519
  return result
@@ -205,6 +205,7 @@ from .types import CITEXT
205
205
  from ... import exc
206
206
  from ... import pool
207
207
  from ... import util
208
+ from ...connectors.asyncio import AsyncAdapt_terminate
208
209
  from ...engine import AdaptedConnection
209
210
  from ...engine import processors
210
211
  from ...sql import sqltypes
@@ -490,6 +491,7 @@ class AsyncAdapt_asyncpg_cursor:
490
491
  )
491
492
 
492
493
  server_side = False
494
+ _awaitable_cursor_close: bool = False
493
495
 
494
496
  def __init__(self, adapt_connection):
495
497
  self._adapt_connection = adapt_connection
@@ -501,6 +503,9 @@ class AsyncAdapt_asyncpg_cursor:
501
503
  self.rowcount = -1
502
504
  self._invalidate_schema_cache_asof = 0
503
505
 
506
+ async def _async_soft_close(self) -> None:
507
+ return
508
+
504
509
  def close(self):
505
510
  self._rows.clear()
506
511
 
@@ -691,7 +696,7 @@ class AsyncAdapt_asyncpg_ss_cursor(AsyncAdapt_asyncpg_cursor):
691
696
  )
692
697
 
693
698
 
694
- class AsyncAdapt_asyncpg_connection(AdaptedConnection):
699
+ class AsyncAdapt_asyncpg_connection(AsyncAdapt_terminate, AdaptedConnection):
695
700
  __slots__ = (
696
701
  "dbapi",
697
702
  "isolation_level",
@@ -897,32 +902,18 @@ class AsyncAdapt_asyncpg_connection(AdaptedConnection):
897
902
 
898
903
  self.await_(self._connection.close())
899
904
 
900
- def terminate(self):
901
- if util.concurrency.in_greenlet():
902
- # in a greenlet; this is the connection was invalidated
903
- # case.
904
- try:
905
- # try to gracefully close; see #10717
906
- # timeout added in asyncpg 0.14.0 December 2017
907
- self.await_(asyncio.shield(self._connection.close(timeout=2)))
908
- except (
909
- asyncio.TimeoutError,
910
- asyncio.CancelledError,
911
- OSError,
912
- self.dbapi.asyncpg.PostgresError,
913
- ) as e:
914
- # in the case where we are recycling an old connection
915
- # that may have already been disconnected, close() will
916
- # fail with the above timeout. in this case, terminate
917
- # the connection without any further waiting.
918
- # see issue #8419
919
- self._connection.terminate()
920
- if isinstance(e, asyncio.CancelledError):
921
- # re-raise CancelledError if we were cancelled
922
- raise
923
- else:
924
- # not in a greenlet; this is the gc cleanup case
925
- self._connection.terminate()
905
+ def _terminate_handled_exceptions(self):
906
+ return super()._terminate_handled_exceptions() + (
907
+ self.dbapi.asyncpg.PostgresError,
908
+ )
909
+
910
+ async def _terminate_graceful_close(self) -> None:
911
+ # timeout added in asyncpg 0.14.0 December 2017
912
+ await self._connection.close(timeout=2)
913
+ self._started = False
914
+
915
+ def _terminate_force_close(self) -> None:
916
+ self._connection.terminate()
926
917
  self._started = False
927
918
 
928
919
  @staticmethod
@@ -279,6 +279,34 @@ class JSONB(JSON):
279
279
 
280
280
  :class:`_types.JSON`
281
281
 
282
+ .. warning::
283
+
284
+ **For applications that have indexes against JSONB subscript
285
+ expressions**
286
+
287
+ SQLAlchemy 2.0.42 made a change in how the subscript operation for
288
+ :class:`.JSONB` is rendered, from ``-> 'element'`` to ``['element']``,
289
+ for PostgreSQL versions greater than 14. This change caused an
290
+ unintended side effect for indexes that were created against
291
+ expressions that use subscript notation, e.g.
292
+ ``Index("ix_entity_json_ab_text", data["a"]["b"].astext)``. If these
293
+ indexes were generated with the older syntax e.g. ``((entity.data ->
294
+ 'a') ->> 'b')``, they will not be used by the PostgreSQL query planner
295
+ when a query is made using SQLAlchemy 2.0.42 or higher on PostgreSQL
296
+ versions 14 or higher. This occurs because the new text will resemble
297
+ ``(entity.data['a'] ->> 'b')`` which will fail to produce the exact
298
+ textual syntax match required by the PostgreSQL query planner.
299
+ Therefore, for users upgrading to SQLAlchemy 2.0.42 or higher, existing
300
+ indexes that were created against :class:`.JSONB` expressions that use
301
+ subscripting would need to be dropped and re-created in order for them
302
+ to work with the new query syntax, e.g. an expression like
303
+ ``((entity.data -> 'a') ->> 'b')`` would become ``(entity.data['a'] ->>
304
+ 'b')``.
305
+
306
+ .. seealso::
307
+
308
+ :ticket:`12868` - discussion of this issue
309
+
282
310
  """
283
311
 
284
312
  __visit_name__ = "JSONB"
@@ -585,6 +585,9 @@ class AsyncAdapt_psycopg_cursor:
585
585
  def arraysize(self, value):
586
586
  self._cursor.arraysize = value
587
587
 
588
+ async def _async_soft_close(self) -> None:
589
+ return
590
+
588
591
  def close(self):
589
592
  self._rows.clear()
590
593
  # Normal cursor just call _close() in a non-sync way.
@@ -141,6 +141,9 @@ class AsyncAdapt_aiosqlite_cursor:
141
141
  self.description: Optional[_DBAPICursorDescription] = None
142
142
  self._rows: Deque[Any] = deque()
143
143
 
144
+ async def _async_soft_close(self) -> None:
145
+ return
146
+
144
147
  def close(self) -> None:
145
148
  self._rows.clear()
146
149
 
@@ -992,7 +992,10 @@ from __future__ import annotations
992
992
  import datetime
993
993
  import numbers
994
994
  import re
995
+ from typing import Any
996
+ from typing import Callable
995
997
  from typing import Optional
998
+ from typing import TYPE_CHECKING
996
999
 
997
1000
  from .json import JSON
998
1001
  from .json import JSONIndexType
@@ -1025,6 +1028,13 @@ from ...types import TEXT # noqa
1025
1028
  from ...types import TIMESTAMP # noqa
1026
1029
  from ...types import VARCHAR # noqa
1027
1030
 
1031
+ if TYPE_CHECKING:
1032
+ from ...engine.interfaces import DBAPIConnection
1033
+ from ...engine.interfaces import Dialect
1034
+ from ...engine.interfaces import IsolationLevel
1035
+ from ...sql.type_api import _BindProcessorType
1036
+ from ...sql.type_api import _ResultProcessorType
1037
+
1028
1038
 
1029
1039
  class _SQliteJson(JSON):
1030
1040
  def result_processor(self, dialect, coltype):
@@ -1163,7 +1173,9 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
1163
1173
  "%(hour)02d:%(minute)02d:%(second)02d"
1164
1174
  )
1165
1175
 
1166
- def bind_processor(self, dialect):
1176
+ def bind_processor(
1177
+ self, dialect: Dialect
1178
+ ) -> Optional[_BindProcessorType[Any]]:
1167
1179
  datetime_datetime = datetime.datetime
1168
1180
  datetime_date = datetime.date
1169
1181
  format_ = self._storage_format
@@ -1199,7 +1211,9 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
1199
1211
 
1200
1212
  return process
1201
1213
 
1202
- def result_processor(self, dialect, coltype):
1214
+ def result_processor(
1215
+ self, dialect: Dialect, coltype: object
1216
+ ) -> Optional[_ResultProcessorType[Any]]:
1203
1217
  if self._reg:
1204
1218
  return processors.str_to_datetime_processor_factory(
1205
1219
  self._reg, datetime.datetime
@@ -1254,7 +1268,9 @@ class DATE(_DateTimeMixin, sqltypes.Date):
1254
1268
 
1255
1269
  _storage_format = "%(year)04d-%(month)02d-%(day)02d"
1256
1270
 
1257
- def bind_processor(self, dialect):
1271
+ def bind_processor(
1272
+ self, dialect: Dialect
1273
+ ) -> Optional[_BindProcessorType[Any]]:
1258
1274
  datetime_date = datetime.date
1259
1275
  format_ = self._storage_format
1260
1276
 
@@ -1275,7 +1291,9 @@ class DATE(_DateTimeMixin, sqltypes.Date):
1275
1291
 
1276
1292
  return process
1277
1293
 
1278
- def result_processor(self, dialect, coltype):
1294
+ def result_processor(
1295
+ self, dialect: Dialect, coltype: object
1296
+ ) -> Optional[_ResultProcessorType[Any]]:
1279
1297
  if self._reg:
1280
1298
  return processors.str_to_datetime_processor_factory(
1281
1299
  self._reg, datetime.date
@@ -2125,13 +2143,13 @@ class SQLiteDialect(default.DefaultDialect):
2125
2143
  )
2126
2144
  def __init__(
2127
2145
  self,
2128
- native_datetime=False,
2129
- json_serializer=None,
2130
- json_deserializer=None,
2131
- _json_serializer=None,
2132
- _json_deserializer=None,
2133
- **kwargs,
2134
- ):
2146
+ native_datetime: bool = False,
2147
+ json_serializer: Optional[Callable[..., Any]] = None,
2148
+ json_deserializer: Optional[Callable[..., Any]] = None,
2149
+ _json_serializer: Optional[Callable[..., Any]] = None,
2150
+ _json_deserializer: Optional[Callable[..., Any]] = None,
2151
+ **kwargs: Any,
2152
+ ) -> None:
2135
2153
  default.DefaultDialect.__init__(self, **kwargs)
2136
2154
 
2137
2155
  if _json_serializer:
@@ -2199,7 +2217,9 @@ class SQLiteDialect(default.DefaultDialect):
2199
2217
  def get_isolation_level_values(self, dbapi_connection):
2200
2218
  return list(self._isolation_lookup)
2201
2219
 
2202
- def set_isolation_level(self, dbapi_connection, level):
2220
+ def set_isolation_level(
2221
+ self, dbapi_connection: DBAPIConnection, level: IsolationLevel
2222
+ ) -> None:
2203
2223
  isolation_level = self._isolation_lookup[level]
2204
2224
 
2205
2225
  cursor = dbapi_connection.cursor()
@@ -2387,7 +2407,10 @@ class SQLiteDialect(default.DefaultDialect):
2387
2407
  )
2388
2408
  # remove create table
2389
2409
  match = re.match(
2390
- r"create table .*?\((.*)\)$",
2410
+ (
2411
+ r"create table .*?\((.*)\)"
2412
+ r"(?:\s*,?\s*(?:WITHOUT\s+ROWID|STRICT))*$"
2413
+ ),
2391
2414
  tablesql.strip(),
2392
2415
  re.DOTALL | re.IGNORECASE,
2393
2416
  )