SQLAlchemy 2.1.0b3__py3-none-any.whl → 2.1.0rc2__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 +2 -1
- sqlalchemy/connectors/asyncio.py +1 -1
- sqlalchemy/connectors/pyodbc.py +5 -16
- sqlalchemy/dialects/__init__.py +1 -1
- sqlalchemy/dialects/mssql/base.py +98 -50
- sqlalchemy/dialects/mssql/mssqlpython.py +35 -19
- sqlalchemy/dialects/mssql/pymssql.py +4 -1
- sqlalchemy/dialects/mssql/pyodbc.py +4 -4
- sqlalchemy/dialects/mysql/aiomysql.py +11 -2
- sqlalchemy/dialects/mysql/asyncmy.py +7 -2
- sqlalchemy/dialects/mysql/base.py +10 -9
- sqlalchemy/dialects/mysql/enumerated.py +1 -1
- sqlalchemy/dialects/mysql/mariadbconnector.py +5 -22
- sqlalchemy/dialects/mysql/mysqlconnector.py +5 -11
- sqlalchemy/dialects/mysql/mysqldb.py +21 -16
- sqlalchemy/dialects/mysql/pymysql.py +10 -0
- sqlalchemy/dialects/oracle/base.py +15 -13
- sqlalchemy/dialects/oracle/cx_oracle.py +171 -37
- sqlalchemy/dialects/oracle/oracledb.py +15 -27
- sqlalchemy/dialects/postgresql/_psycopg_common.py +3 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +5 -13
- sqlalchemy/dialects/postgresql/base.py +261 -36
- sqlalchemy/dialects/postgresql/hstore.py +1 -1
- sqlalchemy/dialects/postgresql/json.py +3 -0
- sqlalchemy/dialects/postgresql/named_types.py +14 -0
- sqlalchemy/dialects/postgresql/pg8000.py +4 -17
- sqlalchemy/dialects/postgresql/pg_catalog.py +1 -0
- sqlalchemy/dialects/postgresql/provision.py +18 -0
- sqlalchemy/dialects/postgresql/psycopg.py +13 -13
- sqlalchemy/dialects/postgresql/psycopg2.py +11 -12
- sqlalchemy/dialects/postgresql/ranges.py +7 -7
- sqlalchemy/dialects/sqlite/aiosqlite.py +53 -0
- sqlalchemy/dialects/sqlite/base.py +131 -41
- sqlalchemy/dialects/sqlite/dml.py +47 -12
- sqlalchemy/dialects/sqlite/provision.py +8 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +6 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +219 -15
- sqlalchemy/engine/_result_cy.py +8 -8
- sqlalchemy/engine/create.py +2 -2
- sqlalchemy/engine/cursor.py +2 -2
- sqlalchemy/engine/default.py +115 -6
- sqlalchemy/engine/events.py +2 -2
- sqlalchemy/engine/interfaces.py +143 -2
- sqlalchemy/engine/reflection.py +44 -4
- sqlalchemy/engine/result.py +4 -4
- sqlalchemy/engine/url.py +1 -1
- sqlalchemy/engine/util.py +15 -7
- sqlalchemy/exc.py +16 -0
- sqlalchemy/ext/associationproxy.py +8 -8
- sqlalchemy/ext/asyncio/base.py +2 -2
- sqlalchemy/ext/asyncio/engine.py +9 -7
- sqlalchemy/ext/asyncio/exc.py +9 -0
- sqlalchemy/ext/asyncio/result.py +4 -4
- sqlalchemy/ext/asyncio/scoping.py +124 -16
- sqlalchemy/ext/asyncio/session.py +271 -9
- sqlalchemy/ext/hybrid.py +6 -6
- sqlalchemy/inspection.py +14 -1
- sqlalchemy/log.py +3 -7
- sqlalchemy/orm/_typing.py +5 -1
- sqlalchemy/orm/attributes.py +5 -5
- sqlalchemy/orm/base.py +17 -2
- sqlalchemy/orm/bulk_persistence.py +16 -0
- sqlalchemy/orm/clsregistry.py +1 -1
- sqlalchemy/orm/collections.py +2 -2
- sqlalchemy/orm/decl_api.py +57 -5
- sqlalchemy/orm/decl_base.py +18 -24
- sqlalchemy/orm/descriptor_props.py +8 -8
- sqlalchemy/orm/exc.py +1 -1
- sqlalchemy/orm/identity.py +1 -1
- sqlalchemy/orm/instrumentation.py +7 -3
- sqlalchemy/orm/interfaces.py +10 -3
- sqlalchemy/orm/loading.py +21 -2
- sqlalchemy/orm/mapped_collection.py +2 -2
- sqlalchemy/orm/mapper.py +32 -7
- sqlalchemy/orm/path_registry.py +100 -22
- sqlalchemy/orm/properties.py +5 -5
- sqlalchemy/orm/query.py +35 -35
- sqlalchemy/orm/relationships.py +43 -26
- sqlalchemy/orm/scoping.py +24 -6
- sqlalchemy/orm/session.py +72 -45
- sqlalchemy/orm/state.py +2 -2
- sqlalchemy/orm/strategies.py +31 -20
- sqlalchemy/orm/strategy_options.py +103 -28
- sqlalchemy/orm/util.py +17 -7
- sqlalchemy/orm/writeonly.py +7 -0
- sqlalchemy/pool/base.py +70 -48
- sqlalchemy/schema.py +1 -0
- sqlalchemy/sql/_cache_key_cy.py +363 -0
- sqlalchemy/sql/_elements_constructors.py +29 -3
- sqlalchemy/sql/_typing.py +16 -3
- sqlalchemy/sql/_util_cy.pxd +11 -0
- sqlalchemy/sql/_util_cy.py +1 -1
- sqlalchemy/sql/annotation.py +6 -6
- sqlalchemy/sql/base.py +13 -9
- sqlalchemy/sql/cache_key.py +132 -283
- sqlalchemy/sql/compiler.py +158 -21
- sqlalchemy/sql/crud.py +5 -5
- sqlalchemy/sql/ddl.py +59 -25
- sqlalchemy/sql/default_comparator.py +8 -2
- sqlalchemy/sql/elements.py +56 -30
- sqlalchemy/sql/functions.py +8 -8
- sqlalchemy/sql/lambdas.py +1 -1
- sqlalchemy/sql/naming.py +1 -6
- sqlalchemy/sql/operators.py +18 -6
- sqlalchemy/sql/schema.py +563 -190
- sqlalchemy/sql/selectable.py +107 -15
- sqlalchemy/sql/sqltypes.py +47 -10
- sqlalchemy/sql/traversals.py +6 -6
- sqlalchemy/sql/type_api.py +14 -10
- sqlalchemy/sql/util.py +14 -14
- sqlalchemy/sql/visitors.py +8 -12
- sqlalchemy/testing/__init__.py +1 -0
- sqlalchemy/testing/cancellation.py +237 -0
- sqlalchemy/testing/engines.py +25 -11
- sqlalchemy/testing/exclusions.py +28 -3
- sqlalchemy/testing/profiles_file.py +350 -0
- sqlalchemy/testing/profiling.py +98 -132
- sqlalchemy/testing/provision.py +21 -0
- sqlalchemy/testing/requirements.py +20 -27
- sqlalchemy/testing/suite/test_reflection.py +264 -0
- sqlalchemy/testing/util.py +41 -0
- sqlalchemy/util/__init__.py +8 -1
- sqlalchemy/util/_collections.py +2 -2
- sqlalchemy/util/_has_cython.py +2 -0
- sqlalchemy/util/compat.py +0 -1
- sqlalchemy/util/concurrency.py +2 -52
- sqlalchemy/util/cython.py +17 -1
- sqlalchemy/util/deprecations.py +1 -1
- sqlalchemy/util/langhelpers.py +487 -13
- sqlalchemy/util/typing.py +6 -6
- {sqlalchemy-2.1.0b3.dist-info → sqlalchemy-2.1.0rc2.dist-info}/METADATA +8 -7
- sqlalchemy-2.1.0rc2.dist-info/RECORD +268 -0
- {sqlalchemy-2.1.0b3.dist-info → sqlalchemy-2.1.0rc2.dist-info}/WHEEL +1 -1
- sqlalchemy-2.1.0rc2.dist-info/licenses/AUTHORS +30 -0
- sqlalchemy-2.1.0b3.dist-info/RECORD +0 -263
- {sqlalchemy-2.1.0b3.dist-info → sqlalchemy-2.1.0rc2.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.1.0b3.dist-info → sqlalchemy-2.1.0rc2.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
|
@@ -74,6 +74,7 @@ from .schema import ExecutableDDLElement as ExecutableDDLElement
|
|
|
74
74
|
from .schema import FetchedValue as FetchedValue
|
|
75
75
|
from .schema import ForeignKey as ForeignKey
|
|
76
76
|
from .schema import ForeignKeyConstraint as ForeignKeyConstraint
|
|
77
|
+
from .schema import ForeignKeyTarget as ForeignKeyTarget
|
|
77
78
|
from .schema import Identity as Identity
|
|
78
79
|
from .schema import Index as Index
|
|
79
80
|
from .schema import insert_sentinel as insert_sentinel
|
|
@@ -284,7 +285,7 @@ from .types import Uuid as Uuid
|
|
|
284
285
|
from .types import VARBINARY as VARBINARY
|
|
285
286
|
from .types import VARCHAR as VARCHAR
|
|
286
287
|
|
|
287
|
-
__version__ = "2.1.
|
|
288
|
+
__version__ = "2.1.0rc2"
|
|
288
289
|
|
|
289
290
|
|
|
290
291
|
def __go(lcls: Any) -> None:
|
sqlalchemy/connectors/asyncio.py
CHANGED
|
@@ -337,7 +337,7 @@ class AsyncAdapt_dbapi_ss_cursor(AsyncAdapt_dbapi_cursor):
|
|
|
337
337
|
def close(self) -> None:
|
|
338
338
|
if self._cursor is not None:
|
|
339
339
|
await_(self._cursor.close())
|
|
340
|
-
self._cursor = None # type: ignore
|
|
340
|
+
self._cursor = None # type: ignore[assignment]
|
|
341
341
|
|
|
342
342
|
def fetchone(self) -> Optional[Any]:
|
|
343
343
|
return await_(self._cursor.fetchone())
|
sqlalchemy/connectors/pyodbc.py
CHANGED
|
@@ -164,25 +164,14 @@ class PyODBCConnector(Connector):
|
|
|
164
164
|
else:
|
|
165
165
|
return False
|
|
166
166
|
|
|
167
|
-
def
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
def _parse_dbapi_version(self, vers: str) -> interfaces.VersionInfoType:
|
|
173
|
-
m = re.match(r"(?:py.*-)?([\d\.]+)(?:-(\w+))?", vers)
|
|
174
|
-
if not m:
|
|
175
|
-
return ()
|
|
176
|
-
vers_tuple: interfaces.VersionInfoType = tuple(
|
|
177
|
-
[int(x) for x in m.group(1).split(".")]
|
|
178
|
-
)
|
|
179
|
-
if m.group(2):
|
|
180
|
-
vers_tuple += (m.group(2),)
|
|
181
|
-
return vers_tuple
|
|
167
|
+
def retrieve_dbapi_version(
|
|
168
|
+
self, dbapi: interfaces.DBAPIModule
|
|
169
|
+
) -> util.VersionInfo:
|
|
170
|
+
return util.parse_version_string(dbapi.version)
|
|
182
171
|
|
|
183
172
|
def _get_server_version_info(
|
|
184
173
|
self, connection: Connection
|
|
185
|
-
) -> interfaces.
|
|
174
|
+
) -> interfaces.ServerVersionInfoType:
|
|
186
175
|
# NOTE: this function is not reliable, particularly when
|
|
187
176
|
# freetds is in use. Implement database-specific server version
|
|
188
177
|
# queries.
|
sqlalchemy/dialects/__init__.py
CHANGED
|
@@ -43,7 +43,7 @@ def _auto_fn(name: str) -> Optional[Callable[[], Type[Dialect]]]:
|
|
|
43
43
|
module: Any = __import__(
|
|
44
44
|
"sqlalchemy.dialects.mysql.mariadb"
|
|
45
45
|
).dialects.mysql.mariadb
|
|
46
|
-
return module.loader(driver) # type: ignore
|
|
46
|
+
return module.loader(driver) # type: ignore[no-any-return]
|
|
47
47
|
else:
|
|
48
48
|
module = __import__("sqlalchemy.dialects.%s" % (dialect,)).dialects
|
|
49
49
|
module = getattr(module, dialect)
|
|
@@ -1561,7 +1561,7 @@ class MSUUid(sqltypes.Uuid):
|
|
|
1561
1561
|
if self.native_uuid:
|
|
1562
1562
|
|
|
1563
1563
|
def process(value):
|
|
1564
|
-
return f"""'{str(value).replace("'
|
|
1564
|
+
return f"""'{str(value).replace("'", "''")}'"""
|
|
1565
1565
|
|
|
1566
1566
|
return process
|
|
1567
1567
|
else:
|
|
@@ -3373,11 +3373,29 @@ class MSDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
3373
3373
|
else:
|
|
3374
3374
|
return self.schema_name
|
|
3375
3375
|
|
|
3376
|
+
@reflection.cache
|
|
3376
3377
|
@_db_plus_owner
|
|
3377
3378
|
def has_table(self, connection, tablename, dbname, owner, schema, **kw):
|
|
3378
3379
|
self._ensure_has_table_connection(connection)
|
|
3379
3380
|
|
|
3380
|
-
return self.
|
|
3381
|
+
return self._internal_has_multi_table(
|
|
3382
|
+
connection, [tablename], owner, schema, **kw
|
|
3383
|
+
)[schema, tablename]
|
|
3384
|
+
|
|
3385
|
+
def has_multi_table(self, connection, table_names, schema=None, **kw):
|
|
3386
|
+
# this does not really matche the signature of _db_plus_owner_multi
|
|
3387
|
+
# so manually do the unpacking here
|
|
3388
|
+
dbname, owner = _owner_plus_db(self, schema)
|
|
3389
|
+
return _switch_db(
|
|
3390
|
+
dbname,
|
|
3391
|
+
connection,
|
|
3392
|
+
self._internal_has_multi_table,
|
|
3393
|
+
connection,
|
|
3394
|
+
table_names,
|
|
3395
|
+
owner,
|
|
3396
|
+
schema,
|
|
3397
|
+
**kw,
|
|
3398
|
+
).items()
|
|
3381
3399
|
|
|
3382
3400
|
@reflection.cache
|
|
3383
3401
|
@_db_plus_owner
|
|
@@ -3452,37 +3470,38 @@ class MSDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
3452
3470
|
view_names = [r[0] for r in connection.execute(s)]
|
|
3453
3471
|
return view_names
|
|
3454
3472
|
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
connection.scalar(
|
|
3462
|
-
# U filters on user tables only.
|
|
3463
|
-
text("SELECT object_id(:table_name, 'U')"),
|
|
3464
|
-
{"table_name": f"tempdb.dbo.[{tablename}]"},
|
|
3465
|
-
)
|
|
3466
|
-
)
|
|
3467
|
-
else:
|
|
3468
|
-
tables = ischema.tables
|
|
3469
|
-
|
|
3470
|
-
s = sql.select(tables.c.table_name).where(
|
|
3471
|
-
sql.and_(
|
|
3472
|
-
sql.or_(
|
|
3473
|
-
tables.c.table_type == "BASE TABLE",
|
|
3474
|
-
tables.c.table_type == "VIEW",
|
|
3475
|
-
),
|
|
3476
|
-
tables.c.table_name == tablename,
|
|
3477
|
-
)
|
|
3473
|
+
def _internal_has_multi_table(
|
|
3474
|
+
self, connection, table_names, owner, schema, **kw
|
|
3475
|
+
):
|
|
3476
|
+
regular_names, multi_object_names, temp_names = (
|
|
3477
|
+
self._partition_filter_names(
|
|
3478
|
+
connection, owner, table_names, ObjectScope.ANY, ObjectKind.ANY
|
|
3478
3479
|
)
|
|
3480
|
+
)
|
|
3479
3481
|
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
+
result = {}
|
|
3483
|
+
if multi_object_names:
|
|
3484
|
+
# in multi_object_names we already have the names that exist in
|
|
3485
|
+
# the database, no need to do another query here
|
|
3486
|
+
name_map = self._multi_name_map(regular_names)
|
|
3487
|
+
for server_name in multi_object_names:
|
|
3488
|
+
result[(schema, name_map(server_name))] = True
|
|
3482
3489
|
|
|
3483
|
-
|
|
3490
|
+
if temp_names:
|
|
3491
|
+
# mssql does not support temporary views
|
|
3492
|
+
# U filters on user tables only.
|
|
3493
|
+
query = text("SELECT object_id(:table_name, 'U')")
|
|
3494
|
+
for temp_name in temp_names:
|
|
3495
|
+
result[(schema, temp_name)] = bool(
|
|
3496
|
+
connection.scalar(
|
|
3497
|
+
query,
|
|
3498
|
+
{"table_name": f"tempdb..[{temp_name}]"},
|
|
3499
|
+
)
|
|
3500
|
+
)
|
|
3484
3501
|
|
|
3485
|
-
|
|
3502
|
+
for name in table_names:
|
|
3503
|
+
result.setdefault((schema, name), False)
|
|
3504
|
+
return result
|
|
3486
3505
|
|
|
3487
3506
|
@reflection.cache
|
|
3488
3507
|
@_db_plus_owner
|
|
@@ -3570,14 +3589,20 @@ class MSDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
3570
3589
|
|
|
3571
3590
|
if coltype in (MSBinary, MSVarBinary, sqltypes.LargeBinary):
|
|
3572
3591
|
kwargs["length"] = maxlen if maxlen != -1 else None
|
|
3573
|
-
elif coltype in (MSString, MSChar
|
|
3592
|
+
elif coltype in (MSString, MSChar):
|
|
3574
3593
|
kwargs["length"] = maxlen if maxlen != -1 else None
|
|
3575
3594
|
if collation:
|
|
3576
3595
|
kwargs["collation"] = collation
|
|
3577
|
-
elif coltype in (MSNVarchar, MSNChar
|
|
3596
|
+
elif coltype in (MSNVarchar, MSNChar):
|
|
3578
3597
|
kwargs["length"] = maxlen // 2 if maxlen != -1 else None
|
|
3579
3598
|
if collation:
|
|
3580
3599
|
kwargs["collation"] = collation
|
|
3600
|
+
elif coltype in (MSText, MSNText):
|
|
3601
|
+
# TEXT / NTEXT are unlengthed LOB types. sys.columns.max_length
|
|
3602
|
+
# reports 16 for these, which is the size of the in-row LOB
|
|
3603
|
+
# pointer and not a character length, so no length is applied.
|
|
3604
|
+
if collation:
|
|
3605
|
+
kwargs["collation"] = collation
|
|
3581
3606
|
|
|
3582
3607
|
if coltype is None:
|
|
3583
3608
|
if base_type is not None and base_type != type_:
|
|
@@ -3853,6 +3878,34 @@ index_info AS (
|
|
|
3853
3878
|
|
|
3854
3879
|
# --- multi-reflection API ---
|
|
3855
3880
|
|
|
3881
|
+
@lru_cache
|
|
3882
|
+
def _partition_filter_names_query(self, add_filter_names: bool):
|
|
3883
|
+
sys_objects = ischema.sys_objects
|
|
3884
|
+
sys_schemas = ischema.sys_schemas
|
|
3885
|
+
|
|
3886
|
+
s = (
|
|
3887
|
+
sql.select(sys_objects.c.name)
|
|
3888
|
+
.select_from(sys_objects)
|
|
3889
|
+
.join(
|
|
3890
|
+
sys_schemas,
|
|
3891
|
+
onclause=sys_objects.c.schema_id == sys_schemas.c.schema_id,
|
|
3892
|
+
)
|
|
3893
|
+
.where(
|
|
3894
|
+
sys_schemas.c.name == sql.bindparam("schema"),
|
|
3895
|
+
sys_objects.c.type.in_(
|
|
3896
|
+
sql.bindparam("type_filter", expanding=True)
|
|
3897
|
+
),
|
|
3898
|
+
)
|
|
3899
|
+
)
|
|
3900
|
+
if add_filter_names:
|
|
3901
|
+
s = s.where(
|
|
3902
|
+
sys_objects.c.name.in_(
|
|
3903
|
+
sql.bindparam("filter_names", expanding=True)
|
|
3904
|
+
)
|
|
3905
|
+
)
|
|
3906
|
+
|
|
3907
|
+
return s
|
|
3908
|
+
|
|
3856
3909
|
def _partition_filter_names(
|
|
3857
3910
|
self, connection, owner, filter_names, scope, kind
|
|
3858
3911
|
):
|
|
@@ -3914,29 +3967,20 @@ index_info AS (
|
|
|
3914
3967
|
|
|
3915
3968
|
type_filter = []
|
|
3916
3969
|
if ObjectKind.TABLE in kind:
|
|
3917
|
-
type_filter.append("
|
|
3970
|
+
type_filter.append("U")
|
|
3918
3971
|
if ObjectKind.VIEW in kind:
|
|
3919
|
-
type_filter.append("
|
|
3972
|
+
type_filter.append("V")
|
|
3920
3973
|
# SQL Server does not support materialized views, so ignore them
|
|
3921
3974
|
if not type_filter:
|
|
3922
3975
|
return (regular_names, [], temp_names)
|
|
3923
3976
|
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
"
|
|
3929
|
-
"AND o.type IN (%s)" % ", ".join(type_filter)
|
|
3930
|
-
)
|
|
3931
|
-
params = [sql.bindparam("owner", owner, ischema.CoerceUnicode())]
|
|
3932
|
-
if regular_names:
|
|
3933
|
-
query += " AND o.name IN :filter_names"
|
|
3934
|
-
params.append(
|
|
3935
|
-
sql.bindparam("filter_names", regular_names, expanding=True)
|
|
3936
|
-
)
|
|
3977
|
+
has_regular_names = bool(regular_names)
|
|
3978
|
+
query = self._partition_filter_names_query(has_regular_names)
|
|
3979
|
+
params = {"schema": owner, "type_filter": type_filter}
|
|
3980
|
+
if has_regular_names:
|
|
3981
|
+
params["filter_names"] = regular_names
|
|
3937
3982
|
|
|
3938
|
-
|
|
3939
|
-
multi_object_names = [row[0] for row in rp]
|
|
3983
|
+
multi_object_names = connection.scalars(query, params).all()
|
|
3940
3984
|
|
|
3941
3985
|
return (regular_names, multi_object_names, temp_names)
|
|
3942
3986
|
|
|
@@ -4305,9 +4349,13 @@ index_info AS (
|
|
|
4305
4349
|
# return empty foreign_keys for temp tables that exist. We
|
|
4306
4350
|
# preserve that here by checking existence and returning the
|
|
4307
4351
|
# default (empty) reflection for each temp that is reachable.
|
|
4352
|
+
has_temp_tables = self._internal_has_multi_table(
|
|
4353
|
+
connection, temp_names, owner="dbo", schema=schema, **kw
|
|
4354
|
+
)
|
|
4308
4355
|
for name in temp_names:
|
|
4309
|
-
|
|
4310
|
-
|
|
4356
|
+
key = (schema, name)
|
|
4357
|
+
if has_temp_tables.get(key):
|
|
4358
|
+
final[key] = ReflectionDefaults.foreign_keys()
|
|
4311
4359
|
|
|
4312
4360
|
return final.items()
|
|
4313
4361
|
|
|
@@ -83,6 +83,24 @@ class MSDialect_mssqlpython(MSDialect):
|
|
|
83
83
|
|
|
84
84
|
supports_native_decimal = True
|
|
85
85
|
|
|
86
|
+
_disconnect_driver_errors = frozenset(
|
|
87
|
+
{
|
|
88
|
+
# mssql-python converts the originating SQLSTATE into these
|
|
89
|
+
# values and exposes them on ``Exception.driver_error``.
|
|
90
|
+
"Disconnect error", # 01002
|
|
91
|
+
"Client unable to establish connection", # 08001
|
|
92
|
+
"Connection not open", # 08003
|
|
93
|
+
"Connection failure during transaction", # 08007
|
|
94
|
+
"Communication link failure", # 08S01
|
|
95
|
+
# mssql-python does not map these SQLSTATE values to a
|
|
96
|
+
# dedicated driver error yet.
|
|
97
|
+
"An error occurred with SQLSTATE code: 08S02",
|
|
98
|
+
"An error occurred with SQLSTATE code: 10054",
|
|
99
|
+
"Connection timeout expired", # HYT01
|
|
100
|
+
"Function sequence error", # HY010
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
86
104
|
# used by pyodbc _ms_numeric_pyodbc class
|
|
87
105
|
_need_decimal_fix = True
|
|
88
106
|
|
|
@@ -163,32 +181,30 @@ class MSDialect_mssqlpython(MSDialect):
|
|
|
163
181
|
],
|
|
164
182
|
cursor: Optional[interfaces.DBAPICursor],
|
|
165
183
|
) -> bool:
|
|
184
|
+
if not isinstance(e, self.loaded_dbapi.Error):
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
if getattr(e, "driver_error", None) in self._disconnect_driver_errors:
|
|
188
|
+
return True
|
|
189
|
+
|
|
166
190
|
if isinstance(e, self.loaded_dbapi.ProgrammingError):
|
|
167
191
|
return (
|
|
168
192
|
"The cursor's connection has been closed." in str(e)
|
|
169
193
|
or "Attempt to use a closed connection." in str(e)
|
|
170
194
|
or "Driver Error: Operation cannot be performed" in str(e)
|
|
171
195
|
)
|
|
172
|
-
elif isinstance(e, self.loaded_dbapi.InterfaceError):
|
|
173
|
-
return bool(re.search(r"Cannot .* on closed connection", str(e)))
|
|
174
|
-
else:
|
|
175
|
-
return False
|
|
176
196
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
189
|
-
if m.group(2):
|
|
190
|
-
vers_tuple += (m.group(2),)
|
|
191
|
-
return vers_tuple
|
|
197
|
+
if isinstance(e, self.loaded_dbapi.InterfaceError):
|
|
198
|
+
return bool(
|
|
199
|
+
re.search(r"Cannot .* on (?:a )?closed connection", str(e))
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def retrieve_dbapi_version(
|
|
205
|
+
self, dbapi: interfaces.DBAPIModule
|
|
206
|
+
) -> util.VersionInfo:
|
|
207
|
+
return util.parse_version_string(dbapi.version)
|
|
192
208
|
|
|
193
209
|
def _get_server_version_info(self, connection):
|
|
194
210
|
vers = connection.exec_driver_sql("select @@version").scalar()
|
|
@@ -61,11 +61,14 @@ class MSDialect_pymssql(MSDialect):
|
|
|
61
61
|
{sqltypes.Numeric: _MSNumeric_pymssql, sqltypes.Float: sqltypes.Float},
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
+
def retrieve_dbapi_version(self, dbapi):
|
|
65
|
+
return util.parse_version_string(getattr(dbapi, "__version__", None))
|
|
66
|
+
|
|
64
67
|
@classmethod
|
|
65
68
|
def import_dbapi(cls):
|
|
66
69
|
module = __import__("pymssql")
|
|
67
70
|
# pymmsql < 2.1.1 doesn't have a Binary method. we use string
|
|
68
|
-
client_ver =
|
|
71
|
+
client_ver = util.parse_version_string(module.__version__)
|
|
69
72
|
if client_ver < (2, 1, 1):
|
|
70
73
|
# TODO: monkeypatching here is less than ideal
|
|
71
74
|
module.Binary = lambda x: x if hasattr(x, "decode") else str(x)
|
|
@@ -606,10 +606,10 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
|
|
606
606
|
and self.dbapi
|
|
607
607
|
and hasattr(self.dbapi.Cursor, "nextset")
|
|
608
608
|
)
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
8
|
|
609
|
+
# a version which can't be determined is assumed to be an old one
|
|
610
|
+
version = self._dbapi_version_or_none
|
|
611
|
+
self._need_decimal_fix = bool(self.dbapi) and (
|
|
612
|
+
version is None or version < (2, 1, 8)
|
|
613
613
|
)
|
|
614
614
|
self.fast_executemany = fast_executemany
|
|
615
615
|
if fast_executemany:
|
|
@@ -39,6 +39,7 @@ from typing import Union
|
|
|
39
39
|
|
|
40
40
|
from .pymysql import _connection_ping_reconnects_true
|
|
41
41
|
from .pymysql import MySQLDialect_pymysql
|
|
42
|
+
from ... import util
|
|
42
43
|
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
|
|
43
44
|
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
44
45
|
from ...connectors.asyncio import AsyncAdapt_dbapi_module
|
|
@@ -104,7 +105,7 @@ class AsyncAdapt_aiomysql_connection(
|
|
|
104
105
|
await_(self._connection.autocommit(value))
|
|
105
106
|
|
|
106
107
|
def get_autocommit(self) -> bool:
|
|
107
|
-
return self._connection.get_autocommit() # type: ignore
|
|
108
|
+
return self._connection.get_autocommit() # type: ignore[no-any-return]
|
|
108
109
|
|
|
109
110
|
def close(self) -> None:
|
|
110
111
|
await_(self._connection.ensure_closed())
|
|
@@ -215,6 +216,14 @@ class MySQLDialect_aiomysql(MySQLDialect_pymysql):
|
|
|
215
216
|
__import__("aiomysql"), __import__("pymysql")
|
|
216
217
|
)
|
|
217
218
|
|
|
219
|
+
def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
|
|
220
|
+
# the version of aiomysql itself, rather than that of the pymysql
|
|
221
|
+
# module it makes use of
|
|
222
|
+
aiomysql = getattr(dbapi, "aiomysql", None)
|
|
223
|
+
return util.parse_version_string(
|
|
224
|
+
getattr(aiomysql, "__version__", None)
|
|
225
|
+
)
|
|
226
|
+
|
|
218
227
|
def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
|
|
219
228
|
dbapi_connection.terminate()
|
|
220
229
|
|
|
@@ -238,7 +247,7 @@ class MySQLDialect_aiomysql(MySQLDialect_pymysql):
|
|
|
238
247
|
return "not connected" in str_e
|
|
239
248
|
|
|
240
249
|
def _found_rows_client_flag(self) -> int:
|
|
241
|
-
from pymysql.constants import CLIENT # type: ignore
|
|
250
|
+
from pymysql.constants import CLIENT # type: ignore[import-untyped]
|
|
242
251
|
|
|
243
252
|
return CLIENT.FOUND_ROWS # type: ignore[no-any-return]
|
|
244
253
|
|
|
@@ -116,7 +116,7 @@ class AsyncAdapt_asyncmy_connection(
|
|
|
116
116
|
await_(self._connection.autocommit(value))
|
|
117
117
|
|
|
118
118
|
def get_autocommit(self) -> bool:
|
|
119
|
-
return self._connection.get_autocommit() # type: ignore
|
|
119
|
+
return self._connection.get_autocommit() # type: ignore[no-any-return]
|
|
120
120
|
|
|
121
121
|
def close(self) -> None:
|
|
122
122
|
await_(self._connection.ensure_closed())
|
|
@@ -200,6 +200,11 @@ class MySQLDialect_asyncmy(MySQLDialect_pymysql):
|
|
|
200
200
|
def import_dbapi(cls) -> DBAPIModule:
|
|
201
201
|
return AsyncAdapt_asyncmy_dbapi(__import__("asyncmy"))
|
|
202
202
|
|
|
203
|
+
def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
|
|
204
|
+
# asyncmy publishes no version of its own within the module, so
|
|
205
|
+
# the installed distribution is consulted
|
|
206
|
+
return util.parse_version_from_metadata("asyncmy")
|
|
207
|
+
|
|
203
208
|
def do_terminate(self, dbapi_connection: DBAPIConnection) -> None:
|
|
204
209
|
dbapi_connection.terminate()
|
|
205
210
|
|
|
@@ -223,7 +228,7 @@ class MySQLDialect_asyncmy(MySQLDialect_pymysql):
|
|
|
223
228
|
)
|
|
224
229
|
|
|
225
230
|
def _found_rows_client_flag(self) -> int:
|
|
226
|
-
from asyncmy.constants import CLIENT # type: ignore
|
|
231
|
+
from asyncmy.constants import CLIENT # type: ignore[import-not-found]
|
|
227
232
|
|
|
228
233
|
return CLIENT.FOUND_ROWS # type: ignore[no-any-return]
|
|
229
234
|
|
|
@@ -1149,6 +1149,7 @@ from ...types import DATE
|
|
|
1149
1149
|
from ...types import LargeBinary
|
|
1150
1150
|
from ...types import UUID
|
|
1151
1151
|
from ...types import VARBINARY
|
|
1152
|
+
from ...util import OrderedSet
|
|
1152
1153
|
from ...util import topological
|
|
1153
1154
|
|
|
1154
1155
|
if TYPE_CHECKING:
|
|
@@ -2135,8 +2136,8 @@ class MySQLDDLCompiler(
|
|
|
2135
2136
|
"SUBPARTITION_BY",
|
|
2136
2137
|
]
|
|
2137
2138
|
|
|
2138
|
-
nonpart_options =
|
|
2139
|
-
part_options =
|
|
2139
|
+
nonpart_options = OrderedSet(opts).difference(partition_options)
|
|
2140
|
+
part_options = OrderedSet(opts).intersection(partition_options)
|
|
2140
2141
|
|
|
2141
2142
|
for opt in topological.sort(
|
|
2142
2143
|
[
|
|
@@ -2976,9 +2977,9 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
|
|
|
2976
2977
|
if isinstance(
|
|
2977
2978
|
e,
|
|
2978
2979
|
(
|
|
2979
|
-
self.dbapi.OperationalError, # type: ignore
|
|
2980
|
-
self.dbapi.ProgrammingError, # type: ignore
|
|
2981
|
-
self.dbapi.InterfaceError, # type: ignore
|
|
2980
|
+
self.dbapi.OperationalError, # type: ignore[union-attr]
|
|
2981
|
+
self.dbapi.ProgrammingError, # type: ignore[union-attr]
|
|
2982
|
+
self.dbapi.InterfaceError, # type: ignore[union-attr]
|
|
2982
2983
|
),
|
|
2983
2984
|
) and self._extract_error_code(e) in (
|
|
2984
2985
|
1927,
|
|
@@ -2991,7 +2992,7 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
|
|
|
2991
2992
|
):
|
|
2992
2993
|
return True
|
|
2993
2994
|
elif isinstance(
|
|
2994
|
-
e, (self.dbapi.InterfaceError, self.dbapi.InternalError) # type: ignore # noqa: E501
|
|
2995
|
+
e, (self.dbapi.InterfaceError, self.dbapi.InternalError) # type: ignore[union-attr] # noqa: E501
|
|
2995
2996
|
):
|
|
2996
2997
|
# if underlying connection is closed,
|
|
2997
2998
|
# this is the error you get
|
|
@@ -3087,7 +3088,7 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
|
|
|
3087
3088
|
#
|
|
3088
3089
|
# there's more "doesn't exist" kinds of messages but they are
|
|
3089
3090
|
# less clear if mysql 8 would suddenly start using one of those
|
|
3090
|
-
if self._extract_error_code(e.orig) in (1146, 1049, 1051): # type: ignore # noqa: E501
|
|
3091
|
+
if self._extract_error_code(e.orig) in (1146, 1049, 1051): # type: ignore[arg-type, operator] # noqa: E501
|
|
3091
3092
|
return False
|
|
3092
3093
|
raise
|
|
3093
3094
|
|
|
@@ -3224,7 +3225,7 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
|
|
|
3224
3225
|
if schema is not None:
|
|
3225
3226
|
current_schema: str = schema
|
|
3226
3227
|
else:
|
|
3227
|
-
current_schema = self.default_schema_name # type: ignore
|
|
3228
|
+
current_schema = self.default_schema_name # type: ignore[assignment] # noqa: E501
|
|
3228
3229
|
|
|
3229
3230
|
charset = self._connection_charset
|
|
3230
3231
|
|
|
@@ -3385,7 +3386,7 @@ class MySQLDialect(_mariadb_shim.MariaDBShim, default.DefaultDialect):
|
|
|
3385
3386
|
def lower(s: str) -> str:
|
|
3386
3387
|
return s
|
|
3387
3388
|
|
|
3388
|
-
default_schema_name: str = connection.dialect.default_schema_name # type: ignore # noqa: E501
|
|
3389
|
+
default_schema_name: str = connection.dialect.default_schema_name # type: ignore[assignment] # noqa: E501
|
|
3389
3390
|
|
|
3390
3391
|
# NOTE: using (table_schema, table_name, lower(column_name)) in (...)
|
|
3391
3392
|
# is very slow since mysql does not seem able to properly use indexse.
|
|
@@ -257,7 +257,7 @@ class SET(_StringType):
|
|
|
257
257
|
if value is not None and not isinstance(value, (int, str)):
|
|
258
258
|
value = ",".join(value)
|
|
259
259
|
if super_convert:
|
|
260
|
-
return super_convert(value) # type: ignore
|
|
260
|
+
return super_convert(value) # type: ignore[arg-type, no-any-return] # noqa: E501
|
|
261
261
|
else:
|
|
262
262
|
return value
|
|
263
263
|
|
|
@@ -30,7 +30,6 @@ be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
|
|
|
30
30
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
|
-
import re
|
|
34
33
|
from typing import Any
|
|
35
34
|
from typing import Optional
|
|
36
35
|
from typing import Sequence
|
|
@@ -60,7 +59,7 @@ if TYPE_CHECKING:
|
|
|
60
59
|
from ...sql.type_api import _ResultProcessorType
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
mariadb_cpy_minimum_version = (1, 0, 1)
|
|
62
|
+
mariadb_cpy_minimum_version = util.VersionInfo((1, 0, 1))
|
|
64
63
|
|
|
65
64
|
|
|
66
65
|
class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]):
|
|
@@ -125,6 +124,8 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
|
|
125
124
|
driver = "mariadbconnector"
|
|
126
125
|
supports_statement_cache = True
|
|
127
126
|
|
|
127
|
+
minimum_dbapi_version = mariadb_cpy_minimum_version
|
|
128
|
+
|
|
128
129
|
# set this to True at the module level to prevent the driver from running
|
|
129
130
|
# against a backend that server detects as MySQL. currently this appears to
|
|
130
131
|
# be unnecessary as MariaDB client libraries have always worked against
|
|
@@ -151,30 +152,12 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
|
|
|
151
152
|
MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID}
|
|
152
153
|
)
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if self.dbapi and hasattr(self.dbapi, "__version__"):
|
|
157
|
-
return tuple(
|
|
158
|
-
[
|
|
159
|
-
int(x)
|
|
160
|
-
for x in re.findall(
|
|
161
|
-
r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
|
|
162
|
-
)
|
|
163
|
-
]
|
|
164
|
-
)
|
|
165
|
-
else:
|
|
166
|
-
return (99, 99, 99)
|
|
155
|
+
def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
|
|
156
|
+
return util.parse_version_string(getattr(dbapi, "__version__", None))
|
|
167
157
|
|
|
168
158
|
def __init__(self, **kwargs: Any) -> None:
|
|
169
159
|
super().__init__(**kwargs)
|
|
170
160
|
self.paramstyle = "qmark"
|
|
171
|
-
if self.dbapi is not None:
|
|
172
|
-
if self._dbapi_version < mariadb_cpy_minimum_version:
|
|
173
|
-
raise NotImplementedError(
|
|
174
|
-
"The minimum required version for MariaDB "
|
|
175
|
-
"Connector/Python is %s"
|
|
176
|
-
% ".".join(str(x) for x in mariadb_cpy_minimum_version)
|
|
177
|
-
)
|
|
178
161
|
|
|
179
162
|
@classmethod
|
|
180
163
|
def import_dbapi(cls) -> DBAPIModule:
|
|
@@ -48,7 +48,6 @@ charset/collation will allow connectivity.
|
|
|
48
48
|
|
|
49
49
|
from __future__ import annotations
|
|
50
50
|
|
|
51
|
-
import re
|
|
52
51
|
from typing import Any
|
|
53
52
|
from typing import cast
|
|
54
53
|
from typing import Optional
|
|
@@ -198,7 +197,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
198
197
|
# supports_sane_rowcount.
|
|
199
198
|
if self.dbapi is not None:
|
|
200
199
|
try:
|
|
201
|
-
from mysql.connector import constants # type: ignore
|
|
200
|
+
from mysql.connector import constants # type: ignore[import-not-found] # noqa: E501
|
|
202
201
|
|
|
203
202
|
ClientFlag = constants.ClientFlag
|
|
204
203
|
|
|
@@ -212,19 +211,14 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
|
|
212
211
|
|
|
213
212
|
return [], opts
|
|
214
213
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if self.dbapi and hasattr(self.dbapi, "__version__"):
|
|
218
|
-
m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
|
|
219
|
-
if m:
|
|
220
|
-
return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
|
|
221
|
-
return None
|
|
214
|
+
def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
|
|
215
|
+
return util.parse_version_string(getattr(dbapi, "__version__", None))
|
|
222
216
|
|
|
223
217
|
def _detect_charset(self, connection: Connection) -> str:
|
|
224
|
-
return connection.connection.charset # type: ignore
|
|
218
|
+
return connection.connection.charset # type: ignore[no-any-return]
|
|
225
219
|
|
|
226
220
|
def _extract_error_code(self, exception: BaseException) -> int:
|
|
227
|
-
return exception.errno # type: ignore
|
|
221
|
+
return exception.errno # type: ignore[attr-defined, no-any-return]
|
|
228
222
|
|
|
229
223
|
def is_disconnect(
|
|
230
224
|
self,
|