SQLAlchemy 2.0.49__py3-none-any.whl → 2.0.51__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 (127) hide show
  1. sqlalchemy/__init__.py +1 -1
  2. sqlalchemy/connectors/aioodbc.py +0 -1
  3. sqlalchemy/dialects/_typing.py +0 -1
  4. sqlalchemy/dialects/mssql/__init__.py +0 -1
  5. sqlalchemy/dialects/mssql/base.py +8 -18
  6. sqlalchemy/dialects/mssql/information_schema.py +0 -1
  7. sqlalchemy/dialects/mssql/pymssql.py +1 -0
  8. sqlalchemy/dialects/mssql/pyodbc.py +0 -1
  9. sqlalchemy/dialects/mysql/aiomysql.py +26 -2
  10. sqlalchemy/dialects/mysql/asyncmy.py +25 -3
  11. sqlalchemy/dialects/mysql/base.py +6 -4
  12. sqlalchemy/dialects/mysql/cymysql.py +1 -0
  13. sqlalchemy/dialects/mysql/dml.py +0 -1
  14. sqlalchemy/dialects/mysql/mariadbconnector.py +1 -0
  15. sqlalchemy/dialects/mysql/mysqlconnector.py +3 -2
  16. sqlalchemy/dialects/mysql/mysqldb.py +1 -0
  17. sqlalchemy/dialects/mysql/pymysql.py +30 -9
  18. sqlalchemy/dialects/mysql/pyodbc.py +1 -0
  19. sqlalchemy/dialects/mysql/reflection.py +2 -2
  20. sqlalchemy/dialects/oracle/cx_oracle.py +1 -0
  21. sqlalchemy/dialects/oracle/oracledb.py +1 -0
  22. sqlalchemy/dialects/oracle/vector.py +2 -4
  23. sqlalchemy/dialects/postgresql/__init__.py +0 -1
  24. sqlalchemy/dialects/postgresql/_psycopg_common.py +36 -0
  25. sqlalchemy/dialects/postgresql/asyncpg.py +4 -1
  26. sqlalchemy/dialects/postgresql/base.py +53 -3
  27. sqlalchemy/dialects/postgresql/dml.py +0 -1
  28. sqlalchemy/dialects/postgresql/ext.py +6 -0
  29. sqlalchemy/dialects/postgresql/hstore.py +2 -3
  30. sqlalchemy/dialects/postgresql/operators.py +0 -1
  31. sqlalchemy/dialects/postgresql/pg8000.py +3 -5
  32. sqlalchemy/dialects/postgresql/provision.py +3 -2
  33. sqlalchemy/dialects/postgresql/psycopg.py +22 -38
  34. sqlalchemy/dialects/postgresql/psycopg2.py +3 -29
  35. sqlalchemy/dialects/postgresql/psycopg2cffi.py +1 -0
  36. sqlalchemy/dialects/sqlite/aiosqlite.py +1 -0
  37. sqlalchemy/dialects/sqlite/base.py +8 -11
  38. sqlalchemy/dialects/sqlite/pysqlcipher.py +10 -7
  39. sqlalchemy/dialects/sqlite/pysqlite.py +1 -0
  40. sqlalchemy/engine/_py_processors.py +0 -1
  41. sqlalchemy/engine/base.py +3 -1
  42. sqlalchemy/engine/create.py +1 -1
  43. sqlalchemy/engine/cursor.py +6 -2
  44. sqlalchemy/engine/interfaces.py +1 -1
  45. sqlalchemy/engine/mock.py +0 -1
  46. sqlalchemy/engine/processors.py +1 -0
  47. sqlalchemy/engine/reflection.py +1 -0
  48. sqlalchemy/engine/result.py +27 -0
  49. sqlalchemy/event/api.py +1 -1
  50. sqlalchemy/event/attr.py +1 -0
  51. sqlalchemy/event/base.py +1 -0
  52. sqlalchemy/event/legacy.py +1 -0
  53. sqlalchemy/event/registry.py +1 -0
  54. sqlalchemy/exc.py +1 -0
  55. sqlalchemy/ext/__init__.py +0 -1
  56. sqlalchemy/ext/associationproxy.py +1 -0
  57. sqlalchemy/ext/asyncio/engine.py +1 -1
  58. sqlalchemy/ext/asyncio/session.py +0 -1
  59. sqlalchemy/ext/automap.py +1 -0
  60. sqlalchemy/ext/baked.py +0 -1
  61. sqlalchemy/ext/compiler.py +1 -0
  62. sqlalchemy/ext/declarative/extensions.py +1 -0
  63. sqlalchemy/ext/horizontal_shard.py +1 -0
  64. sqlalchemy/ext/instrumentation.py +1 -1
  65. sqlalchemy/ext/mypy/plugin.py +1 -0
  66. sqlalchemy/ext/orderinglist.py +1 -0
  67. sqlalchemy/ext/serializer.py +0 -1
  68. sqlalchemy/future/__init__.py +1 -0
  69. sqlalchemy/inspection.py +1 -0
  70. sqlalchemy/log.py +2 -2
  71. sqlalchemy/orm/collections.py +1 -0
  72. sqlalchemy/orm/context.py +1 -1
  73. sqlalchemy/orm/decl_api.py +14 -11
  74. sqlalchemy/orm/dependency.py +1 -1
  75. sqlalchemy/orm/descriptor_props.py +1 -1
  76. sqlalchemy/orm/dynamic.py +0 -1
  77. sqlalchemy/orm/evaluator.py +0 -1
  78. sqlalchemy/orm/events.py +1 -0
  79. sqlalchemy/orm/instrumentation.py +0 -1
  80. sqlalchemy/orm/loading.py +2 -2
  81. sqlalchemy/orm/mapper.py +1 -0
  82. sqlalchemy/orm/persistence.py +1 -0
  83. sqlalchemy/orm/properties.py +3 -4
  84. sqlalchemy/orm/query.py +1 -1
  85. sqlalchemy/orm/relationships.py +8 -1
  86. sqlalchemy/orm/scoping.py +2 -3
  87. sqlalchemy/orm/session.py +29 -7
  88. sqlalchemy/orm/strategies.py +1 -1
  89. sqlalchemy/orm/unitofwork.py +2 -3
  90. sqlalchemy/orm/util.py +4 -0
  91. sqlalchemy/pool/impl.py +1 -0
  92. sqlalchemy/sql/_orm_types.py +0 -1
  93. sqlalchemy/sql/_py_util.py +1 -1
  94. sqlalchemy/sql/base.py +0 -1
  95. sqlalchemy/sql/compiler.py +3 -1
  96. sqlalchemy/sql/crud.py +2 -1
  97. sqlalchemy/sql/ddl.py +1 -0
  98. sqlalchemy/sql/dml.py +1 -0
  99. sqlalchemy/sql/elements.py +7 -4
  100. sqlalchemy/sql/expression.py +0 -1
  101. sqlalchemy/sql/lambdas.py +2 -3
  102. sqlalchemy/sql/schema.py +3 -4
  103. sqlalchemy/sql/selectable.py +13 -28
  104. sqlalchemy/sql/sqltypes.py +1 -1
  105. sqlalchemy/sql/traversals.py +0 -1
  106. sqlalchemy/sql/util.py +2 -1
  107. sqlalchemy/testing/engines.py +0 -1
  108. sqlalchemy/testing/fixtures/base.py +10 -5
  109. sqlalchemy/testing/fixtures/mypy.py +4 -8
  110. sqlalchemy/testing/plugin/bootstrap.py +0 -1
  111. sqlalchemy/testing/plugin/pytestplugin.py +2 -5
  112. sqlalchemy/testing/profiling.py +0 -1
  113. sqlalchemy/testing/provision.py +0 -1
  114. sqlalchemy/testing/suite/test_reflection.py +0 -1
  115. sqlalchemy/testing/util.py +0 -1
  116. sqlalchemy/types.py +0 -1
  117. sqlalchemy/util/_collections.py +1 -1
  118. sqlalchemy/util/compat.py +1 -2
  119. sqlalchemy/util/langhelpers.py +17 -14
  120. sqlalchemy/util/preloaded.py +1 -0
  121. sqlalchemy/util/queue.py +1 -1
  122. sqlalchemy/util/tool_support.py +1 -0
  123. {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/METADATA +1 -1
  124. {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/RECORD +127 -127
  125. {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/WHEEL +0 -0
  126. {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.dist-info}/licenses/LICENSE +0 -0
  127. {sqlalchemy-2.0.49.dist-info → sqlalchemy-2.0.51.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.49"
272
+ __version__ = "2.0.51"
273
273
 
274
274
 
275
275
  def __go(lcls: Any) -> None:
@@ -20,7 +20,6 @@ from .. import util
20
20
  from ..util.concurrency import await_fallback
21
21
  from ..util.concurrency import await_only
22
22
 
23
-
24
23
  if TYPE_CHECKING:
25
24
  from ..engine.interfaces import ConnectArgsType
26
25
  from ..engine.url import URL
@@ -18,7 +18,6 @@ from ..sql.schema import Column
18
18
  from ..sql.schema import ColumnCollectionConstraint
19
19
  from ..sql.schema import Index
20
20
 
21
-
22
21
  _OnConflictConstraintT = Union[str, ColumnCollectionConstraint, Index, None]
23
22
  _OnConflictIndexElementsT = Optional[
24
23
  Iterable[Union[Column[Any], str, roles.DDLConstraintColumnRole]]
@@ -45,7 +45,6 @@ from .base import VARCHAR
45
45
  from .base import XML
46
46
  from ...sql import try_cast
47
47
 
48
-
49
48
  base.dialect = dialect = pyodbc.dialect
50
49
 
51
50
 
@@ -3220,8 +3220,7 @@ class MSDialect(default.DefaultDialect):
3220
3220
 
3221
3221
  view_name = f"sys.{row[0]}"
3222
3222
 
3223
- cursor.execute(
3224
- """
3223
+ cursor.execute("""
3225
3224
  SELECT CASE transaction_isolation_level
3226
3225
  WHEN 0 THEN NULL
3227
3226
  WHEN 1 THEN 'READ UNCOMMITTED'
@@ -3232,10 +3231,7 @@ class MSDialect(default.DefaultDialect):
3232
3231
  AS TRANSACTION_ISOLATION_LEVEL
3233
3232
  FROM {}
3234
3233
  where session_id = @@SPID
3235
- """.format(
3236
- view_name
3237
- )
3238
- )
3234
+ """.format(view_name))
3239
3235
  except self.dbapi.Error as err:
3240
3236
  raise NotImplementedError(
3241
3237
  "Can't fetch isolation level; encountered error {} when "
@@ -3432,8 +3428,7 @@ class MSDialect(default.DefaultDialect):
3432
3428
  else "NULL as filter_definition"
3433
3429
  )
3434
3430
  rp = connection.execution_options(future_result=True).execute(
3435
- sql.text(
3436
- f"""
3431
+ sql.text(f"""
3437
3432
  select
3438
3433
  ind.index_id,
3439
3434
  ind.is_unique,
@@ -3453,8 +3448,7 @@ where
3453
3448
  and ind.type != 0
3454
3449
  order by
3455
3450
  ind.name
3456
- """
3457
- )
3451
+ """)
3458
3452
  .bindparams(
3459
3453
  sql.bindparam("tabname", tablename, ischema.CoerceUnicode()),
3460
3454
  sql.bindparam("schname", owner, ischema.CoerceUnicode()),
@@ -3482,8 +3476,7 @@ order by
3482
3476
  do["mssql_where"] = row["filter_definition"]
3483
3477
 
3484
3478
  rp = connection.execution_options(future_result=True).execute(
3485
- sql.text(
3486
- """
3479
+ sql.text("""
3487
3480
  select
3488
3481
  ind_col.index_id,
3489
3482
  col.name,
@@ -3503,8 +3496,7 @@ where
3503
3496
  order by
3504
3497
  ind_col.index_id,
3505
3498
  ind_col.key_ordinal
3506
- """
3507
- )
3499
+ """)
3508
3500
  .bindparams(
3509
3501
  sql.bindparam("tabname", tablename, ischema.CoerceUnicode()),
3510
3502
  sql.bindparam("schname", owner, ischema.CoerceUnicode()),
@@ -3930,8 +3922,7 @@ order by
3930
3922
  ):
3931
3923
  # Foreign key constraints
3932
3924
  s = (
3933
- text(
3934
- """\
3925
+ text("""\
3935
3926
  WITH fk_info AS (
3936
3927
  SELECT
3937
3928
  ischema_ref_con.constraint_schema,
@@ -4031,8 +4022,7 @@ index_info AS (
4031
4022
 
4032
4023
  ORDER BY fk_info.constraint_schema, fk_info.constraint_name,
4033
4024
  fk_info.ordinal_position
4034
- """
4035
- )
4025
+ """)
4036
4026
  .bindparams(
4037
4027
  sql.bindparam("tablename", tablename, ischema.CoerceUnicode()),
4038
4028
  sql.bindparam("owner", owner, ischema.CoerceUnicode()),
@@ -20,7 +20,6 @@ from ...types import String
20
20
  from ...types import TypeDecorator
21
21
  from ...types import Unicode
22
22
 
23
-
24
23
  ischema = MetaData()
25
24
 
26
25
 
@@ -22,6 +22,7 @@ pymssql is a Python module that provides a Python DBAPI interface around
22
22
 
23
23
 
24
24
  """ # noqa
25
+
25
26
  import re
26
27
 
27
28
  from .base import MSDialect
@@ -359,7 +359,6 @@ hook. See that method for usage examples.
359
359
 
360
360
  """ # noqa
361
361
 
362
-
363
362
  import datetime
364
363
  import decimal
365
364
  import re
@@ -28,6 +28,7 @@ This dialect should normally be used only with the
28
28
  )
29
29
 
30
30
  """ # noqa
31
+
31
32
  from __future__ import annotations
32
33
 
33
34
  from types import ModuleType
@@ -38,6 +39,7 @@ from typing import Tuple
38
39
  from typing import TYPE_CHECKING
39
40
  from typing import Union
40
41
 
42
+ from .pymysql import _connection_ping_reconnects_true
41
43
  from .pymysql import MySQLDialect_pymysql
42
44
  from ... import pool
43
45
  from ... import util
@@ -46,6 +48,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
46
48
  from ...connectors.asyncio import AsyncAdapt_dbapi_module
47
49
  from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
48
50
  from ...connectors.asyncio import AsyncAdapt_terminate
51
+ from ...util import langhelpers
49
52
  from ...util.concurrency import await_fallback
50
53
  from ...util.concurrency import await_only
51
54
 
@@ -91,9 +94,12 @@ class AsyncAdapt_aiomysql_connection(
91
94
  _cursor_cls = AsyncAdapt_aiomysql_cursor
92
95
  _ss_cursor_cls = AsyncAdapt_aiomysql_ss_cursor
93
96
 
94
- def ping(self, reconnect: bool) -> None:
97
+ def ping(self, reconnect: bool = False) -> None:
95
98
  assert not reconnect
96
- self.await_(self._connection.ping(reconnect))
99
+ if self.dbapi._send_false_to_ping:
100
+ self.await_(self._connection.ping(reconnect=False))
101
+ else:
102
+ self.await_(self._connection.ping())
97
103
 
98
104
  def character_set_name(self) -> Optional[str]:
99
105
  return self._connection.character_set_name() # type: ignore[no-any-return] # noqa: E501
@@ -170,6 +176,24 @@ class AsyncAdapt_aiomysql_dbapi(AsyncAdapt_dbapi_module):
170
176
  await_only(creator_fn(*arg, **kw)),
171
177
  )
172
178
 
179
+ @langhelpers.memoized_property
180
+ def _send_false_to_ping(self) -> bool:
181
+ """determine if aiomysql has deprecated, changed the default of,
182
+ or removed the 'reconnect' argument of connection.ping().
183
+
184
+ See #13306 and #10492
185
+
186
+ """ # noqa: E501
187
+
188
+ try:
189
+ Connection = __import__(
190
+ "aiomysql.connection"
191
+ ).connection.Connection
192
+ except (ImportError, AttributeError):
193
+ return True
194
+ else:
195
+ return _connection_ping_reconnects_true(Connection)
196
+
173
197
  def _init_cursors_subclasses(
174
198
  self,
175
199
  ) -> Tuple[AsyncIODBAPICursor, AsyncIODBAPICursor]:
@@ -26,6 +26,7 @@ This dialect should normally be used only with the
26
26
  )
27
27
 
28
28
  """ # noqa
29
+
29
30
  from __future__ import annotations
30
31
 
31
32
  from types import ModuleType
@@ -35,6 +36,7 @@ from typing import Optional
35
36
  from typing import TYPE_CHECKING
36
37
  from typing import Union
37
38
 
39
+ from .pymysql import _connection_ping_reconnects_true
38
40
  from .pymysql import MySQLDialect_pymysql
39
41
  from ... import pool
40
42
  from ... import util
@@ -43,6 +45,7 @@ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
43
45
  from ...connectors.asyncio import AsyncAdapt_dbapi_module
44
46
  from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
45
47
  from ...connectors.asyncio import AsyncAdapt_terminate
48
+ from ...util import langhelpers
46
49
  from ...util.concurrency import await_fallback
47
50
  from ...util.concurrency import await_only
48
51
 
@@ -86,18 +89,21 @@ class AsyncAdapt_asyncmy_connection(
86
89
  if isinstance(error, AttributeError):
87
90
  raise self.dbapi.InternalError(
88
91
  "network operation failed due to asyncmy attribute error"
89
- )
92
+ ) from error
90
93
 
91
94
  raise error
92
95
 
93
- def ping(self, reconnect: bool) -> None:
96
+ def ping(self, reconnect: bool = False) -> None:
94
97
  assert not reconnect
95
98
  return self.await_(self._do_ping())
96
99
 
97
100
  async def _do_ping(self) -> None:
98
101
  try:
99
102
  async with self._execute_mutex:
100
- await self._connection.ping(False)
103
+ if self.dbapi._send_false_to_ping:
104
+ await self._connection.ping(reconnect=False)
105
+ else:
106
+ await self._connection.ping()
101
107
  except Exception as error:
102
108
  self._handle_exception(error)
103
109
 
@@ -171,6 +177,22 @@ class AsyncAdapt_asyncmy_dbapi(AsyncAdapt_dbapi_module):
171
177
  await_only(creator_fn(*arg, **kw)),
172
178
  )
173
179
 
180
+ @langhelpers.memoized_property
181
+ def _send_false_to_ping(self) -> bool:
182
+ """determine if asyncmy has deprecated, changed the default of,
183
+ or removed the 'reconnect' argument of connection.ping().
184
+
185
+ See #13306 and #10492
186
+
187
+ """ # noqa: E501
188
+
189
+ try:
190
+ Connection = __import__("asyncmy.connection").connection.Connection
191
+ except (ImportError, AttributeError):
192
+ return True
193
+ else:
194
+ return _connection_ping_reconnects_true(Connection)
195
+
174
196
 
175
197
  class MySQLDialect_asyncmy(MySQLDialect_pymysql):
176
198
  driver = "asyncmy"
@@ -1063,6 +1063,7 @@ output:
1063
1063
  )
1064
1064
 
1065
1065
  """ # noqa
1066
+
1066
1067
  from __future__ import annotations
1067
1068
 
1068
1069
  from collections import defaultdict
@@ -2764,6 +2765,7 @@ class MySQLDialect(default.DefaultDialect):
2764
2765
  # i.e. first connect
2765
2766
  _backslash_escapes = True
2766
2767
  _server_ansiquotes = False
2768
+ _casing = 0
2767
2769
 
2768
2770
  server_version_info: Tuple[int, ...]
2769
2771
  identifier_preparer: MySQLIdentifierPreparer
@@ -3186,9 +3188,9 @@ class MySQLDialect(default.DefaultDialect):
3186
3188
  self._is_mysql and self.server_version_info >= (8, 0, 1)
3187
3189
  )
3188
3190
 
3189
- self._needs_correct_for_88718_96365 = (
3190
- not self.is_mariadb and self.server_version_info >= (8,)
3191
- )
3191
+ self._needs_correct_for_88718_96365 = self.server_version_info >= (
3192
+ 8,
3193
+ ) and (self.server_version_info < (8, 0, 14) or self._casing == 2)
3192
3194
 
3193
3195
  self.delete_returning = (
3194
3196
  self.is_mariadb and self.server_version_info >= (10, 0, 5)
@@ -3253,7 +3255,7 @@ class MySQLDialect(default.DefaultDialect):
3253
3255
  def _is_mariadb_102(self) -> bool:
3254
3256
  return (
3255
3257
  self.is_mariadb
3256
- and self._mariadb_normalized_version_info # type:ignore[operator]
3258
+ and self._mariadb_normalized_version_info # type: ignore[operator]
3257
3259
  > (
3258
3260
  10,
3259
3261
  2,
@@ -20,6 +20,7 @@ r"""
20
20
  dialects are mysqlclient and PyMySQL.
21
21
 
22
22
  """ # noqa
23
+
23
24
  from __future__ import annotations
24
25
 
25
26
  from typing import Any
@@ -28,7 +28,6 @@ from ...sql.expression import alias
28
28
  from ...sql.selectable import NamedFromClause
29
29
  from ...util.typing import Self
30
30
 
31
-
32
31
  __all__ = ("Insert", "insert")
33
32
 
34
33
 
@@ -27,6 +27,7 @@ be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver.
27
27
  .. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python
28
28
 
29
29
  """ # noqa
30
+
30
31
  from __future__ import annotations
31
32
 
32
33
  import re
@@ -45,6 +45,7 @@ charset/collation will allow connectivity.
45
45
 
46
46
 
47
47
  """ # noqa
48
+
48
49
  from __future__ import annotations
49
50
 
50
51
  import re
@@ -110,8 +111,8 @@ class IdentifierPreparerCommon_mysqlconnector:
110
111
 
111
112
  def _escape_identifier(self, value: str) -> str:
112
113
  value = value.replace(
113
- self.escape_quote, # type:ignore[attr-defined]
114
- self.escape_to_quote, # type:ignore[attr-defined]
114
+ self.escape_quote, # type: ignore[attr-defined]
115
+ self.escape_to_quote, # type: ignore[attr-defined]
115
116
  )
116
117
  return value
117
118
 
@@ -84,6 +84,7 @@ Server Side Cursors
84
84
  The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
85
85
 
86
86
  """
87
+
87
88
  from __future__ import annotations
88
89
 
89
90
  import re
@@ -47,11 +47,13 @@ and targets 100% compatibility. Most behavioral notes for MySQL-python apply
47
47
  to the pymysql driver as well.
48
48
 
49
49
  """ # noqa
50
+
50
51
  from __future__ import annotations
51
52
 
52
53
  from typing import Any
53
54
  from typing import Dict
54
55
  from typing import Optional
56
+ from typing import Type
55
57
  from typing import TYPE_CHECKING
56
58
  from typing import Union
57
59
 
@@ -69,6 +71,31 @@ if TYPE_CHECKING:
69
71
  from ...engine.url import URL
70
72
 
71
73
 
74
+ def _connection_ping_reconnects_true(connection_cls: Type[Any]) -> bool:
75
+ """Given a Connection class like pymysql.Connection, aiomysql.Connection,
76
+ asyncmy.Connection, inspect the ping() method and determine if it
77
+ has a "reconnect" parameter that either defaults to True, or is positional.
78
+
79
+ a return value of True here means that when we call ``connection.ping()``,
80
+ we **must** pass `reconnect=False`. a return value of False means that
81
+ we should call ``connection.ping()`` with **no arguments**.
82
+
83
+ This routine originates from issue #10492 for pymysql, however arg
84
+ signature mismatches in aiomysql/asyncmy tracked by issue #13306
85
+ necessitated a more open ended function.
86
+
87
+ """
88
+ insp = langhelpers.get_callable_argspec(connection_cls.ping)
89
+ try:
90
+ reconnect_arg = insp.args[1]
91
+ except IndexError:
92
+ return False
93
+ else:
94
+ return reconnect_arg == "reconnect" and (
95
+ not insp.defaults or insp.defaults[0] is not False
96
+ )
97
+
98
+
72
99
  class MySQLDialect_pymysql(MySQLDialect_mysqldb):
73
100
  driver = "pymysql"
74
101
  supports_statement_cache = True
@@ -97,6 +124,8 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
97
124
  https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
98
125
  for background.
99
126
 
127
+ Revised as part of #13306
128
+
100
129
  """ # noqa: E501
101
130
 
102
131
  try:
@@ -106,15 +135,7 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
106
135
  except (ImportError, AttributeError):
107
136
  return True
108
137
  else:
109
- insp = langhelpers.get_callable_argspec(Connection.ping)
110
- try:
111
- reconnect_arg = insp.args[1]
112
- except IndexError:
113
- return False
114
- else:
115
- return reconnect_arg == "reconnect" and (
116
- not insp.defaults or insp.defaults[0] is not False
117
- )
138
+ return _connection_ping_reconnects_true(Connection)
118
139
 
119
140
  def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
120
141
  if self._send_false_to_ping:
@@ -42,6 +42,7 @@ Pass through exact pyodbc connection string::
42
42
  connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
43
43
 
44
44
  """ # noqa
45
+
45
46
  from __future__ import annotations
46
47
 
47
48
  import datetime
@@ -371,7 +371,7 @@ class MySQLTableDefinitionParser:
371
371
 
372
372
  buffer = []
373
373
  for row in columns:
374
- (name, col_type, nullable, default, extra) = (
374
+ name, col_type, nullable, default, extra = (
375
375
  row[i] for i in (0, 1, 2, 4, 5)
376
376
  )
377
377
 
@@ -704,7 +704,7 @@ def cleanup_text(raw_text: str) -> str:
704
704
  if "\\" in raw_text:
705
705
  raw_text = re.sub(
706
706
  _control_char_regexp,
707
- lambda s: _control_char_map[s[0]], # type: ignore[index]
707
+ lambda s: _control_char_map[s[0]], # type: ignore[unused-ignore,index] # noqa: E501
708
708
  raw_text,
709
709
  )
710
710
  return raw_text.replace("''", "'")
@@ -489,6 +489,7 @@ SQLAlchemy type (or a subclass of such).
489
489
  as better integration of outputtypehandlers.
490
490
 
491
491
  """ # noqa
492
+
492
493
  from __future__ import annotations
493
494
 
494
495
  import decimal
@@ -588,6 +588,7 @@ SQLAlchemy type (or a subclass of such).
588
588
  .. versionadded:: 2.0.0 added support for the python-oracledb driver.
589
589
 
590
590
  """ # noqa
591
+
591
592
  from __future__ import annotations
592
593
 
593
594
  import collections
@@ -313,12 +313,10 @@ class VECTOR(types.TypeEngine):
313
313
  )
314
314
 
315
315
  else:
316
- raise TypeError(
317
- """
316
+ raise TypeError("""
318
317
  Invalid input for VECTOR: expected a list, an array.array,
319
318
  or a SparseVector object.
320
- """
321
- )
319
+ """)
322
320
 
323
321
  return process
324
322
 
@@ -89,7 +89,6 @@ from .types import TIMESTAMP
89
89
  from .types import TSQUERY
90
90
  from .types import TSVECTOR
91
91
 
92
-
93
92
  # Alias psycopg also as psycopg_async
94
93
  psycopg_async = type(
95
94
  "psycopg_async", (ModuleType,), {"dialect": psycopg.dialect_async}
@@ -187,3 +187,39 @@ class _PGDialect_common_psycopg(PGDialect):
187
187
  dbapi_connection.autocommit = before_autocommit
188
188
 
189
189
  return True
190
+
191
+ def do_begin_twophase(self, connection, xid):
192
+ connection.connection.tpc_begin(xid)
193
+
194
+ def do_prepare_twophase(self, connection, xid):
195
+ connection.connection.tpc_prepare()
196
+
197
+ def _do_twophase(self, dbapi_conn, operation, xid, recover=False):
198
+ if recover:
199
+ if not self._twophase_idle_check(dbapi_conn):
200
+ dbapi_conn.rollback()
201
+ operation(xid)
202
+ else:
203
+ operation()
204
+
205
+ def _twophase_idle_check(self, dbapi_conn):
206
+ raise NotImplementedError
207
+
208
+ def do_rollback_twophase(
209
+ self, connection, xid, is_prepared=True, recover=False
210
+ ):
211
+ dbapi_conn = connection.connection.dbapi_connection
212
+ self._do_twophase(
213
+ dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover
214
+ )
215
+
216
+ def do_commit_twophase(
217
+ self, connection, xid, is_prepared=True, recover=False
218
+ ):
219
+ dbapi_conn = connection.connection.dbapi_connection
220
+ self._do_twophase(
221
+ dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover
222
+ )
223
+
224
+ def do_recover_twophase(self, connection):
225
+ return [str(row) for row in connection.connection.tpc_recover()]
@@ -990,6 +990,9 @@ class AsyncAdapt_asyncpg_dbapi:
990
990
  class InternalServerError(InternalError):
991
991
  pass
992
992
 
993
+ class InternalClientError(InternalError):
994
+ pass
995
+
993
996
  class InvalidCachedStatementError(NotSupportedError):
994
997
  def __init__(self, message):
995
998
  super().__init__(
@@ -1014,6 +1017,7 @@ class AsyncAdapt_asyncpg_dbapi:
1014
1017
  asyncpg.exceptions.InterfaceError: self.InterfaceError,
1015
1018
  asyncpg.exceptions.InvalidCachedStatementError: self.InvalidCachedStatementError, # noqa: E501
1016
1019
  asyncpg.exceptions.InternalServerError: self.InternalServerError,
1020
+ asyncpg.exceptions.InternalClientError: self.InternalClientError,
1017
1021
  }
1018
1022
 
1019
1023
  def Binary(self, value):
@@ -1212,7 +1216,6 @@ class PGDialect_asyncpg(PGDialect):
1212
1216
  """
1213
1217
 
1214
1218
  asyncpg_connection = conn._connection
1215
- deserializer = self._json_deserializer or _py_json.loads
1216
1219
 
1217
1220
  def _jsonb_encoder(str_value):
1218
1221
  # \x01 is the prefix for jsonb used by PostgreSQL.
@@ -1198,6 +1198,21 @@ constraints, enabling table inheritance hierarchies in PostgreSQL.
1198
1198
 
1199
1199
  Table("some_table", metadata, ..., postgresql_inherits=("t1", "t2", ...))
1200
1200
 
1201
+ For schema-qualified parent table names, use :class:`.quoted_name` with
1202
+ ``quote=False`` to prevent the dotted name from being quoted as a single
1203
+ identifier::
1204
+
1205
+ from sqlalchemy.sql import quoted_name
1206
+
1207
+ Table(
1208
+ "some_table",
1209
+ metadata,
1210
+ ...,
1211
+ postgresql_inherits=quoted_name(
1212
+ "my_schema.some_supertable", quote=False
1213
+ ),
1214
+ )
1215
+
1201
1216
  ``ON COMMIT``
1202
1217
  ^^^^^^^^^^^^^
1203
1218
 
@@ -1390,6 +1405,29 @@ that are subject to the action::
1390
1405
  .. versionadded:: 2.0.40
1391
1406
 
1392
1407
 
1408
+ ``NULLS NOT DISTINCT``
1409
+ ^^^^^^^^^^^^^^^^^^^^^^
1410
+
1411
+ By default, two ``null`` values are not considered equal for unique constraints
1412
+ and indexes. Therefore, seemingly duplicate rows may be stored if one of the
1413
+ values in the constraint is ``null``. This default behavior is implementation
1414
+ defined, so other SQL dialects may behave differently than PostgreSQL.
1415
+
1416
+ The ``NULLS NOT DISTINCT`` clause can be used to change this behavior, treating
1417
+ null values as equal and preventing unintended duplicate rows. The opposite
1418
+ ``NULLS DISTINCT`` clause can also be used to make PostgreSQL's default behavior
1419
+ explict.
1420
+
1421
+ The ``postgresql_nulls_not_distinct`` parameter can be set to ``True`` to
1422
+ add the ``NULLS NOT DISTINCT`` clause, or ``False`` to add ``NULLS DISTINCT``.
1423
+ Not setting it, or passing ``None``, will not add a clause and keep the default
1424
+ behavior.
1425
+
1426
+ This feature requires PostgreSQL 15 or later.
1427
+
1428
+ .. versionadded:: 2.0.16
1429
+
1430
+
1393
1431
  .. _postgresql_table_valued_overview:
1394
1432
 
1395
1433
  Table values, Table and Column valued functions, Row and Tuple objects
@@ -3478,7 +3516,11 @@ class PGDialect(default.DefaultDialect):
3478
3516
  self.do_begin(connection.connection)
3479
3517
 
3480
3518
  def do_prepare_twophase(self, connection, xid):
3481
- connection.exec_driver_sql("PREPARE TRANSACTION '%s'" % xid)
3519
+ connection.execute(
3520
+ sql.text("PREPARE TRANSACTION :xid").bindparams(
3521
+ sql.bindparam("xid", xid, literal_execute=True)
3522
+ )
3523
+ )
3482
3524
 
3483
3525
  def do_rollback_twophase(
3484
3526
  self, connection, xid, is_prepared=True, recover=False
@@ -3490,7 +3532,11 @@ class PGDialect(default.DefaultDialect):
3490
3532
  # Must find out a way how to make the dbapi not
3491
3533
  # open a transaction.
3492
3534
  connection.exec_driver_sql("ROLLBACK")
3493
- connection.exec_driver_sql("ROLLBACK PREPARED '%s'" % xid)
3535
+ connection.execute(
3536
+ sql.text("ROLLBACK PREPARED :xid").bindparams(
3537
+ sql.bindparam("xid", xid, literal_execute=True)
3538
+ )
3539
+ )
3494
3540
  connection.exec_driver_sql("BEGIN")
3495
3541
  self.do_rollback(connection.connection)
3496
3542
  else:
@@ -3502,7 +3548,11 @@ class PGDialect(default.DefaultDialect):
3502
3548
  if is_prepared:
3503
3549
  if recover:
3504
3550
  connection.exec_driver_sql("ROLLBACK")
3505
- connection.exec_driver_sql("COMMIT PREPARED '%s'" % xid)
3551
+ connection.execute(
3552
+ sql.text("COMMIT PREPARED :xid").bindparams(
3553
+ sql.bindparam("xid", xid, literal_execute=True)
3554
+ )
3555
+ )
3506
3556
  connection.exec_driver_sql("BEGIN")
3507
3557
  self.do_rollback(connection.connection)
3508
3558
  else: