SQLAlchemy 2.0.41__py3-none-any.whl → 2.0.43__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.
- sqlalchemy/__init__.py +1 -1
- sqlalchemy/connectors/aioodbc.py +10 -0
- sqlalchemy/connectors/asyncio.py +210 -72
- sqlalchemy/connectors/pyodbc.py +8 -5
- sqlalchemy/cyextension/util.pyx +15 -16
- sqlalchemy/dialects/__init__.py +2 -1
- sqlalchemy/dialects/mssql/base.py +85 -56
- sqlalchemy/dialects/mssql/information_schema.py +47 -16
- sqlalchemy/dialects/mysql/aiomysql.py +85 -176
- sqlalchemy/dialects/mysql/asyncmy.py +76 -190
- sqlalchemy/dialects/mysql/base.py +615 -273
- sqlalchemy/dialects/mysql/cymysql.py +34 -12
- sqlalchemy/dialects/mysql/enumerated.py +65 -26
- sqlalchemy/dialects/mysql/expression.py +6 -3
- sqlalchemy/dialects/mysql/json.py +24 -14
- sqlalchemy/dialects/mysql/mariadb.py +12 -6
- sqlalchemy/dialects/mysql/mariadbconnector.py +76 -31
- sqlalchemy/dialects/mysql/mysqlconnector.py +84 -35
- sqlalchemy/dialects/mysql/mysqldb.py +59 -50
- sqlalchemy/dialects/mysql/provision.py +0 -1
- sqlalchemy/dialects/mysql/pymysql.py +32 -10
- sqlalchemy/dialects/mysql/pyodbc.py +32 -14
- sqlalchemy/dialects/mysql/reflection.py +87 -37
- sqlalchemy/dialects/mysql/reserved_words.py +0 -1
- sqlalchemy/dialects/mysql/types.py +120 -58
- sqlalchemy/dialects/oracle/__init__.py +4 -0
- sqlalchemy/dialects/oracle/base.py +79 -24
- sqlalchemy/dialects/oracle/cx_oracle.py +3 -0
- sqlalchemy/dialects/oracle/vector.py +104 -6
- sqlalchemy/dialects/postgresql/_psycopg_common.py +3 -0
- sqlalchemy/dialects/postgresql/array.py +1 -1
- sqlalchemy/dialects/postgresql/asyncpg.py +7 -1
- sqlalchemy/dialects/postgresql/base.py +66 -15
- sqlalchemy/dialects/postgresql/named_types.py +0 -14
- sqlalchemy/dialects/postgresql/pg8000.py +3 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +14 -0
- sqlalchemy/dialects/postgresql/ranges.py +2 -2
- sqlalchemy/dialects/sqlite/aiosqlite.py +89 -44
- sqlalchemy/dialects/sqlite/base.py +8 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +23 -2
- sqlalchemy/engine/_py_util.py +2 -2
- sqlalchemy/engine/base.py +12 -8
- sqlalchemy/engine/create.py +15 -0
- sqlalchemy/engine/cursor.py +3 -3
- sqlalchemy/engine/default.py +15 -6
- sqlalchemy/engine/interfaces.py +61 -10
- sqlalchemy/engine/result.py +1 -1
- sqlalchemy/engine/strategies.py +1 -4
- sqlalchemy/event/api.py +1 -3
- sqlalchemy/ext/associationproxy.py +15 -1
- sqlalchemy/ext/asyncio/base.py +1 -1
- sqlalchemy/ext/asyncio/engine.py +1 -1
- sqlalchemy/ext/asyncio/result.py +1 -1
- sqlalchemy/ext/indexable.py +1 -0
- sqlalchemy/ext/mutable.py +1 -0
- sqlalchemy/ext/mypy/names.py +1 -1
- sqlalchemy/ext/orderinglist.py +48 -36
- sqlalchemy/orm/_orm_constructors.py +79 -8
- sqlalchemy/orm/attributes.py +21 -11
- sqlalchemy/orm/base.py +3 -5
- sqlalchemy/orm/bulk_persistence.py +17 -5
- sqlalchemy/orm/collections.py +1 -1
- sqlalchemy/orm/context.py +2 -2
- sqlalchemy/orm/decl_api.py +3 -0
- sqlalchemy/orm/decl_base.py +11 -5
- sqlalchemy/orm/dependency.py +1 -3
- sqlalchemy/orm/descriptor_props.py +16 -1
- sqlalchemy/orm/events.py +2 -4
- sqlalchemy/orm/interfaces.py +7 -1
- sqlalchemy/orm/loading.py +13 -9
- sqlalchemy/orm/mapper.py +12 -8
- sqlalchemy/orm/path_registry.py +1 -3
- sqlalchemy/orm/persistence.py +8 -2
- sqlalchemy/orm/properties.py +39 -16
- sqlalchemy/orm/relationships.py +1 -2
- sqlalchemy/orm/session.py +1 -1
- sqlalchemy/orm/state_changes.py +1 -3
- sqlalchemy/orm/strategies.py +1 -1
- sqlalchemy/orm/strategy_options.py +25 -6
- sqlalchemy/orm/util.py +1 -1
- sqlalchemy/orm/writeonly.py +2 -6
- sqlalchemy/pool/base.py +3 -3
- sqlalchemy/pool/impl.py +5 -7
- sqlalchemy/schema.py +1 -3
- sqlalchemy/sql/_selectable_constructors.py +61 -13
- sqlalchemy/sql/_typing.py +2 -2
- sqlalchemy/sql/base.py +104 -82
- sqlalchemy/sql/coercions.py +1 -1
- sqlalchemy/sql/compiler.py +66 -13
- sqlalchemy/sql/crud.py +66 -0
- sqlalchemy/sql/ddl.py +3 -1
- sqlalchemy/sql/default_comparator.py +1 -2
- sqlalchemy/sql/elements.py +41 -32
- sqlalchemy/sql/expression.py +1 -4
- sqlalchemy/sql/functions.py +2 -4
- sqlalchemy/sql/lambdas.py +11 -9
- sqlalchemy/sql/naming.py +1 -4
- sqlalchemy/sql/schema.py +3 -3
- sqlalchemy/sql/selectable.py +42 -16
- sqlalchemy/sql/sqltypes.py +14 -5
- sqlalchemy/sql/type_api.py +16 -10
- sqlalchemy/sql/util.py +2 -4
- sqlalchemy/sql/visitors.py +1 -4
- sqlalchemy/testing/assertions.py +2 -0
- sqlalchemy/testing/exclusions.py +2 -2
- sqlalchemy/testing/fixtures/base.py +5 -0
- sqlalchemy/testing/requirements.py +25 -0
- sqlalchemy/testing/suite/test_cte.py +26 -0
- sqlalchemy/testing/suite/test_dialect.py +37 -1
- sqlalchemy/testing/suite/test_reflection.py +114 -19
- sqlalchemy/testing/util.py +5 -8
- sqlalchemy/types.py +1 -3
- sqlalchemy/util/_py_collections.py +4 -4
- {sqlalchemy-2.0.41.dist-info → sqlalchemy-2.0.43.dist-info}/METADATA +1 -1
- {sqlalchemy-2.0.41.dist-info → sqlalchemy-2.0.43.dist-info}/RECORD +118 -118
- {sqlalchemy-2.0.41.dist-info → sqlalchemy-2.0.43.dist-info}/WHEEL +1 -1
- {sqlalchemy-2.0.41.dist-info → sqlalchemy-2.0.43.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.0.41.dist-info → sqlalchemy-2.0.43.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
sqlalchemy/connectors/aioodbc.py
CHANGED
|
@@ -20,6 +20,7 @@ from .. import util
|
|
|
20
20
|
from ..util.concurrency import await_fallback
|
|
21
21
|
from ..util.concurrency import await_only
|
|
22
22
|
|
|
23
|
+
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
25
|
from ..engine.interfaces import ConnectArgsType
|
|
25
26
|
from ..engine.url import URL
|
|
@@ -58,6 +59,15 @@ class AsyncAdapt_aioodbc_connection(AsyncAdapt_dbapi_connection):
|
|
|
58
59
|
|
|
59
60
|
self._connection._conn.autocommit = value
|
|
60
61
|
|
|
62
|
+
def ping(self, reconnect):
|
|
63
|
+
return self.await_(self._connection.ping(reconnect))
|
|
64
|
+
|
|
65
|
+
def add_output_converter(self, *arg, **kw):
|
|
66
|
+
self._connection.add_output_converter(*arg, **kw)
|
|
67
|
+
|
|
68
|
+
def character_set_name(self):
|
|
69
|
+
return self._connection.character_set_name()
|
|
70
|
+
|
|
61
71
|
def cursor(self, server_side=False):
|
|
62
72
|
# aioodbc sets connection=None when closed and just fails with
|
|
63
73
|
# AttributeError here. Here we use the same ProgrammingError +
|
sqlalchemy/connectors/asyncio.py
CHANGED
|
@@ -4,18 +4,121 @@
|
|
|
4
4
|
#
|
|
5
5
|
# This module is part of SQLAlchemy and is released under
|
|
6
6
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
-
# mypy: ignore-errors
|
|
8
7
|
|
|
9
8
|
"""generic asyncio-adapted versions of DBAPI connection and cursor"""
|
|
10
9
|
|
|
11
10
|
from __future__ import annotations
|
|
12
11
|
|
|
12
|
+
import asyncio
|
|
13
13
|
import collections
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Any
|
|
16
|
+
from typing import AsyncIterator
|
|
17
|
+
from typing import Deque
|
|
18
|
+
from typing import Iterator
|
|
19
|
+
from typing import NoReturn
|
|
20
|
+
from typing import Optional
|
|
21
|
+
from typing import Sequence
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
14
23
|
|
|
15
24
|
from ..engine import AdaptedConnection
|
|
16
|
-
from ..util.concurrency import asyncio
|
|
17
25
|
from ..util.concurrency import await_fallback
|
|
18
26
|
from ..util.concurrency import await_only
|
|
27
|
+
from ..util.typing import Protocol
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from ..engine.interfaces import _DBAPICursorDescription
|
|
31
|
+
from ..engine.interfaces import _DBAPIMultiExecuteParams
|
|
32
|
+
from ..engine.interfaces import _DBAPISingleExecuteParams
|
|
33
|
+
from ..engine.interfaces import DBAPIModule
|
|
34
|
+
from ..util.typing import Self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AsyncIODBAPIConnection(Protocol):
|
|
38
|
+
"""protocol representing an async adapted version of a
|
|
39
|
+
:pep:`249` database connection.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# note that async DBAPIs dont agree if close() should be awaitable,
|
|
45
|
+
# so it is omitted here and picked up by the __getattr__ hook below
|
|
46
|
+
|
|
47
|
+
async def commit(self) -> None: ...
|
|
48
|
+
|
|
49
|
+
def cursor(self, *args: Any, **kwargs: Any) -> AsyncIODBAPICursor: ...
|
|
50
|
+
|
|
51
|
+
async def rollback(self) -> None: ...
|
|
52
|
+
|
|
53
|
+
def __getattr__(self, key: str) -> Any: ...
|
|
54
|
+
|
|
55
|
+
def __setattr__(self, key: str, value: Any) -> None: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AsyncIODBAPICursor(Protocol):
|
|
59
|
+
"""protocol representing an async adapted version
|
|
60
|
+
of a :pep:`249` database cursor.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __aenter__(self) -> Any: ...
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def description(
|
|
69
|
+
self,
|
|
70
|
+
) -> _DBAPICursorDescription:
|
|
71
|
+
"""The description attribute of the Cursor."""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def rowcount(self) -> int: ...
|
|
76
|
+
|
|
77
|
+
arraysize: int
|
|
78
|
+
|
|
79
|
+
lastrowid: int
|
|
80
|
+
|
|
81
|
+
async def close(self) -> None: ...
|
|
82
|
+
|
|
83
|
+
async def execute(
|
|
84
|
+
self,
|
|
85
|
+
operation: Any,
|
|
86
|
+
parameters: Optional[_DBAPISingleExecuteParams] = None,
|
|
87
|
+
) -> Any: ...
|
|
88
|
+
|
|
89
|
+
async def executemany(
|
|
90
|
+
self,
|
|
91
|
+
operation: Any,
|
|
92
|
+
parameters: _DBAPIMultiExecuteParams,
|
|
93
|
+
) -> Any: ...
|
|
94
|
+
|
|
95
|
+
async def fetchone(self) -> Optional[Any]: ...
|
|
96
|
+
|
|
97
|
+
async def fetchmany(self, size: Optional[int] = ...) -> Sequence[Any]: ...
|
|
98
|
+
|
|
99
|
+
async def fetchall(self) -> Sequence[Any]: ...
|
|
100
|
+
|
|
101
|
+
async def setinputsizes(self, sizes: Sequence[Any]) -> None: ...
|
|
102
|
+
|
|
103
|
+
def setoutputsize(self, size: Any, column: Any) -> None: ...
|
|
104
|
+
|
|
105
|
+
async def callproc(
|
|
106
|
+
self, procname: str, parameters: Sequence[Any] = ...
|
|
107
|
+
) -> Any: ...
|
|
108
|
+
|
|
109
|
+
async def nextset(self) -> Optional[bool]: ...
|
|
110
|
+
|
|
111
|
+
def __aiter__(self) -> AsyncIterator[Any]: ...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AsyncAdapt_dbapi_module:
|
|
115
|
+
if TYPE_CHECKING:
|
|
116
|
+
Error = DBAPIModule.Error
|
|
117
|
+
OperationalError = DBAPIModule.OperationalError
|
|
118
|
+
InterfaceError = DBAPIModule.InterfaceError
|
|
119
|
+
IntegrityError = DBAPIModule.IntegrityError
|
|
120
|
+
|
|
121
|
+
def __getattr__(self, key: str) -> Any: ...
|
|
19
122
|
|
|
20
123
|
|
|
21
124
|
class AsyncAdapt_dbapi_cursor:
|
|
@@ -28,96 +131,136 @@ class AsyncAdapt_dbapi_cursor:
|
|
|
28
131
|
"_rows",
|
|
29
132
|
)
|
|
30
133
|
|
|
31
|
-
|
|
134
|
+
_cursor: AsyncIODBAPICursor
|
|
135
|
+
_adapt_connection: AsyncAdapt_dbapi_connection
|
|
136
|
+
_connection: AsyncIODBAPIConnection
|
|
137
|
+
_rows: Deque[Any]
|
|
138
|
+
|
|
139
|
+
def __init__(self, adapt_connection: AsyncAdapt_dbapi_connection):
|
|
32
140
|
self._adapt_connection = adapt_connection
|
|
33
141
|
self._connection = adapt_connection._connection
|
|
142
|
+
|
|
34
143
|
self.await_ = adapt_connection.await_
|
|
35
144
|
|
|
36
|
-
cursor = self._connection
|
|
145
|
+
cursor = self._make_new_cursor(self._connection)
|
|
37
146
|
self._cursor = self._aenter_cursor(cursor)
|
|
38
147
|
|
|
39
148
|
if not self.server_side:
|
|
40
149
|
self._rows = collections.deque()
|
|
41
150
|
|
|
42
|
-
def _aenter_cursor(self, cursor):
|
|
43
|
-
return self.await_(cursor.__aenter__())
|
|
151
|
+
def _aenter_cursor(self, cursor: AsyncIODBAPICursor) -> AsyncIODBAPICursor:
|
|
152
|
+
return self.await_(cursor.__aenter__()) # type: ignore[no-any-return]
|
|
153
|
+
|
|
154
|
+
def _make_new_cursor(
|
|
155
|
+
self, connection: AsyncIODBAPIConnection
|
|
156
|
+
) -> AsyncIODBAPICursor:
|
|
157
|
+
return connection.cursor()
|
|
44
158
|
|
|
45
159
|
@property
|
|
46
|
-
def description(self):
|
|
160
|
+
def description(self) -> Optional[_DBAPICursorDescription]:
|
|
47
161
|
return self._cursor.description
|
|
48
162
|
|
|
49
163
|
@property
|
|
50
|
-
def rowcount(self):
|
|
164
|
+
def rowcount(self) -> int:
|
|
51
165
|
return self._cursor.rowcount
|
|
52
166
|
|
|
53
167
|
@property
|
|
54
|
-
def arraysize(self):
|
|
168
|
+
def arraysize(self) -> int:
|
|
55
169
|
return self._cursor.arraysize
|
|
56
170
|
|
|
57
171
|
@arraysize.setter
|
|
58
|
-
def arraysize(self, value):
|
|
172
|
+
def arraysize(self, value: int) -> None:
|
|
59
173
|
self._cursor.arraysize = value
|
|
60
174
|
|
|
61
175
|
@property
|
|
62
|
-
def lastrowid(self):
|
|
176
|
+
def lastrowid(self) -> int:
|
|
63
177
|
return self._cursor.lastrowid
|
|
64
178
|
|
|
65
|
-
def close(self):
|
|
179
|
+
def close(self) -> None:
|
|
66
180
|
# note we aren't actually closing the cursor here,
|
|
67
181
|
# we are just letting GC do it. see notes in aiomysql dialect
|
|
68
182
|
self._rows.clear()
|
|
69
183
|
|
|
70
|
-
def execute(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
184
|
+
def execute(
|
|
185
|
+
self,
|
|
186
|
+
operation: Any,
|
|
187
|
+
parameters: Optional[_DBAPISingleExecuteParams] = None,
|
|
188
|
+
) -> Any:
|
|
189
|
+
try:
|
|
190
|
+
return self.await_(self._execute_async(operation, parameters))
|
|
191
|
+
except Exception as error:
|
|
192
|
+
self._adapt_connection._handle_exception(error)
|
|
193
|
+
|
|
194
|
+
def executemany(
|
|
195
|
+
self,
|
|
196
|
+
operation: Any,
|
|
197
|
+
seq_of_parameters: _DBAPIMultiExecuteParams,
|
|
198
|
+
) -> Any:
|
|
199
|
+
try:
|
|
200
|
+
return self.await_(
|
|
201
|
+
self._executemany_async(operation, seq_of_parameters)
|
|
202
|
+
)
|
|
203
|
+
except Exception as error:
|
|
204
|
+
self._adapt_connection._handle_exception(error)
|
|
77
205
|
|
|
78
|
-
async def _execute_async(
|
|
206
|
+
async def _execute_async(
|
|
207
|
+
self, operation: Any, parameters: Optional[_DBAPISingleExecuteParams]
|
|
208
|
+
) -> Any:
|
|
79
209
|
async with self._adapt_connection._execute_mutex:
|
|
80
|
-
|
|
210
|
+
if parameters is None:
|
|
211
|
+
result = await self._cursor.execute(operation)
|
|
212
|
+
else:
|
|
213
|
+
result = await self._cursor.execute(operation, parameters)
|
|
81
214
|
|
|
82
215
|
if self._cursor.description and not self.server_side:
|
|
83
216
|
self._rows = collections.deque(await self._cursor.fetchall())
|
|
84
217
|
return result
|
|
85
218
|
|
|
86
|
-
async def _executemany_async(
|
|
219
|
+
async def _executemany_async(
|
|
220
|
+
self,
|
|
221
|
+
operation: Any,
|
|
222
|
+
seq_of_parameters: _DBAPIMultiExecuteParams,
|
|
223
|
+
) -> Any:
|
|
87
224
|
async with self._adapt_connection._execute_mutex:
|
|
88
225
|
return await self._cursor.executemany(operation, seq_of_parameters)
|
|
89
226
|
|
|
90
|
-
def nextset(self):
|
|
227
|
+
def nextset(self) -> None:
|
|
91
228
|
self.await_(self._cursor.nextset())
|
|
92
229
|
if self._cursor.description and not self.server_side:
|
|
93
230
|
self._rows = collections.deque(
|
|
94
231
|
self.await_(self._cursor.fetchall())
|
|
95
232
|
)
|
|
96
233
|
|
|
97
|
-
def setinputsizes(self, *inputsizes):
|
|
234
|
+
def setinputsizes(self, *inputsizes: Any) -> None:
|
|
98
235
|
# NOTE: this is overrridden in aioodbc due to
|
|
99
236
|
# see https://github.com/aio-libs/aioodbc/issues/451
|
|
100
237
|
# right now
|
|
101
238
|
|
|
102
239
|
return self.await_(self._cursor.setinputsizes(*inputsizes))
|
|
103
240
|
|
|
104
|
-
def
|
|
241
|
+
def __enter__(self) -> Self:
|
|
242
|
+
return self
|
|
243
|
+
|
|
244
|
+
def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
|
|
245
|
+
self.close()
|
|
246
|
+
|
|
247
|
+
def __iter__(self) -> Iterator[Any]:
|
|
105
248
|
while self._rows:
|
|
106
249
|
yield self._rows.popleft()
|
|
107
250
|
|
|
108
|
-
def fetchone(self):
|
|
251
|
+
def fetchone(self) -> Optional[Any]:
|
|
109
252
|
if self._rows:
|
|
110
253
|
return self._rows.popleft()
|
|
111
254
|
else:
|
|
112
255
|
return None
|
|
113
256
|
|
|
114
|
-
def fetchmany(self, size=None):
|
|
257
|
+
def fetchmany(self, size: Optional[int] = None) -> Sequence[Any]:
|
|
115
258
|
if size is None:
|
|
116
259
|
size = self.arraysize
|
|
117
260
|
rr = self._rows
|
|
118
261
|
return [rr.popleft() for _ in range(min(size, len(rr)))]
|
|
119
262
|
|
|
120
|
-
def fetchall(self):
|
|
263
|
+
def fetchall(self) -> Sequence[Any]:
|
|
121
264
|
retval = list(self._rows)
|
|
122
265
|
self._rows.clear()
|
|
123
266
|
return retval
|
|
@@ -127,30 +270,21 @@ class AsyncAdapt_dbapi_ss_cursor(AsyncAdapt_dbapi_cursor):
|
|
|
127
270
|
__slots__ = ()
|
|
128
271
|
server_side = True
|
|
129
272
|
|
|
130
|
-
def
|
|
131
|
-
self._adapt_connection = adapt_connection
|
|
132
|
-
self._connection = adapt_connection._connection
|
|
133
|
-
self.await_ = adapt_connection.await_
|
|
134
|
-
|
|
135
|
-
cursor = self._connection.cursor()
|
|
136
|
-
|
|
137
|
-
self._cursor = self.await_(cursor.__aenter__())
|
|
138
|
-
|
|
139
|
-
def close(self):
|
|
273
|
+
def close(self) -> None:
|
|
140
274
|
if self._cursor is not None:
|
|
141
275
|
self.await_(self._cursor.close())
|
|
142
|
-
self._cursor = None
|
|
276
|
+
self._cursor = None # type: ignore
|
|
143
277
|
|
|
144
|
-
def fetchone(self):
|
|
278
|
+
def fetchone(self) -> Optional[Any]:
|
|
145
279
|
return self.await_(self._cursor.fetchone())
|
|
146
280
|
|
|
147
|
-
def fetchmany(self, size=None):
|
|
281
|
+
def fetchmany(self, size: Optional[int] = None) -> Any:
|
|
148
282
|
return self.await_(self._cursor.fetchmany(size=size))
|
|
149
283
|
|
|
150
|
-
def fetchall(self):
|
|
284
|
+
def fetchall(self) -> Sequence[Any]:
|
|
151
285
|
return self.await_(self._cursor.fetchall())
|
|
152
286
|
|
|
153
|
-
def __iter__(self):
|
|
287
|
+
def __iter__(self) -> Iterator[Any]:
|
|
154
288
|
iterator = self._cursor.__aiter__()
|
|
155
289
|
while True:
|
|
156
290
|
try:
|
|
@@ -164,46 +298,50 @@ class AsyncAdapt_dbapi_connection(AdaptedConnection):
|
|
|
164
298
|
_ss_cursor_cls = AsyncAdapt_dbapi_ss_cursor
|
|
165
299
|
|
|
166
300
|
await_ = staticmethod(await_only)
|
|
301
|
+
|
|
167
302
|
__slots__ = ("dbapi", "_execute_mutex")
|
|
168
303
|
|
|
169
|
-
|
|
304
|
+
_connection: AsyncIODBAPIConnection
|
|
305
|
+
|
|
306
|
+
def __init__(self, dbapi: Any, connection: AsyncIODBAPIConnection):
|
|
170
307
|
self.dbapi = dbapi
|
|
171
308
|
self._connection = connection
|
|
172
309
|
self._execute_mutex = asyncio.Lock()
|
|
173
310
|
|
|
174
|
-
def
|
|
175
|
-
return self.await_(self._connection.ping(reconnect))
|
|
176
|
-
|
|
177
|
-
def add_output_converter(self, *arg, **kw):
|
|
178
|
-
self._connection.add_output_converter(*arg, **kw)
|
|
179
|
-
|
|
180
|
-
def character_set_name(self):
|
|
181
|
-
return self._connection.character_set_name()
|
|
182
|
-
|
|
183
|
-
@property
|
|
184
|
-
def autocommit(self):
|
|
185
|
-
return self._connection.autocommit
|
|
186
|
-
|
|
187
|
-
@autocommit.setter
|
|
188
|
-
def autocommit(self, value):
|
|
189
|
-
# https://github.com/aio-libs/aioodbc/issues/448
|
|
190
|
-
# self._connection.autocommit = value
|
|
191
|
-
|
|
192
|
-
self._connection._conn.autocommit = value
|
|
193
|
-
|
|
194
|
-
def cursor(self, server_side=False):
|
|
311
|
+
def cursor(self, server_side: bool = False) -> AsyncAdapt_dbapi_cursor:
|
|
195
312
|
if server_side:
|
|
196
313
|
return self._ss_cursor_cls(self)
|
|
197
314
|
else:
|
|
198
315
|
return self._cursor_cls(self)
|
|
199
316
|
|
|
200
|
-
def
|
|
201
|
-
self
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
317
|
+
def execute(
|
|
318
|
+
self,
|
|
319
|
+
operation: Any,
|
|
320
|
+
parameters: Optional[_DBAPISingleExecuteParams] = None,
|
|
321
|
+
) -> Any:
|
|
322
|
+
"""lots of DBAPIs seem to provide this, so include it"""
|
|
323
|
+
cursor = self.cursor()
|
|
324
|
+
cursor.execute(operation, parameters)
|
|
325
|
+
return cursor
|
|
326
|
+
|
|
327
|
+
def _handle_exception(self, error: Exception) -> NoReturn:
|
|
328
|
+
exc_info = sys.exc_info()
|
|
329
|
+
|
|
330
|
+
raise error.with_traceback(exc_info[2])
|
|
331
|
+
|
|
332
|
+
def rollback(self) -> None:
|
|
333
|
+
try:
|
|
334
|
+
self.await_(self._connection.rollback())
|
|
335
|
+
except Exception as error:
|
|
336
|
+
self._handle_exception(error)
|
|
337
|
+
|
|
338
|
+
def commit(self) -> None:
|
|
339
|
+
try:
|
|
340
|
+
self.await_(self._connection.commit())
|
|
341
|
+
except Exception as error:
|
|
342
|
+
self._handle_exception(error)
|
|
343
|
+
|
|
344
|
+
def close(self) -> None:
|
|
207
345
|
self.await_(self._connection.close())
|
|
208
346
|
|
|
209
347
|
|
sqlalchemy/connectors/pyodbc.py
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import re
|
|
11
|
-
from types import ModuleType
|
|
12
11
|
import typing
|
|
13
12
|
from typing import Any
|
|
14
13
|
from typing import Dict
|
|
@@ -29,6 +28,7 @@ from ..engine import URL
|
|
|
29
28
|
from ..sql.type_api import TypeEngine
|
|
30
29
|
|
|
31
30
|
if typing.TYPE_CHECKING:
|
|
31
|
+
from ..engine.interfaces import DBAPIModule
|
|
32
32
|
from ..engine.interfaces import IsolationLevel
|
|
33
33
|
|
|
34
34
|
|
|
@@ -48,15 +48,13 @@ class PyODBCConnector(Connector):
|
|
|
48
48
|
# hold the desired driver name
|
|
49
49
|
pyodbc_driver_name: Optional[str] = None
|
|
50
50
|
|
|
51
|
-
dbapi: ModuleType
|
|
52
|
-
|
|
53
51
|
def __init__(self, use_setinputsizes: bool = False, **kw: Any):
|
|
54
52
|
super().__init__(**kw)
|
|
55
53
|
if use_setinputsizes:
|
|
56
54
|
self.bind_typing = interfaces.BindTyping.SETINPUTSIZES
|
|
57
55
|
|
|
58
56
|
@classmethod
|
|
59
|
-
def import_dbapi(cls) ->
|
|
57
|
+
def import_dbapi(cls) -> DBAPIModule:
|
|
60
58
|
return __import__("pyodbc")
|
|
61
59
|
|
|
62
60
|
def create_connect_args(self, url: URL) -> ConnectArgsType:
|
|
@@ -150,7 +148,7 @@ class PyODBCConnector(Connector):
|
|
|
150
148
|
],
|
|
151
149
|
cursor: Optional[interfaces.DBAPICursor],
|
|
152
150
|
) -> bool:
|
|
153
|
-
if isinstance(e, self.
|
|
151
|
+
if isinstance(e, self.loaded_dbapi.ProgrammingError):
|
|
154
152
|
return "The cursor's connection has been closed." in str(
|
|
155
153
|
e
|
|
156
154
|
) or "Attempt to use a closed connection." in str(e)
|
|
@@ -245,3 +243,8 @@ class PyODBCConnector(Connector):
|
|
|
245
243
|
else:
|
|
246
244
|
dbapi_connection.autocommit = False
|
|
247
245
|
super().set_isolation_level(dbapi_connection, level)
|
|
246
|
+
|
|
247
|
+
def detect_autocommit_setting(
|
|
248
|
+
self, dbapi_conn: interfaces.DBAPIConnection
|
|
249
|
+
) -> bool:
|
|
250
|
+
return bool(dbapi_conn.autocommit)
|
sqlalchemy/cyextension/util.pyx
CHANGED
|
@@ -10,28 +10,24 @@ from sqlalchemy import exc
|
|
|
10
10
|
|
|
11
11
|
cdef tuple _Empty_Tuple = ()
|
|
12
12
|
|
|
13
|
-
cdef inline bint
|
|
13
|
+
cdef inline bint _is_mapping_or_tuple(object value):
|
|
14
14
|
return isinstance(value, dict) or isinstance(value, tuple) or isinstance(value, Mapping)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
item = params[0]
|
|
21
|
-
if not _mapping_or_tuple(item):
|
|
22
|
-
ret = 0
|
|
23
|
-
raise exc.ArgumentError(
|
|
24
|
-
"List argument must consist only of tuples or dictionaries"
|
|
25
|
-
)
|
|
26
|
-
return ret
|
|
16
|
+
|
|
17
|
+
cdef inline bint _is_mapping(object value):
|
|
18
|
+
return isinstance(value, dict) or isinstance(value, Mapping)
|
|
19
|
+
|
|
27
20
|
|
|
28
21
|
def _distill_params_20(object params):
|
|
29
22
|
if params is None:
|
|
30
23
|
return _Empty_Tuple
|
|
31
24
|
elif isinstance(params, list) or isinstance(params, tuple):
|
|
32
|
-
|
|
25
|
+
if params and not _is_mapping(params[0]):
|
|
26
|
+
raise exc.ArgumentError(
|
|
27
|
+
"List argument must consist only of dictionaries"
|
|
28
|
+
)
|
|
33
29
|
return params
|
|
34
|
-
elif
|
|
30
|
+
elif _is_mapping(params):
|
|
35
31
|
return [params]
|
|
36
32
|
else:
|
|
37
33
|
raise exc.ArgumentError("mapping or list expected for parameters")
|
|
@@ -41,9 +37,12 @@ def _distill_raw_params(object params):
|
|
|
41
37
|
if params is None:
|
|
42
38
|
return _Empty_Tuple
|
|
43
39
|
elif isinstance(params, list):
|
|
44
|
-
|
|
40
|
+
if params and not _is_mapping_or_tuple(params[0]):
|
|
41
|
+
raise exc.ArgumentError(
|
|
42
|
+
"List argument must consist only of tuples or dictionaries"
|
|
43
|
+
)
|
|
45
44
|
return params
|
|
46
|
-
elif
|
|
45
|
+
elif _is_mapping_or_tuple(params):
|
|
47
46
|
return [params]
|
|
48
47
|
else:
|
|
49
48
|
raise exc.ArgumentError("mapping or sequence expected for parameters")
|
sqlalchemy/dialects/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from typing import Any
|
|
10
11
|
from typing import Callable
|
|
11
12
|
from typing import Optional
|
|
12
13
|
from typing import Type
|
|
@@ -39,7 +40,7 @@ def _auto_fn(name: str) -> Optional[Callable[[], Type[Dialect]]]:
|
|
|
39
40
|
# hardcoded. if mysql / mariadb etc were third party dialects
|
|
40
41
|
# they would just publish all the entrypoints, which would actually
|
|
41
42
|
# look much nicer.
|
|
42
|
-
module = __import__(
|
|
43
|
+
module: Any = __import__(
|
|
43
44
|
"sqlalchemy.dialects.mysql.mariadb"
|
|
44
45
|
).dialects.mysql.mariadb
|
|
45
46
|
return module.loader(driver) # type: ignore
|