SQLAlchemy 2.0.38__py3-none-any.whl → 2.0.40__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/pyodbc.py +2 -4
- sqlalchemy/dialects/mysql/base.py +40 -8
- sqlalchemy/dialects/mysql/mariadb.py +17 -11
- sqlalchemy/dialects/mysql/mysqlconnector.py +84 -19
- sqlalchemy/dialects/mysql/provision.py +4 -0
- sqlalchemy/dialects/mysql/reflection.py +1 -1
- sqlalchemy/dialects/mysql/types.py +4 -5
- sqlalchemy/dialects/postgresql/array.py +132 -58
- sqlalchemy/dialects/postgresql/base.py +53 -9
- sqlalchemy/dialects/postgresql/json.py +51 -22
- sqlalchemy/dialects/postgresql/types.py +14 -4
- sqlalchemy/dialects/sqlite/base.py +27 -10
- sqlalchemy/engine/cursor.py +6 -1
- sqlalchemy/engine/default.py +27 -12
- sqlalchemy/engine/interfaces.py +11 -4
- sqlalchemy/engine/result.py +3 -3
- sqlalchemy/ext/asyncio/base.py +7 -7
- sqlalchemy/ext/asyncio/engine.py +5 -3
- sqlalchemy/ext/asyncio/result.py +1 -0
- sqlalchemy/ext/asyncio/session.py +21 -2
- sqlalchemy/orm/context.py +102 -34
- sqlalchemy/orm/decl_api.py +1 -1
- sqlalchemy/orm/decl_base.py +1 -1
- sqlalchemy/orm/exc.py +9 -0
- sqlalchemy/orm/mapper.py +5 -4
- sqlalchemy/orm/properties.py +12 -3
- sqlalchemy/orm/query.py +11 -4
- sqlalchemy/orm/util.py +6 -5
- sqlalchemy/pool/base.py +2 -1
- sqlalchemy/sql/_elements_constructors.py +14 -4
- sqlalchemy/sql/_selectable_constructors.py +94 -16
- sqlalchemy/sql/_typing.py +4 -1
- sqlalchemy/sql/base.py +4 -1
- sqlalchemy/sql/coercions.py +1 -1
- sqlalchemy/sql/compiler.py +256 -137
- sqlalchemy/sql/crud.py +12 -3
- sqlalchemy/sql/ddl.py +105 -52
- sqlalchemy/sql/dml.py +10 -0
- sqlalchemy/sql/elements.py +37 -19
- sqlalchemy/sql/functions.py +42 -7
- sqlalchemy/sql/schema.py +23 -5
- sqlalchemy/sql/selectable.py +38 -34
- sqlalchemy/sql/sqltypes.py +113 -40
- sqlalchemy/sql/type_api.py +20 -1
- sqlalchemy/sql/util.py +1 -1
- sqlalchemy/testing/assertions.py +2 -2
- sqlalchemy/testing/requirements.py +19 -0
- sqlalchemy/testing/suite/test_reflection.py +48 -0
- sqlalchemy/testing/suite/test_results.py +2 -0
- sqlalchemy/testing/suite/test_select.py +9 -0
- sqlalchemy/testing/suite/test_types.py +4 -0
- sqlalchemy/util/_collections.py +3 -1
- sqlalchemy/util/typing.py +64 -21
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.40.dist-info}/METADATA +10 -9
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.40.dist-info}/RECORD +59 -59
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.40.dist-info}/WHEEL +1 -1
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.40.dist-info/licenses}/LICENSE +0 -0
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.40.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
sqlalchemy/connectors/pyodbc.py
CHANGED
|
@@ -227,11 +227,9 @@ class PyODBCConnector(Connector):
|
|
|
227
227
|
)
|
|
228
228
|
|
|
229
229
|
def get_isolation_level_values(
|
|
230
|
-
self,
|
|
230
|
+
self, dbapi_conn: interfaces.DBAPIConnection
|
|
231
231
|
) -> List[IsolationLevel]:
|
|
232
|
-
return super().get_isolation_level_values(
|
|
233
|
-
"AUTOCOMMIT"
|
|
234
|
-
]
|
|
232
|
+
return [*super().get_isolation_level_values(dbapi_conn), "AUTOCOMMIT"]
|
|
235
233
|
|
|
236
234
|
def set_isolation_level(
|
|
237
235
|
self,
|
|
@@ -182,6 +182,31 @@ For fully atomic transactions as well as support for foreign key
|
|
|
182
182
|
constraints, all participating ``CREATE TABLE`` statements must specify a
|
|
183
183
|
transactional engine, which in the vast majority of cases is ``InnoDB``.
|
|
184
184
|
|
|
185
|
+
Partitioning can similarly be specified using similar options.
|
|
186
|
+
In the example below the create table will specify ``PARTITION_BY``,
|
|
187
|
+
``PARTITIONS``, ``SUBPARTITIONS`` and ``SUBPARTITION_BY``::
|
|
188
|
+
|
|
189
|
+
# can also use mariadb_* prefix
|
|
190
|
+
Table(
|
|
191
|
+
"testtable",
|
|
192
|
+
MetaData(),
|
|
193
|
+
Column("id", Integer(), primary_key=True, autoincrement=True),
|
|
194
|
+
Column("other_id", Integer(), primary_key=True, autoincrement=False),
|
|
195
|
+
mysql_partitions="2",
|
|
196
|
+
mysql_partition_by="KEY(other_id)",
|
|
197
|
+
mysql_subpartition_by="HASH(some_expr)",
|
|
198
|
+
mysql_subpartitions="2",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
This will render:
|
|
202
|
+
|
|
203
|
+
.. sourcecode:: sql
|
|
204
|
+
|
|
205
|
+
CREATE TABLE testtable (
|
|
206
|
+
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
207
|
+
other_id INTEGER NOT NULL,
|
|
208
|
+
PRIMARY KEY (id, other_id)
|
|
209
|
+
)PARTITION BY KEY(other_id) PARTITIONS 2 SUBPARTITION BY HASH(some_expr) SUBPARTITIONS 2
|
|
185
210
|
|
|
186
211
|
Case Sensitivity and Table Reflection
|
|
187
212
|
-------------------------------------
|
|
@@ -1903,12 +1928,13 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
|
|
1903
1928
|
colspec.append("AUTO_INCREMENT")
|
|
1904
1929
|
else:
|
|
1905
1930
|
default = self.get_column_default_string(column)
|
|
1931
|
+
|
|
1906
1932
|
if default is not None:
|
|
1907
1933
|
if (
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
and
|
|
1934
|
+
self.dialect._support_default_function
|
|
1935
|
+
and not re.match(r"^\s*[\'\"\(]", default)
|
|
1936
|
+
and "ON UPDATE" not in default
|
|
1937
|
+
and re.match(r".*\W.*", default)
|
|
1912
1938
|
):
|
|
1913
1939
|
colspec.append(f"DEFAULT ({default})")
|
|
1914
1940
|
else:
|
|
@@ -2495,6 +2521,10 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
2495
2521
|
# allow for the "true" and "false" keywords, however
|
|
2496
2522
|
supports_native_boolean = False
|
|
2497
2523
|
|
|
2524
|
+
# support for BIT type; mysqlconnector coerces result values automatically,
|
|
2525
|
+
# all other MySQL DBAPIs require a conversion routine
|
|
2526
|
+
supports_native_bit = False
|
|
2527
|
+
|
|
2498
2528
|
# identifiers are 64, however aliases can be 255...
|
|
2499
2529
|
max_identifier_length = 255
|
|
2500
2530
|
max_index_name_length = 64
|
|
@@ -2696,10 +2726,12 @@ class MySQLDialect(default.DefaultDialect):
|
|
|
2696
2726
|
% (".".join(map(str, server_version_info)),)
|
|
2697
2727
|
)
|
|
2698
2728
|
if is_mariadb:
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2729
|
+
|
|
2730
|
+
if not issubclass(self.preparer, MariaDBIdentifierPreparer):
|
|
2731
|
+
self.preparer = MariaDBIdentifierPreparer
|
|
2732
|
+
# this would have been set by the default dialect already,
|
|
2733
|
+
# so set it again
|
|
2734
|
+
self.identifier_preparer = self.preparer(self)
|
|
2703
2735
|
|
|
2704
2736
|
# this will be updated on first connect in initialize()
|
|
2705
2737
|
# if using older mariadb version
|
|
@@ -46,16 +46,22 @@ class MariaDBDialect(MySQLDialect):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def loader(driver):
|
|
49
|
-
|
|
49
|
+
dialect_mod = __import__(
|
|
50
50
|
"sqlalchemy.dialects.mysql.%s" % driver
|
|
51
51
|
).dialects.mysql
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
|
|
53
|
+
driver_mod = getattr(dialect_mod, driver)
|
|
54
|
+
if hasattr(driver_mod, "mariadb_dialect"):
|
|
55
|
+
driver_cls = driver_mod.mariadb_dialect
|
|
56
|
+
return driver_cls
|
|
57
|
+
else:
|
|
58
|
+
driver_cls = driver_mod.dialect
|
|
59
|
+
|
|
60
|
+
return type(
|
|
61
|
+
"MariaDBDialect_%s" % driver,
|
|
62
|
+
(
|
|
63
|
+
MariaDBDialect,
|
|
64
|
+
driver_cls,
|
|
65
|
+
),
|
|
66
|
+
{"supports_statement_cache": True},
|
|
67
|
+
)
|
|
@@ -14,24 +14,51 @@ r"""
|
|
|
14
14
|
:connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
|
15
15
|
:url: https://pypi.org/project/mysql-connector-python/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Driver Status
|
|
18
|
+
-------------
|
|
19
|
+
|
|
20
|
+
MySQL Connector/Python is supported as of SQLAlchemy 2.0.39 to the
|
|
21
|
+
degree which the driver is functional. There are still ongoing issues
|
|
22
|
+
with features such as server side cursors which remain disabled until
|
|
23
|
+
upstream issues are repaired.
|
|
24
|
+
|
|
25
|
+
.. versionchanged:: 2.0.39
|
|
26
|
+
|
|
27
|
+
The MySQL Connector/Python dialect has been updated to support the
|
|
28
|
+
latest version of this DBAPI. Previously, MySQL Connector/Python
|
|
29
|
+
was not fully supported.
|
|
30
|
+
|
|
31
|
+
Connecting to MariaDB with MySQL Connector/Python
|
|
32
|
+
--------------------------------------------------
|
|
33
|
+
|
|
34
|
+
MySQL Connector/Python may attempt to pass an incompatible collation to the
|
|
35
|
+
database when connecting to MariaDB. Experimentation has shown that using
|
|
36
|
+
``?charset=utf8mb4&collation=utfmb4_general_ci`` or similar MariaDB-compatible
|
|
37
|
+
charset/collation will allow connectivity.
|
|
18
38
|
|
|
19
|
-
The MySQL Connector/Python DBAPI has had many issues since its release,
|
|
20
|
-
some of which may remain unresolved, and the mysqlconnector dialect is
|
|
21
|
-
**not tested as part of SQLAlchemy's continuous integration**.
|
|
22
|
-
The recommended MySQL dialects are mysqlclient and PyMySQL.
|
|
23
39
|
|
|
24
40
|
""" # noqa
|
|
25
41
|
|
|
26
42
|
import re
|
|
27
43
|
|
|
28
44
|
from .base import BIT
|
|
45
|
+
from .base import MariaDBIdentifierPreparer
|
|
29
46
|
from .base import MySQLCompiler
|
|
30
47
|
from .base import MySQLDialect
|
|
48
|
+
from .base import MySQLExecutionContext
|
|
31
49
|
from .base import MySQLIdentifierPreparer
|
|
50
|
+
from .mariadb import MariaDBDialect
|
|
32
51
|
from ... import util
|
|
33
52
|
|
|
34
53
|
|
|
54
|
+
class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
|
|
55
|
+
def create_server_side_cursor(self):
|
|
56
|
+
return self._dbapi_connection.cursor(buffered=False)
|
|
57
|
+
|
|
58
|
+
def create_default_cursor(self):
|
|
59
|
+
return self._dbapi_connection.cursor(buffered=True)
|
|
60
|
+
|
|
61
|
+
|
|
35
62
|
class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
|
36
63
|
def visit_mod_binary(self, binary, operator, **kw):
|
|
37
64
|
return (
|
|
@@ -41,7 +68,7 @@ class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
|
|
41
68
|
)
|
|
42
69
|
|
|
43
70
|
|
|
44
|
-
class
|
|
71
|
+
class IdentifierPreparerCommon_mysqlconnector:
|
|
45
72
|
@property
|
|
46
73
|
def _double_percents(self):
|
|
47
74
|
return False
|
|
@@ -55,6 +82,18 @@ class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
|
|
|
55
82
|
return value
|
|
56
83
|
|
|
57
84
|
|
|
85
|
+
class MySQLIdentifierPreparer_mysqlconnector(
|
|
86
|
+
IdentifierPreparerCommon_mysqlconnector, MySQLIdentifierPreparer
|
|
87
|
+
):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MariaDBIdentifierPreparer_mysqlconnector(
|
|
92
|
+
IdentifierPreparerCommon_mysqlconnector, MariaDBIdentifierPreparer
|
|
93
|
+
):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
58
97
|
class _myconnpyBIT(BIT):
|
|
59
98
|
def result_processor(self, dialect, coltype):
|
|
60
99
|
"""MySQL-connector already converts mysql bits, so."""
|
|
@@ -71,9 +110,16 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
71
110
|
|
|
72
111
|
supports_native_decimal = True
|
|
73
112
|
|
|
113
|
+
supports_native_bit = True
|
|
114
|
+
|
|
115
|
+
# not until https://bugs.mysql.com/bug.php?id=117548
|
|
116
|
+
supports_server_side_cursors = False
|
|
117
|
+
|
|
74
118
|
default_paramstyle = "format"
|
|
75
119
|
statement_compiler = MySQLCompiler_mysqlconnector
|
|
76
120
|
|
|
121
|
+
execution_ctx_cls = MySQLExecutionContext_mysqlconnector
|
|
122
|
+
|
|
77
123
|
preparer = MySQLIdentifierPreparer_mysqlconnector
|
|
78
124
|
|
|
79
125
|
colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
|
|
@@ -111,9 +157,13 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
111
157
|
util.coerce_kw_type(opts, "use_pure", bool)
|
|
112
158
|
util.coerce_kw_type(opts, "use_unicode", bool)
|
|
113
159
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
|
|
160
|
+
# note that "buffered" is set to False by default in MySQL/connector
|
|
161
|
+
# python. If you set it to True, then there is no way to get a server
|
|
162
|
+
# side cursor because the logic is written to disallow that.
|
|
163
|
+
|
|
164
|
+
# leaving this at True until
|
|
165
|
+
# https://bugs.mysql.com/bug.php?id=117548 can be fixed
|
|
166
|
+
opts["buffered"] = True
|
|
117
167
|
|
|
118
168
|
# FOUND_ROWS must be set in ClientFlag to enable
|
|
119
169
|
# supports_sane_rowcount.
|
|
@@ -128,6 +178,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
128
178
|
opts["client_flags"] = client_flags
|
|
129
179
|
except Exception:
|
|
130
180
|
pass
|
|
181
|
+
|
|
131
182
|
return [[], opts]
|
|
132
183
|
|
|
133
184
|
@util.memoized_property
|
|
@@ -145,7 +196,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
145
196
|
|
|
146
197
|
def is_disconnect(self, e, connection, cursor):
|
|
147
198
|
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
|
|
148
|
-
exceptions = (
|
|
199
|
+
exceptions = (
|
|
200
|
+
self.dbapi.OperationalError,
|
|
201
|
+
self.dbapi.InterfaceError,
|
|
202
|
+
self.dbapi.ProgrammingError,
|
|
203
|
+
)
|
|
149
204
|
if isinstance(e, exceptions):
|
|
150
205
|
return (
|
|
151
206
|
e.errno in errnos
|
|
@@ -161,20 +216,30 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
161
216
|
def _compat_fetchone(self, rp, charset=None):
|
|
162
217
|
return rp.fetchone()
|
|
163
218
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
219
|
+
def get_isolation_level_values(self, dbapi_connection):
|
|
220
|
+
return (
|
|
221
|
+
"SERIALIZABLE",
|
|
222
|
+
"READ UNCOMMITTED",
|
|
223
|
+
"READ COMMITTED",
|
|
224
|
+
"REPEATABLE READ",
|
|
225
|
+
"AUTOCOMMIT",
|
|
226
|
+
)
|
|
171
227
|
|
|
172
|
-
def
|
|
228
|
+
def set_isolation_level(self, connection, level):
|
|
173
229
|
if level == "AUTOCOMMIT":
|
|
174
230
|
connection.autocommit = True
|
|
175
231
|
else:
|
|
176
232
|
connection.autocommit = False
|
|
177
|
-
super().
|
|
233
|
+
super().set_isolation_level(connection, level)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class MariaDBDialect_mysqlconnector(
|
|
237
|
+
MariaDBDialect, MySQLDialect_mysqlconnector
|
|
238
|
+
):
|
|
239
|
+
supports_statement_cache = True
|
|
240
|
+
_allows_uuid_binds = False
|
|
241
|
+
preparer = MariaDBIdentifierPreparer_mysqlconnector
|
|
178
242
|
|
|
179
243
|
|
|
180
244
|
dialect = MySQLDialect_mysqlconnector
|
|
245
|
+
mariadb_dialect = MariaDBDialect_mysqlconnector
|
|
@@ -42,6 +42,10 @@ def generate_driver_url(url, driver, query_str):
|
|
|
42
42
|
|
|
43
43
|
if driver == "mariadbconnector":
|
|
44
44
|
new_url = new_url.difference_update_query(["charset"])
|
|
45
|
+
elif driver == "mysqlconnector":
|
|
46
|
+
new_url = new_url.update_query_pairs(
|
|
47
|
+
[("collation", "utf8mb4_general_ci")]
|
|
48
|
+
)
|
|
45
49
|
|
|
46
50
|
try:
|
|
47
51
|
new_url.get_dialect()
|
|
@@ -451,7 +451,7 @@ class MySQLTableDefinitionParser:
|
|
|
451
451
|
r"(?: +COLLATE +(?P<collate>[\w_]+))?"
|
|
452
452
|
r"(?: +(?P<notnull>(?:NOT )?NULL))?"
|
|
453
453
|
r"(?: +DEFAULT +(?P<default>"
|
|
454
|
-
r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+"
|
|
454
|
+
r"(?:NULL|'(?:''|[^'])*'|\(.+?\)|[\-\w\.\(\)]+"
|
|
455
455
|
r"(?: +ON UPDATE [\-\w\.\(\)]+)?)"
|
|
456
456
|
r"))?"
|
|
457
457
|
r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P<generated>\("
|
|
@@ -374,12 +374,11 @@ class BIT(sqltypes.TypeEngine):
|
|
|
374
374
|
self.length = length
|
|
375
375
|
|
|
376
376
|
def result_processor(self, dialect, coltype):
|
|
377
|
-
"""Convert a MySQL's 64 bit, variable length binary string to a
|
|
377
|
+
"""Convert a MySQL's 64 bit, variable length binary string to a
|
|
378
|
+
long."""
|
|
378
379
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
"""
|
|
380
|
+
if dialect.supports_native_bit:
|
|
381
|
+
return None
|
|
383
382
|
|
|
384
383
|
def process(value):
|
|
385
384
|
if value is not None:
|