SQLAlchemy 2.0.39__py3-none-any.whl → 2.0.41__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 (88) hide show
  1. sqlalchemy/__init__.py +1 -12
  2. sqlalchemy/connectors/pyodbc.py +2 -4
  3. sqlalchemy/dialects/mssql/base.py +3 -6
  4. sqlalchemy/dialects/mysql/base.py +15 -9
  5. sqlalchemy/dialects/mysql/mariadb.py +17 -11
  6. sqlalchemy/dialects/mysql/mysqlconnector.py +92 -19
  7. sqlalchemy/dialects/mysql/provision.py +4 -0
  8. sqlalchemy/dialects/mysql/reflection.py +1 -1
  9. sqlalchemy/dialects/mysql/types.py +4 -5
  10. sqlalchemy/dialects/oracle/__init__.py +10 -0
  11. sqlalchemy/dialects/oracle/base.py +264 -1
  12. sqlalchemy/dialects/oracle/vector.py +266 -0
  13. sqlalchemy/dialects/postgresql/_psycopg_common.py +0 -1
  14. sqlalchemy/dialects/postgresql/array.py +132 -58
  15. sqlalchemy/dialects/postgresql/base.py +211 -77
  16. sqlalchemy/dialects/postgresql/ext.py +44 -9
  17. sqlalchemy/dialects/postgresql/json.py +1 -1
  18. sqlalchemy/dialects/postgresql/named_types.py +44 -11
  19. sqlalchemy/dialects/postgresql/pg_catalog.py +19 -7
  20. sqlalchemy/dialects/sqlite/aiosqlite.py +3 -26
  21. sqlalchemy/dialects/sqlite/base.py +202 -123
  22. sqlalchemy/dialects/sqlite/provision.py +0 -2
  23. sqlalchemy/dialects/sqlite/pysqlite.py +3 -69
  24. sqlalchemy/engine/base.py +10 -11
  25. sqlalchemy/engine/cursor.py +6 -1
  26. sqlalchemy/engine/default.py +28 -15
  27. sqlalchemy/engine/interfaces.py +11 -4
  28. sqlalchemy/engine/mock.py +7 -6
  29. sqlalchemy/engine/reflection.py +4 -1
  30. sqlalchemy/engine/result.py +10 -3
  31. sqlalchemy/engine/url.py +1 -1
  32. sqlalchemy/ext/asyncio/base.py +7 -7
  33. sqlalchemy/ext/asyncio/engine.py +5 -3
  34. sqlalchemy/ext/asyncio/scoping.py +1 -2
  35. sqlalchemy/ext/asyncio/session.py +22 -4
  36. sqlalchemy/orm/clsregistry.py +2 -2
  37. sqlalchemy/orm/context.py +1 -3
  38. sqlalchemy/orm/decl_api.py +1 -1
  39. sqlalchemy/orm/decl_base.py +1 -3
  40. sqlalchemy/orm/dependency.py +1 -1
  41. sqlalchemy/orm/exc.py +9 -0
  42. sqlalchemy/orm/mapper.py +2 -2
  43. sqlalchemy/orm/properties.py +12 -5
  44. sqlalchemy/orm/query.py +5 -6
  45. sqlalchemy/orm/relationships.py +0 -5
  46. sqlalchemy/orm/scoping.py +1 -2
  47. sqlalchemy/orm/session.py +2 -10
  48. sqlalchemy/orm/strategies.py +0 -3
  49. sqlalchemy/orm/strategy_options.py +0 -1
  50. sqlalchemy/orm/util.py +6 -5
  51. sqlalchemy/pool/base.py +1 -1
  52. sqlalchemy/schema.py +1 -0
  53. sqlalchemy/sql/_elements_constructors.py +14 -4
  54. sqlalchemy/sql/_selectable_constructors.py +2 -0
  55. sqlalchemy/sql/_typing.py +5 -0
  56. sqlalchemy/sql/base.py +19 -7
  57. sqlalchemy/sql/cache_key.py +1 -1
  58. sqlalchemy/sql/coercions.py +1 -1
  59. sqlalchemy/sql/compiler.py +238 -132
  60. sqlalchemy/sql/crud.py +13 -4
  61. sqlalchemy/sql/ddl.py +51 -47
  62. sqlalchemy/sql/elements.py +24 -17
  63. sqlalchemy/sql/functions.py +94 -52
  64. sqlalchemy/sql/lambdas.py +2 -5
  65. sqlalchemy/sql/schema.py +26 -9
  66. sqlalchemy/sql/selectable.py +15 -5
  67. sqlalchemy/sql/sqltypes.py +119 -42
  68. sqlalchemy/sql/type_api.py +4 -0
  69. sqlalchemy/sql/util.py +1 -1
  70. sqlalchemy/testing/assertions.py +2 -2
  71. sqlalchemy/testing/fixtures/mypy.py +25 -5
  72. sqlalchemy/testing/plugin/pytestplugin.py +0 -1
  73. sqlalchemy/testing/requirements.py +59 -0
  74. sqlalchemy/testing/suite/test_dialect.py +2 -2
  75. sqlalchemy/testing/suite/test_reflection.py +49 -3
  76. sqlalchemy/testing/suite/test_results.py +2 -0
  77. sqlalchemy/testing/suite/test_select.py +1 -1
  78. sqlalchemy/util/__init__.py +1 -1
  79. sqlalchemy/util/_collections.py +3 -1
  80. sqlalchemy/util/compat.py +2 -0
  81. sqlalchemy/util/langhelpers.py +112 -31
  82. sqlalchemy/util/topological.py +1 -1
  83. sqlalchemy/util/typing.py +71 -27
  84. {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.41.dist-info}/METADATA +10 -10
  85. {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.41.dist-info}/RECORD +88 -87
  86. {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.41.dist-info}/WHEEL +1 -1
  87. {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.41.dist-info/licenses}/LICENSE +0 -0
  88. {sqlalchemy-2.0.39.dist-info → sqlalchemy-2.0.41.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.39"
272
+ __version__ = "2.0.41"
273
273
 
274
274
 
275
275
  def __go(lcls: Any) -> None:
@@ -281,14 +281,3 @@ def __go(lcls: Any) -> None:
281
281
 
282
282
 
283
283
  __go(locals())
284
-
285
-
286
- def __getattr__(name: str) -> Any:
287
- if name == "SingleonThreadPool":
288
- _util.warn_deprecated(
289
- "SingleonThreadPool was a typo in the v2 series. "
290
- "Please use the correct SingletonThreadPool name.",
291
- "2.0.24",
292
- )
293
- return SingletonThreadPool
294
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -227,11 +227,9 @@ class PyODBCConnector(Connector):
227
227
  )
228
228
 
229
229
  def get_isolation_level_values(
230
- self, dbapi_connection: interfaces.DBAPIConnection
230
+ self, dbapi_conn: interfaces.DBAPIConnection
231
231
  ) -> List[IsolationLevel]:
232
- return super().get_isolation_level_values(dbapi_connection) + [
233
- "AUTOCOMMIT"
234
- ]
232
+ return [*super().get_isolation_level_values(dbapi_conn), "AUTOCOMMIT"]
235
233
 
236
234
  def set_isolation_level(
237
235
  self,
@@ -3991,10 +3991,8 @@ index_info AS (
3991
3991
  )
3992
3992
 
3993
3993
  # group rows by constraint ID, to handle multi-column FKs
3994
- fkeys = []
3995
-
3996
- def fkey_rec():
3997
- return {
3994
+ fkeys = util.defaultdict(
3995
+ lambda: {
3998
3996
  "name": None,
3999
3997
  "constrained_columns": [],
4000
3998
  "referred_schema": None,
@@ -4002,8 +4000,7 @@ index_info AS (
4002
4000
  "referred_columns": [],
4003
4001
  "options": {},
4004
4002
  }
4005
-
4006
- fkeys = util.defaultdict(fkey_rec)
4003
+ )
4007
4004
 
4008
4005
  for r in connection.execute(s).all():
4009
4006
  (
@@ -1928,12 +1928,13 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
1928
1928
  colspec.append("AUTO_INCREMENT")
1929
1929
  else:
1930
1930
  default = self.get_column_default_string(column)
1931
+
1931
1932
  if default is not None:
1932
1933
  if (
1933
- isinstance(
1934
- column.server_default.arg, functions.FunctionElement
1935
- )
1936
- and self.dialect._support_default_function
1934
+ self.dialect._support_default_function
1935
+ and not re.match(r"^\s*[\'\"\(]", default)
1936
+ and not re.search(r"ON +UPDATE", default, re.I)
1937
+ and re.match(r".*\W.*", default)
1937
1938
  ):
1938
1939
  colspec.append(f"DEFAULT ({default})")
1939
1940
  else:
@@ -2520,6 +2521,10 @@ class MySQLDialect(default.DefaultDialect):
2520
2521
  # allow for the "true" and "false" keywords, however
2521
2522
  supports_native_boolean = False
2522
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
+
2523
2528
  # identifiers are 64, however aliases can be 255...
2524
2529
  max_identifier_length = 255
2525
2530
  max_index_name_length = 64
@@ -2721,10 +2726,12 @@ class MySQLDialect(default.DefaultDialect):
2721
2726
  % (".".join(map(str, server_version_info)),)
2722
2727
  )
2723
2728
  if is_mariadb:
2724
- self.preparer = MariaDBIdentifierPreparer
2725
- # this would have been set by the default dialect already,
2726
- # so set it again
2727
- self.identifier_preparer = self.preparer(self)
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)
2728
2735
 
2729
2736
  # this will be updated on first connect in initialize()
2730
2737
  # if using older mariadb version
@@ -3471,7 +3478,6 @@ class MySQLDialect(default.DefaultDialect):
3471
3478
  full_name = self.identifier_preparer.format_table(table)
3472
3479
  st = "SHOW CREATE TABLE %s" % full_name
3473
3480
 
3474
- rp = None
3475
3481
  try:
3476
3482
  rp = connection.execution_options(
3477
3483
  skip_user_error_events=True
@@ -46,16 +46,22 @@ class MariaDBDialect(MySQLDialect):
46
46
 
47
47
 
48
48
  def loader(driver):
49
- driver_mod = __import__(
49
+ dialect_mod = __import__(
50
50
  "sqlalchemy.dialects.mysql.%s" % driver
51
51
  ).dialects.mysql
52
- driver_cls = getattr(driver_mod, driver).dialect
53
-
54
- return type(
55
- "MariaDBDialect_%s" % driver,
56
- (
57
- MariaDBDialect,
58
- driver_cls,
59
- ),
60
- {"supports_statement_cache": True},
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,59 @@ r"""
14
14
  :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
15
15
  :url: https://pypi.org/project/mysql-connector-python/
16
16
 
17
- .. note::
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
+ .. warning:: The MySQL Connector/Python driver published by Oracle is subject
26
+ to frequent, major regressions of essential functionality such as being able
27
+ to correctly persist simple binary strings which indicate it is not well
28
+ tested. The SQLAlchemy project is not able to maintain this dialect fully as
29
+ regressions in the driver prevent it from being included in continuous
30
+ integration.
31
+
32
+ .. versionchanged:: 2.0.39
33
+
34
+ The MySQL Connector/Python dialect has been updated to support the
35
+ latest version of this DBAPI. Previously, MySQL Connector/Python
36
+ was not fully supported. However, support remains limited due to ongoing
37
+ regressions introduced in this driver.
38
+
39
+ Connecting to MariaDB with MySQL Connector/Python
40
+ --------------------------------------------------
41
+
42
+ MySQL Connector/Python may attempt to pass an incompatible collation to the
43
+ database when connecting to MariaDB. Experimentation has shown that using
44
+ ``?charset=utf8mb4&collation=utfmb4_general_ci`` or similar MariaDB-compatible
45
+ charset/collation will allow connectivity.
18
46
 
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
47
 
24
48
  """ # noqa
25
49
 
26
50
  import re
27
51
 
28
52
  from .base import BIT
53
+ from .base import MariaDBIdentifierPreparer
29
54
  from .base import MySQLCompiler
30
55
  from .base import MySQLDialect
56
+ from .base import MySQLExecutionContext
31
57
  from .base import MySQLIdentifierPreparer
58
+ from .mariadb import MariaDBDialect
32
59
  from ... import util
33
60
 
34
61
 
62
+ class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
63
+ def create_server_side_cursor(self):
64
+ return self._dbapi_connection.cursor(buffered=False)
65
+
66
+ def create_default_cursor(self):
67
+ return self._dbapi_connection.cursor(buffered=True)
68
+
69
+
35
70
  class MySQLCompiler_mysqlconnector(MySQLCompiler):
36
71
  def visit_mod_binary(self, binary, operator, **kw):
37
72
  return (
@@ -41,7 +76,7 @@ class MySQLCompiler_mysqlconnector(MySQLCompiler):
41
76
  )
42
77
 
43
78
 
44
- class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
79
+ class IdentifierPreparerCommon_mysqlconnector:
45
80
  @property
46
81
  def _double_percents(self):
47
82
  return False
@@ -55,6 +90,18 @@ class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
55
90
  return value
56
91
 
57
92
 
93
+ class MySQLIdentifierPreparer_mysqlconnector(
94
+ IdentifierPreparerCommon_mysqlconnector, MySQLIdentifierPreparer
95
+ ):
96
+ pass
97
+
98
+
99
+ class MariaDBIdentifierPreparer_mysqlconnector(
100
+ IdentifierPreparerCommon_mysqlconnector, MariaDBIdentifierPreparer
101
+ ):
102
+ pass
103
+
104
+
58
105
  class _myconnpyBIT(BIT):
59
106
  def result_processor(self, dialect, coltype):
60
107
  """MySQL-connector already converts mysql bits, so."""
@@ -71,9 +118,16 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
71
118
 
72
119
  supports_native_decimal = True
73
120
 
121
+ supports_native_bit = True
122
+
123
+ # not until https://bugs.mysql.com/bug.php?id=117548
124
+ supports_server_side_cursors = False
125
+
74
126
  default_paramstyle = "format"
75
127
  statement_compiler = MySQLCompiler_mysqlconnector
76
128
 
129
+ execution_ctx_cls = MySQLExecutionContext_mysqlconnector
130
+
77
131
  preparer = MySQLIdentifierPreparer_mysqlconnector
78
132
 
79
133
  colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
@@ -111,9 +165,13 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
111
165
  util.coerce_kw_type(opts, "use_pure", bool)
112
166
  util.coerce_kw_type(opts, "use_unicode", bool)
113
167
 
114
- # unfortunately, MySQL/connector python refuses to release a
115
- # cursor without reading fully, so non-buffered isn't an option
116
- opts.setdefault("buffered", True)
168
+ # note that "buffered" is set to False by default in MySQL/connector
169
+ # python. If you set it to True, then there is no way to get a server
170
+ # side cursor because the logic is written to disallow that.
171
+
172
+ # leaving this at True until
173
+ # https://bugs.mysql.com/bug.php?id=117548 can be fixed
174
+ opts["buffered"] = True
117
175
 
118
176
  # FOUND_ROWS must be set in ClientFlag to enable
119
177
  # supports_sane_rowcount.
@@ -128,6 +186,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
128
186
  opts["client_flags"] = client_flags
129
187
  except Exception:
130
188
  pass
189
+
131
190
  return [[], opts]
132
191
 
133
192
  @util.memoized_property
@@ -145,7 +204,11 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
145
204
 
146
205
  def is_disconnect(self, e, connection, cursor):
147
206
  errnos = (2006, 2013, 2014, 2045, 2055, 2048)
148
- exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError)
207
+ exceptions = (
208
+ self.dbapi.OperationalError,
209
+ self.dbapi.InterfaceError,
210
+ self.dbapi.ProgrammingError,
211
+ )
149
212
  if isinstance(e, exceptions):
150
213
  return (
151
214
  e.errno in errnos
@@ -161,20 +224,30 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
161
224
  def _compat_fetchone(self, rp, charset=None):
162
225
  return rp.fetchone()
163
226
 
164
- _isolation_lookup = {
165
- "SERIALIZABLE",
166
- "READ UNCOMMITTED",
167
- "READ COMMITTED",
168
- "REPEATABLE READ",
169
- "AUTOCOMMIT",
170
- }
227
+ def get_isolation_level_values(self, dbapi_connection):
228
+ return (
229
+ "SERIALIZABLE",
230
+ "READ UNCOMMITTED",
231
+ "READ COMMITTED",
232
+ "REPEATABLE READ",
233
+ "AUTOCOMMIT",
234
+ )
171
235
 
172
- def _set_isolation_level(self, connection, level):
236
+ def set_isolation_level(self, connection, level):
173
237
  if level == "AUTOCOMMIT":
174
238
  connection.autocommit = True
175
239
  else:
176
240
  connection.autocommit = False
177
- super()._set_isolation_level(connection, level)
241
+ super().set_isolation_level(connection, level)
242
+
243
+
244
+ class MariaDBDialect_mysqlconnector(
245
+ MariaDBDialect, MySQLDialect_mysqlconnector
246
+ ):
247
+ supports_statement_cache = True
248
+ _allows_uuid_binds = False
249
+ preparer = MariaDBIdentifierPreparer_mysqlconnector
178
250
 
179
251
 
180
252
  dialect = MySQLDialect_mysqlconnector
253
+ 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 long.
377
+ """Convert a MySQL's 64 bit, variable length binary string to a
378
+ long."""
378
379
 
379
- TODO: this is MySQL-db, pyodbc specific. OurSQL and mysqlconnector
380
- already do this, so this logic should be moved to those dialects.
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:
@@ -32,6 +32,11 @@ from .base import ROWID
32
32
  from .base import TIMESTAMP
33
33
  from .base import VARCHAR
34
34
  from .base import VARCHAR2
35
+ from .base import VECTOR
36
+ from .base import VectorIndexConfig
37
+ from .base import VectorIndexType
38
+ from .vector import VectorDistanceType
39
+ from .vector import VectorStorageFormat
35
40
 
36
41
  # Alias oracledb also as oracledb_async
37
42
  oracledb_async = type(
@@ -64,4 +69,9 @@ __all__ = (
64
69
  "NVARCHAR2",
65
70
  "ROWID",
66
71
  "REAL",
72
+ "VECTOR",
73
+ "VectorDistanceType",
74
+ "VectorIndexType",
75
+ "VectorIndexConfig",
76
+ "VectorStorageFormat",
67
77
  )