SQLAlchemy 2.0.42__py3-none-any.whl → 2.0.43__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. sqlalchemy/__init__.py +1 -1
  2. sqlalchemy/connectors/pyodbc.py +5 -0
  3. sqlalchemy/dialects/mysql/aiomysql.py +3 -0
  4. sqlalchemy/dialects/mysql/asyncmy.py +3 -0
  5. sqlalchemy/dialects/mysql/mariadbconnector.py +3 -0
  6. sqlalchemy/dialects/mysql/mysqlconnector.py +3 -0
  7. sqlalchemy/dialects/mysql/mysqldb.py +3 -0
  8. sqlalchemy/dialects/oracle/__init__.py +4 -0
  9. sqlalchemy/dialects/oracle/base.py +79 -24
  10. sqlalchemy/dialects/oracle/cx_oracle.py +3 -0
  11. sqlalchemy/dialects/oracle/vector.py +104 -6
  12. sqlalchemy/dialects/postgresql/_psycopg_common.py +3 -0
  13. sqlalchemy/dialects/postgresql/asyncpg.py +3 -0
  14. sqlalchemy/dialects/postgresql/pg8000.py +3 -0
  15. sqlalchemy/dialects/sqlite/base.py +8 -0
  16. sqlalchemy/dialects/sqlite/pysqlite.py +3 -0
  17. sqlalchemy/engine/base.py +11 -5
  18. sqlalchemy/engine/create.py +15 -0
  19. sqlalchemy/engine/default.py +7 -0
  20. sqlalchemy/engine/interfaces.py +32 -0
  21. sqlalchemy/engine/result.py +1 -1
  22. sqlalchemy/orm/descriptor_props.py +5 -1
  23. sqlalchemy/orm/loading.py +13 -9
  24. sqlalchemy/orm/persistence.py +8 -2
  25. sqlalchemy/orm/properties.py +38 -15
  26. sqlalchemy/sql/functions.py +1 -3
  27. sqlalchemy/testing/requirements.py +7 -0
  28. sqlalchemy/testing/suite/test_dialect.py +37 -1
  29. {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.43.dist-info}/METADATA +1 -1
  30. {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.43.dist-info}/RECORD +33 -33
  31. {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.43.dist-info}/WHEEL +0 -0
  32. {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.43.dist-info}/licenses/LICENSE +0 -0
  33. {sqlalchemy-2.0.42.dist-info → sqlalchemy-2.0.43.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.42"
272
+ __version__ = "2.0.43"
273
273
 
274
274
 
275
275
  def __go(lcls: Any) -> None:
@@ -243,3 +243,8 @@ class PyODBCConnector(Connector):
243
243
  else:
244
244
  dbapi_connection.autocommit = False
245
245
  super().set_isolation_level(dbapi_connection, level)
246
+
247
+ def detect_autocommit_setting(
248
+ self, dbapi_conn: interfaces.DBAPIConnection
249
+ ) -> bool:
250
+ return bool(dbapi_conn.autocommit)
@@ -98,6 +98,9 @@ class AsyncAdapt_aiomysql_connection(AsyncAdapt_dbapi_connection):
98
98
  def autocommit(self, value: Any) -> None:
99
99
  self.await_(self._connection.autocommit(value))
100
100
 
101
+ def get_autocommit(self) -> bool:
102
+ return self._connection.get_autocommit() # type: ignore
103
+
101
104
  def terminate(self) -> None:
102
105
  # it's not awaitable.
103
106
  self._connection.close()
@@ -104,6 +104,9 @@ class AsyncAdapt_asyncmy_connection(AsyncAdapt_dbapi_connection):
104
104
  def autocommit(self, value: Any) -> None:
105
105
  self.await_(self._connection.autocommit(value))
106
106
 
107
+ def get_autocommit(self) -> bool:
108
+ return self._connection.get_autocommit() # type: ignore
109
+
107
110
  def terminate(self) -> None:
108
111
  # it's not awaitable.
109
112
  self._connection.close()
@@ -253,6 +253,9 @@ class MySQLDialect_mariadbconnector(MySQLDialect):
253
253
  "AUTOCOMMIT",
254
254
  )
255
255
 
256
+ def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
257
+ return bool(dbapi_conn.autocommit)
258
+
256
259
  def set_isolation_level(
257
260
  self, dbapi_connection: DBAPIConnection, level: IsolationLevel
258
261
  ) -> None:
@@ -277,6 +277,9 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
277
277
  "AUTOCOMMIT",
278
278
  )
279
279
 
280
+ def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
281
+ return bool(dbapi_conn.autocommit)
282
+
280
283
  def set_isolation_level(
281
284
  self, dbapi_connection: DBAPIConnection, level: IsolationLevel
282
285
  ) -> None:
@@ -298,6 +298,9 @@ class MySQLDialect_mysqldb(MySQLDialect):
298
298
  "AUTOCOMMIT",
299
299
  )
300
300
 
301
+ def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
302
+ return dbapi_conn.get_autocommit() # type: ignore[no-any-return]
303
+
301
304
  def set_isolation_level(
302
305
  self, dbapi_connection: DBAPIConnection, level: IsolationLevel
303
306
  ) -> None:
@@ -35,8 +35,10 @@ from .base import VARCHAR2
35
35
  from .base import VECTOR
36
36
  from .base import VectorIndexConfig
37
37
  from .base import VectorIndexType
38
+ from .vector import SparseVector
38
39
  from .vector import VectorDistanceType
39
40
  from .vector import VectorStorageFormat
41
+ from .vector import VectorStorageType
40
42
 
41
43
  # Alias oracledb also as oracledb_async
42
44
  oracledb_async = type(
@@ -74,4 +76,6 @@ __all__ = (
74
76
  "VectorIndexType",
75
77
  "VectorIndexConfig",
76
78
  "VectorStorageFormat",
79
+ "VectorStorageType",
80
+ "SparseVector",
77
81
  )
@@ -729,8 +729,22 @@ VECTOR Datatype
729
729
 
730
730
  Oracle Database 23ai introduced a new VECTOR datatype for artificial intelligence
731
731
  and machine learning search operations. The VECTOR datatype is a homogeneous array
732
- of 8-bit signed integers, 8-bit unsigned integers (binary), 32-bit floating-point numbers,
733
- or 64-bit floating-point numbers.
732
+ of 8-bit signed integers, 8-bit unsigned integers (binary), 32-bit floating-point
733
+ numbers, or 64-bit floating-point numbers.
734
+
735
+ A vector's storage type can be either DENSE or SPARSE. A dense vector contains
736
+ meaningful values in most or all of its dimensions. In contrast, a sparse vector
737
+ has non-zero values in only a few dimensions, with the majority being zero.
738
+
739
+ Sparse vectors are represented by the total number of vector dimensions, an array
740
+ of indices, and an array of values where each value’s location in the vector is
741
+ indicated by the corresponding indices array position. All other vector values are
742
+ treated as zero.
743
+
744
+ The storage formats that can be used with sparse vectors are float32, float64, and
745
+ int8. Note that the binary storage format cannot be used with sparse vectors.
746
+
747
+ Sparse vectors are supported when you are using Oracle Database 23.7 or later.
734
748
 
735
749
  .. seealso::
736
750
 
@@ -738,17 +752,26 @@ or 64-bit floating-point numbers.
738
752
  <https://python-oracledb.readthedocs.io/en/latest/user_guide/vector_data_type.html>`_ - in the documentation
739
753
  for the :ref:`oracledb` driver.
740
754
 
741
- .. versionadded:: 2.0.41
755
+ .. versionadded:: 2.0.41 - Added VECTOR datatype
756
+
757
+ .. versionadded:: 2.0.43 - Added DENSE/SPARSE support
742
758
 
743
759
  CREATE TABLE support for VECTOR
744
760
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
745
761
 
746
- With the :class:`.VECTOR` datatype, you can specify the dimension for the data
747
- and the storage format. Valid values for storage format are enum values from
748
- :class:`.VectorStorageFormat`. To create a table that includes a
749
- :class:`.VECTOR` column::
762
+ With the :class:`.VECTOR` datatype, you can specify the number of dimensions,
763
+ the storage format, and the storage type for the data. Valid values for the
764
+ storage format are enum members of :class:`.VectorStorageFormat`. Valid values
765
+ for the storage type are enum members of :class:`.VectorStorageType`. If
766
+ storage type is not specified, a DENSE vector is created by default.
767
+
768
+ To create a table that includes a :class:`.VECTOR` column::
750
769
 
751
- from sqlalchemy.dialects.oracle import VECTOR, VectorStorageFormat
770
+ from sqlalchemy.dialects.oracle import (
771
+ VECTOR,
772
+ VectorStorageFormat,
773
+ VectorStorageType,
774
+ )
752
775
 
753
776
  t = Table(
754
777
  "t1",
@@ -756,7 +779,11 @@ and the storage format. Valid values for storage format are enum values from
756
779
  Column("id", Integer, primary_key=True),
757
780
  Column(
758
781
  "embedding",
759
- VECTOR(dim=3, storage_format=VectorStorageFormat.FLOAT32),
782
+ VECTOR(
783
+ dim=3,
784
+ storage_format=VectorStorageFormat.FLOAT32,
785
+ storage_type=VectorStorageType.SPARSE,
786
+ ),
760
787
  ),
761
788
  Column(...),
762
789
  ...,
@@ -764,31 +791,40 @@ and the storage format. Valid values for storage format are enum values from
764
791
 
765
792
  Vectors can also be defined with an arbitrary number of dimensions and formats.
766
793
  This allows you to specify vectors of different dimensions with the various
767
- storage formats mentioned above.
794
+ storage formats mentioned below.
768
795
 
769
796
  **Examples**
770
797
 
771
- * In this case, the storage format is flexible, allowing any vector type data to be inserted,
772
- such as INT8 or BINARY etc::
798
+ * In this case, the storage format is flexible, allowing any vector type data to be
799
+ inserted, such as INT8 or BINARY etc::
773
800
 
774
801
  vector_col: Mapped[array.array] = mapped_column(VECTOR(dim=3))
775
802
 
776
- * The dimension is flexible in this case, meaning that any dimension vector can be used::
803
+ * The dimension is flexible in this case, meaning that any dimension vector can
804
+ be used::
777
805
 
778
806
  vector_col: Mapped[array.array] = mapped_column(
779
807
  VECTOR(storage_format=VectorStorageType.INT8)
780
808
  )
781
809
 
782
- * Both the dimensions and the storage format are flexible::
810
+ * Both the dimensions and the storage format are flexible. It creates a DENSE vector::
783
811
 
784
812
  vector_col: Mapped[array.array] = mapped_column(VECTOR)
785
813
 
814
+ * To create a SPARSE vector with both dimensions and the storage format as flexible,
815
+ use the :attr:`.VectorStorageType.SPARSE` storage type::
816
+
817
+ vector_col: Mapped[array.array] = mapped_column(
818
+ VECTOR(storage_type=VectorStorageType.SPARSE)
819
+ )
820
+
786
821
  Python Datatypes for VECTOR
787
822
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
788
823
 
789
824
  VECTOR data can be inserted using Python list or Python ``array.array()`` objects.
790
- Python arrays of type FLOAT (32-bit), DOUBLE (64-bit), or INT (8-bit signed integer)
791
- are used as bind values when inserting VECTOR columns::
825
+ Python arrays of type FLOAT (32-bit), DOUBLE (64-bit), INT (8-bit signed integers),
826
+ or BINARY (8-bit unsigned integers) are used as bind values when inserting
827
+ VECTOR columns::
792
828
 
793
829
  from sqlalchemy import insert, select
794
830
 
@@ -798,6 +834,21 @@ are used as bind values when inserting VECTOR columns::
798
834
  {"id": 1, "embedding": [1, 2, 3]},
799
835
  )
800
836
 
837
+ Data can be inserted into a sparse vector using the :class:`_oracle.SparseVector`
838
+ class, creating an object consisting of the number of dimensions, an array of indices, and a
839
+ corresponding array of values::
840
+
841
+ from sqlalchemy import insert, select
842
+ from sqlalchemy.dialects.oracle import SparseVector
843
+
844
+ sparse_val = SparseVector(10, [1, 2], array.array("d", [23.45, 221.22]))
845
+
846
+ with engine.begin() as conn:
847
+ conn.execute(
848
+ insert(t1),
849
+ {"id": 1, "embedding": sparse_val},
850
+ )
851
+
801
852
  VECTOR Indexes
802
853
  ~~~~~~~~~~~~~~
803
854
 
@@ -805,6 +856,8 @@ The VECTOR feature supports an Oracle-specific parameter ``oracle_vector``
805
856
  on the :class:`.Index` construct, which allows the construction of VECTOR
806
857
  indexes.
807
858
 
859
+ SPARSE vectors cannot be used in the creation of vector indexes.
860
+
808
861
  To utilize VECTOR indexing, set the ``oracle_vector`` parameter to True to use
809
862
  the default values provided by Oracle. HNSW is the default indexing method::
810
863
 
@@ -1157,14 +1210,16 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
1157
1210
  return "ROWID"
1158
1211
 
1159
1212
  def visit_VECTOR(self, type_, **kw):
1160
- if type_.dim is None and type_.storage_format is None:
1161
- return "VECTOR(*,*)"
1162
- elif type_.storage_format is None:
1163
- return f"VECTOR({type_.dim},*)"
1164
- elif type_.dim is None:
1165
- return f"VECTOR(*,{type_.storage_format.value})"
1166
- else:
1167
- return f"VECTOR({type_.dim},{type_.storage_format.value})"
1213
+ dim = type_.dim if type_.dim is not None else "*"
1214
+ storage_format = (
1215
+ type_.storage_format.value
1216
+ if type_.storage_format is not None
1217
+ else "*"
1218
+ )
1219
+ storage_type = (
1220
+ type_.storage_type.value if type_.storage_type is not None else "*"
1221
+ )
1222
+ return f"VECTOR({dim},{storage_format},{storage_type})"
1168
1223
 
1169
1224
 
1170
1225
  class OracleCompiler(compiler.SQLCompiler):
@@ -1234,6 +1234,9 @@ class OracleDialect_cx_oracle(OracleDialect):
1234
1234
  with dbapi_connection.cursor() as cursor:
1235
1235
  cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}")
1236
1236
 
1237
+ def detect_autocommit_setting(self, dbapi_conn) -> bool:
1238
+ return bool(dbapi_conn.autocommit)
1239
+
1237
1240
  def _detect_decimal_char(self, connection):
1238
1241
  # we have the option to change this setting upon connect,
1239
1242
  # or just look at what it is upon connect and convert.
@@ -13,6 +13,7 @@ import array
13
13
  from dataclasses import dataclass
14
14
  from enum import Enum
15
15
  from typing import Optional
16
+ from typing import Union
16
17
 
17
18
  import sqlalchemy.types as types
18
19
  from sqlalchemy.types import Float
@@ -95,6 +96,27 @@ class VectorStorageFormat(Enum):
95
96
  """
96
97
 
97
98
 
99
+ class VectorStorageType(Enum):
100
+ """Enum representing the vector type,
101
+
102
+ See :ref:`oracle_vector_datatype` for background.
103
+
104
+ .. versionadded:: 2.0.43
105
+
106
+ """
107
+
108
+ SPARSE = "SPARSE"
109
+ """
110
+ A Sparse vector is a vector which has zero value for
111
+ most of its dimensions.
112
+ """
113
+ DENSE = "DENSE"
114
+ """
115
+ A Dense vector is a vector where most, if not all, elements
116
+ hold meaningful values.
117
+ """
118
+
119
+
98
120
  @dataclass
99
121
  class VectorIndexConfig:
100
122
  """Define the configuration for Oracle VECTOR Index.
@@ -176,6 +198,39 @@ class VectorIndexConfig:
176
198
  )
177
199
 
178
200
 
201
+ class SparseVector:
202
+ """
203
+ Lightweight SQLAlchemy-side version of SparseVector.
204
+ This mimics oracledb.SparseVector.
205
+
206
+ .. versionadded:: 2.0.43
207
+
208
+ """
209
+
210
+ def __init__(
211
+ self,
212
+ num_dimensions: int,
213
+ indices: Union[list, array.array],
214
+ values: Union[list, array.array],
215
+ ):
216
+ if not isinstance(indices, array.array) or indices.typecode != "I":
217
+ indices = array.array("I", indices)
218
+ if not isinstance(values, array.array):
219
+ values = array.array("d", values)
220
+ if len(indices) != len(values):
221
+ raise TypeError("indices and values must be of the same length!")
222
+
223
+ self.num_dimensions = num_dimensions
224
+ self.indices = indices
225
+ self.values = values
226
+
227
+ def __str__(self):
228
+ return (
229
+ f"SparseVector(num_dimensions={self.num_dimensions}, "
230
+ f"size={len(self.indices)}, typecode={self.values.typecode})"
231
+ )
232
+
233
+
179
234
  class VECTOR(types.TypeEngine):
180
235
  """Oracle VECTOR datatype.
181
236
 
@@ -196,17 +251,22 @@ class VECTOR(types.TypeEngine):
196
251
  VectorStorageFormat.FLOAT64: "d", # Double
197
252
  }
198
253
 
199
- def __init__(self, dim=None, storage_format=None):
254
+ def __init__(self, dim=None, storage_format=None, storage_type=None):
200
255
  """Construct a VECTOR.
201
256
 
202
257
  :param dim: integer. The dimension of the VECTOR datatype. This
203
258
  should be an integer value.
204
259
 
205
260
  :param storage_format: VectorStorageFormat. The VECTOR storage
206
- type format. This may be Enum values form
261
+ type format. This should be Enum values form
207
262
  :class:`.VectorStorageFormat` INT8, BINARY, FLOAT32, or FLOAT64.
208
263
 
264
+ :param storage_type: VectorStorageType. The Vector storage type. This
265
+ should be Enum values from :class:`.VectorStorageType` SPARSE or
266
+ DENSE.
267
+
209
268
  """
269
+
210
270
  if dim is not None and not isinstance(dim, int):
211
271
  raise TypeError("dim must be an interger")
212
272
  if storage_format is not None and not isinstance(
@@ -215,12 +275,22 @@ class VECTOR(types.TypeEngine):
215
275
  raise TypeError(
216
276
  "storage_format must be an enum of type VectorStorageFormat"
217
277
  )
278
+ if storage_type is not None and not isinstance(
279
+ storage_type, VectorStorageType
280
+ ):
281
+ raise TypeError(
282
+ "storage_type must be an enum of type VectorStorageType"
283
+ )
284
+
218
285
  self.dim = dim
219
286
  self.storage_format = storage_format
287
+ self.storage_type = storage_type
220
288
 
221
289
  def _cached_bind_processor(self, dialect):
222
290
  """
223
- Convert a list to a array.array before binding it to the database.
291
+ Converts a Python-side SparseVector instance into an
292
+ oracledb.SparseVectormor a compatible array format before
293
+ binding it to the database.
224
294
  """
225
295
 
226
296
  def process(value):
@@ -233,20 +303,48 @@ class VECTOR(types.TypeEngine):
233
303
  value = array.array(typecode, value)
234
304
  return value
235
305
 
306
+ # Convert SqlAlchemy SparseVector to oracledb SparseVector object
307
+ elif isinstance(value, SparseVector):
308
+ return dialect.dbapi.SparseVector(
309
+ value.num_dimensions,
310
+ value.indices,
311
+ value.values,
312
+ )
313
+
236
314
  else:
237
- raise TypeError("VECTOR accepts list or array.array()")
315
+ raise TypeError(
316
+ """
317
+ Invalid input for VECTOR: expected a list, an array.array,
318
+ or a SparseVector object.
319
+ """
320
+ )
238
321
 
239
322
  return process
240
323
 
241
324
  def _cached_result_processor(self, dialect, coltype):
242
325
  """
243
- Convert a array.array to list before binding it to the database.
326
+ Converts database-returned values into Python-native representations.
327
+ If the value is an oracledb.SparseVector, it is converted into the
328
+ SQLAlchemy-side SparseVector class.
329
+ If the value is a array.array, it is converted to a plain Python list.
330
+
244
331
  """
245
332
 
246
333
  def process(value):
247
- if isinstance(value, array.array):
334
+ if value is None:
335
+ return None
336
+
337
+ elif isinstance(value, array.array):
248
338
  return list(value)
249
339
 
340
+ # Convert Oracledb SparseVector to SqlAlchemy SparseVector object
341
+ elif isinstance(value, dialect.dbapi.SparseVector):
342
+ return SparseVector(
343
+ num_dimensions=value.num_dimensions,
344
+ indices=value.indices,
345
+ values=value.values,
346
+ )
347
+
250
348
  return process
251
349
 
252
350
  def _array_typecode(self, typecode):
@@ -170,6 +170,9 @@ class _PGDialect_common_psycopg(PGDialect):
170
170
  def _do_autocommit(self, connection, value):
171
171
  connection.autocommit = value
172
172
 
173
+ def detect_autocommit_setting(self, dbapi_connection):
174
+ return bool(dbapi_connection.autocommit)
175
+
173
176
  def do_ping(self, dbapi_connection):
174
177
  before_autocommit = dbapi_connection.autocommit
175
178
 
@@ -1117,6 +1117,9 @@ class PGDialect_asyncpg(PGDialect):
1117
1117
  def set_isolation_level(self, dbapi_connection, level):
1118
1118
  dbapi_connection.set_isolation_level(self._isolation_lookup[level])
1119
1119
 
1120
+ def detect_autocommit_setting(self, dbapi_conn) -> bool:
1121
+ return bool(dbapi_conn.autocommit)
1122
+
1120
1123
  def set_readonly(self, connection, value):
1121
1124
  connection.readonly = value
1122
1125
 
@@ -540,6 +540,9 @@ class PGDialect_pg8000(PGDialect):
540
540
  cursor.execute("COMMIT")
541
541
  cursor.close()
542
542
 
543
+ def detect_autocommit_setting(self, dbapi_conn) -> bool:
544
+ return bool(dbapi_conn.autocommit)
545
+
543
546
  def set_readonly(self, connection, value):
544
547
  cursor = connection.cursor()
545
548
  try:
@@ -396,10 +396,18 @@ new connections through the usage of events::
396
396
 
397
397
  @event.listens_for(Engine, "connect")
398
398
  def set_sqlite_pragma(dbapi_connection, connection_record):
399
+ # the sqlite3 driver will not set PRAGMA foreign_keys
400
+ # if autocommit=False; set to True temporarily
401
+ ac = dbapi_connection.autocommit
402
+ dbapi_connection.autocommit = True
403
+
399
404
  cursor = dbapi_connection.cursor()
400
405
  cursor.execute("PRAGMA foreign_keys=ON")
401
406
  cursor.close()
402
407
 
408
+ # restore previous autocommit setting
409
+ dbapi_connection.autocommit = ac
410
+
403
411
  .. warning::
404
412
 
405
413
  When SQLite foreign keys are enabled, it is **not possible**
@@ -502,6 +502,9 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
502
502
  dbapi_connection.isolation_level = ""
503
503
  return super().set_isolation_level(dbapi_connection, level)
504
504
 
505
+ def detect_autocommit_setting(self, dbapi_connection):
506
+ return dbapi_connection.isolation_level is None
507
+
505
508
  def on_connect(self):
506
509
  def regexp(a, b):
507
510
  if b is None:
sqlalchemy/engine/base.py CHANGED
@@ -1112,10 +1112,16 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
1112
1112
  if self._still_open_and_dbapi_connection_is_valid:
1113
1113
  if self._echo:
1114
1114
  if self._is_autocommit_isolation():
1115
- self._log_info(
1116
- "ROLLBACK using DBAPI connection.rollback(), "
1117
- "DBAPI should ignore due to autocommit mode"
1118
- )
1115
+ if self.dialect.skip_autocommit_rollback:
1116
+ self._log_info(
1117
+ "ROLLBACK will be skipped by "
1118
+ "skip_autocommit_rollback"
1119
+ )
1120
+ else:
1121
+ self._log_info(
1122
+ "ROLLBACK using DBAPI connection.rollback(); "
1123
+ "set skip_autocommit_rollback to prevent fully"
1124
+ )
1119
1125
  else:
1120
1126
  self._log_info("ROLLBACK")
1121
1127
  try:
@@ -1131,7 +1137,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
1131
1137
  if self._is_autocommit_isolation():
1132
1138
  self._log_info(
1133
1139
  "COMMIT using DBAPI connection.commit(), "
1134
- "DBAPI should ignore due to autocommit mode"
1140
+ "has no effect due to autocommit mode"
1135
1141
  )
1136
1142
  else:
1137
1143
  self._log_info("COMMIT")
@@ -468,6 +468,9 @@ def create_engine(url: Union[str, _url.URL], **kwargs: Any) -> Engine:
468
468
 
469
469
  :ref:`pool_reset_on_return`
470
470
 
471
+ :ref:`dbapi_autocommit_skip_rollback` - a more modern approach
472
+ to using connections with no transactional instructions
473
+
471
474
  :param pool_timeout=30: number of seconds to wait before giving
472
475
  up on getting a connection from the pool. This is only used
473
476
  with :class:`~sqlalchemy.pool.QueuePool`. This can be a float but is
@@ -524,6 +527,18 @@ def create_engine(url: Union[str, _url.URL], **kwargs: Any) -> Engine:
524
527
 
525
528
  .. versionadded:: 1.4
526
529
 
530
+ :param skip_autocommit_rollback: When True, the dialect will
531
+ unconditionally skip all calls to the DBAPI ``connection.rollback()``
532
+ method if the DBAPI connection is confirmed to be in "autocommit" mode.
533
+ The availability of this feature is dialect specific; if not available,
534
+ a ``NotImplementedError`` is raised by the dialect when rollback occurs.
535
+
536
+ .. seealso::
537
+
538
+ :ref:`dbapi_autocommit_skip_rollback`
539
+
540
+ .. versionadded:: 2.0.43
541
+
527
542
  :param use_insertmanyvalues: True by default, use the "insertmanyvalues"
528
543
  execution style for INSERT..RETURNING statements by default.
529
544
 
@@ -307,6 +307,7 @@ class DefaultDialect(Dialect):
307
307
  # Linting.NO_LINTING constant
308
308
  compiler_linting: Linting = int(compiler.NO_LINTING), # type: ignore
309
309
  server_side_cursors: bool = False,
310
+ skip_autocommit_rollback: bool = False,
310
311
  **kwargs: Any,
311
312
  ):
312
313
  if server_side_cursors:
@@ -331,6 +332,8 @@ class DefaultDialect(Dialect):
331
332
 
332
333
  self.dbapi = dbapi
333
334
 
335
+ self.skip_autocommit_rollback = skip_autocommit_rollback
336
+
334
337
  if paramstyle is not None:
335
338
  self.paramstyle = paramstyle
336
339
  elif self.dbapi is not None:
@@ -701,6 +704,10 @@ class DefaultDialect(Dialect):
701
704
  pass
702
705
 
703
706
  def do_rollback(self, dbapi_connection):
707
+ if self.skip_autocommit_rollback and self.detect_autocommit_setting(
708
+ dbapi_connection
709
+ ):
710
+ return
704
711
  dbapi_connection.rollback()
705
712
 
706
713
  def do_commit(self, dbapi_connection):
@@ -779,6 +779,14 @@ class Dialect(EventTarget):
779
779
  default_isolation_level: Optional[IsolationLevel]
780
780
  """the isolation that is implicitly present on new connections"""
781
781
 
782
+ skip_autocommit_rollback: bool
783
+ """Whether or not the :paramref:`.create_engine.skip_autocommit_rollback`
784
+ parameter was set.
785
+
786
+ .. versionadded:: 2.0.43
787
+
788
+ """
789
+
782
790
  # create_engine() -> isolation_level currently goes here
783
791
  _on_connect_isolation_level: Optional[IsolationLevel]
784
792
 
@@ -2481,6 +2489,30 @@ class Dialect(EventTarget):
2481
2489
 
2482
2490
  raise NotImplementedError()
2483
2491
 
2492
+ def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
2493
+ """Detect the current autocommit setting for a DBAPI connection.
2494
+
2495
+ :param dbapi_connection: a DBAPI connection object
2496
+ :return: True if autocommit is enabled, False if disabled
2497
+ :rtype: bool
2498
+
2499
+ This method inspects the given DBAPI connection to determine
2500
+ whether autocommit mode is currently enabled. The specific
2501
+ mechanism for detecting autocommit varies by database dialect
2502
+ and DBAPI driver, however it should be done **without** network
2503
+ round trips.
2504
+
2505
+ .. note::
2506
+
2507
+ Not all dialects support autocommit detection. Dialects
2508
+ that do not support this feature will raise
2509
+ :exc:`NotImplementedError`.
2510
+
2511
+ """
2512
+ raise NotImplementedError(
2513
+ "This dialect cannot detect autocommit on a DBAPI connection"
2514
+ )
2515
+
2484
2516
  def get_default_isolation_level(
2485
2517
  self, dbapi_conn: DBAPIConnection
2486
2518
  ) -> IsolationLevel:
@@ -2013,7 +2013,7 @@ class MappingResult(_WithKeys, FilterResult[RowMapping]):
2013
2013
  return self
2014
2014
 
2015
2015
  def columns(self, *col_expressions: _KeyIndexType) -> Self:
2016
- r"""Establish the columns that should be returned in each row."""
2016
+ """Establish the columns that should be returned in each row."""
2017
2017
  return self._column_slices(col_expressions)
2018
2018
 
2019
2019
  def partitions(
@@ -393,7 +393,9 @@ class CompositeProperty(
393
393
  self.composite_class = argument
394
394
 
395
395
  if is_dataclass(self.composite_class):
396
- self._setup_for_dataclass(registry, cls, originating_module, key)
396
+ self._setup_for_dataclass(
397
+ decl_scan, registry, cls, originating_module, key
398
+ )
397
399
  else:
398
400
  for attr in self.attrs:
399
401
  if (
@@ -437,6 +439,7 @@ class CompositeProperty(
437
439
  @util.preload_module("sqlalchemy.orm.decl_base")
438
440
  def _setup_for_dataclass(
439
441
  self,
442
+ decl_scan: _ClassScanMapperConfig,
440
443
  registry: _RegistryType,
441
444
  cls: Type[Any],
442
445
  originating_module: Optional[str],
@@ -464,6 +467,7 @@ class CompositeProperty(
464
467
 
465
468
  if isinstance(attr, MappedColumn):
466
469
  attr.declarative_scan_for_composite(
470
+ decl_scan,
467
471
  registry,
468
472
  cls,
469
473
  originating_module,
sqlalchemy/orm/loading.py CHANGED
@@ -39,6 +39,7 @@ from .base import PassiveFlag
39
39
  from .context import FromStatement
40
40
  from .context import ORMCompileState
41
41
  from .context import QueryContext
42
+ from .strategies import SelectInLoader
42
43
  from .util import _none_set
43
44
  from .util import state_str
44
45
  from .. import exc as sa_exc
@@ -1309,15 +1310,18 @@ def _load_subclass_via_in(
1309
1310
  if context.populate_existing:
1310
1311
  q2 = q2.execution_options(populate_existing=True)
1311
1312
 
1312
- context.session.execute(
1313
- q2,
1314
- dict(
1315
- primary_keys=[
1316
- state.key[1][0] if zero_idx else state.key[1]
1317
- for state, load_attrs in states
1318
- ]
1319
- ),
1320
- ).unique().scalars().all()
1313
+ while states:
1314
+ chunk = states[0 : SelectInLoader._chunksize]
1315
+ states = states[SelectInLoader._chunksize :]
1316
+ context.session.execute(
1317
+ q2,
1318
+ dict(
1319
+ primary_keys=[
1320
+ state.key[1][0] if zero_idx else state.key[1]
1321
+ for state, load_attrs in chunk
1322
+ ]
1323
+ ),
1324
+ ).unique().scalars().all()
1321
1325
 
1322
1326
  return do_load
1323
1327
 
@@ -1374,7 +1374,13 @@ def _emit_post_update_statements(
1374
1374
  )
1375
1375
 
1376
1376
  rows += c.rowcount
1377
- for state, state_dict, mapper_rec, connection, params in records:
1377
+ for i, (
1378
+ state,
1379
+ state_dict,
1380
+ mapper_rec,
1381
+ connection,
1382
+ params,
1383
+ ) in enumerate(records):
1378
1384
  _postfetch_post_update(
1379
1385
  mapper_rec,
1380
1386
  uowtransaction,
@@ -1382,7 +1388,7 @@ def _emit_post_update_statements(
1382
1388
  state,
1383
1389
  state_dict,
1384
1390
  c,
1385
- c.context.compiled_parameters[0],
1391
+ c.context.compiled_parameters[i],
1386
1392
  )
1387
1393
 
1388
1394
  if check_rowcount:
@@ -663,20 +663,12 @@ class MappedColumn(
663
663
  # Column will be merged into it in _init_column_for_annotation().
664
664
  return MappedColumn()
665
665
 
666
- def declarative_scan(
666
+ def _adjust_for_existing_column(
667
667
  self,
668
668
  decl_scan: _ClassScanMapperConfig,
669
- registry: _RegistryType,
670
- cls: Type[Any],
671
- originating_module: Optional[str],
672
669
  key: str,
673
- mapped_container: Optional[Type[Mapped[Any]]],
674
- annotation: Optional[_AnnotationScanType],
675
- extracted_mapped_annotation: Optional[_AnnotationScanType],
676
- is_dataclass_field: bool,
677
- ) -> None:
678
- column = self.column
679
-
670
+ given_column: Column[_T],
671
+ ) -> Column[_T]:
680
672
  if (
681
673
  self._use_existing_column
682
674
  and decl_scan.inherits
@@ -688,10 +680,31 @@ class MappedColumn(
688
680
  )
689
681
  supercls_mapper = class_mapper(decl_scan.inherits, False)
690
682
 
691
- colname = column.name if column.name is not None else key
692
- column = self.column = supercls_mapper.local_table.c.get( # type: ignore[assignment] # noqa: E501
693
- colname, column
683
+ colname = (
684
+ given_column.name if given_column.name is not None else key
694
685
  )
686
+ given_column = supercls_mapper.local_table.c.get( # type: ignore[assignment] # noqa: E501
687
+ colname, given_column
688
+ )
689
+ return given_column
690
+
691
+ def declarative_scan(
692
+ self,
693
+ decl_scan: _ClassScanMapperConfig,
694
+ registry: _RegistryType,
695
+ cls: Type[Any],
696
+ originating_module: Optional[str],
697
+ key: str,
698
+ mapped_container: Optional[Type[Mapped[Any]]],
699
+ annotation: Optional[_AnnotationScanType],
700
+ extracted_mapped_annotation: Optional[_AnnotationScanType],
701
+ is_dataclass_field: bool,
702
+ ) -> None:
703
+ column = self.column
704
+
705
+ column = self.column = self._adjust_for_existing_column(
706
+ decl_scan, key, self.column
707
+ )
695
708
 
696
709
  if column.key is None:
697
710
  column.key = key
@@ -708,6 +721,8 @@ class MappedColumn(
708
721
 
709
722
  self._init_column_for_annotation(
710
723
  cls,
724
+ decl_scan,
725
+ key,
711
726
  registry,
712
727
  extracted_mapped_annotation,
713
728
  originating_module,
@@ -716,6 +731,7 @@ class MappedColumn(
716
731
  @util.preload_module("sqlalchemy.orm.decl_base")
717
732
  def declarative_scan_for_composite(
718
733
  self,
734
+ decl_scan: _ClassScanMapperConfig,
719
735
  registry: _RegistryType,
720
736
  cls: Type[Any],
721
737
  originating_module: Optional[str],
@@ -726,12 +742,14 @@ class MappedColumn(
726
742
  decl_base = util.preloaded.orm_decl_base
727
743
  decl_base._undefer_column_name(param_name, self.column)
728
744
  self._init_column_for_annotation(
729
- cls, registry, param_annotation, originating_module
745
+ cls, decl_scan, key, registry, param_annotation, originating_module
730
746
  )
731
747
 
732
748
  def _init_column_for_annotation(
733
749
  self,
734
750
  cls: Type[Any],
751
+ decl_scan: _ClassScanMapperConfig,
752
+ key: str,
735
753
  registry: _RegistryType,
736
754
  argument: _AnnotationScanType,
737
755
  originating_module: Optional[str],
@@ -778,6 +796,11 @@ class MappedColumn(
778
796
  use_args_from = None
779
797
 
780
798
  if use_args_from is not None:
799
+
800
+ self.column = use_args_from._adjust_for_existing_column(
801
+ decl_scan, key, self.column
802
+ )
803
+
781
804
  if (
782
805
  not self._has_insert_default
783
806
  and use_args_from.column.default is not None
@@ -745,9 +745,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
745
745
  # expressions against getitem. This may need to be made
746
746
  # more portable if in the future we support other DBs
747
747
  # besides postgresql.
748
- if against is operators.getitem and isinstance(
749
- self.type, sqltypes.ARRAY
750
- ):
748
+ if against in (operators.getitem, operators.json_getitem_op):
751
749
  return Grouping(self)
752
750
  else:
753
751
  return super().self_group(against=against)
@@ -1033,6 +1033,13 @@ class SuiteRequirements(Requirements):
1033
1033
  """target dialect supports 'AUTOCOMMIT' as an isolation_level"""
1034
1034
  return exclusions.closed()
1035
1035
 
1036
+ @property
1037
+ def skip_autocommit_rollback(self):
1038
+ """target dialect supports the detect_autocommit_setting() method and
1039
+ uses the default implementation of do_rollback()"""
1040
+
1041
+ return exclusions.closed()
1042
+
1036
1043
  @property
1037
1044
  def isolation_level(self):
1038
1045
  """target dialect supports general isolation level settings.
@@ -17,6 +17,7 @@ from .. import eq_
17
17
  from .. import fixtures
18
18
  from .. import is_not_none
19
19
  from .. import is_true
20
+ from .. import mock
20
21
  from .. import ne_
21
22
  from .. import provide_metadata
22
23
  from ..assertions import expect_raises
@@ -293,7 +294,11 @@ class AutocommitIsolationTest(fixtures.TablesTest):
293
294
  test_needs_acid=True,
294
295
  )
295
296
 
296
- def _test_conn_autocommits(self, conn, autocommit):
297
+ def _test_conn_autocommits(self, conn, autocommit, ensure_table=False):
298
+ if ensure_table:
299
+ self.tables.some_table.create(conn, checkfirst=True)
300
+ conn.commit()
301
+
297
302
  trans = conn.begin()
298
303
  conn.execute(
299
304
  self.tables.some_table.insert(), {"id": 1, "data": "some data"}
@@ -336,6 +341,37 @@ class AutocommitIsolationTest(fixtures.TablesTest):
336
341
  )
337
342
  self._test_conn_autocommits(conn, False)
338
343
 
344
+ @testing.requires.skip_autocommit_rollback
345
+ @testing.variation("autocommit_setting", ["false", "engine", "option"])
346
+ @testing.variation("block_rollback", [True, False])
347
+ def test_autocommit_block(
348
+ self, testing_engine, autocommit_setting, block_rollback
349
+ ):
350
+ kw = {}
351
+ if bool(block_rollback):
352
+ kw["skip_autocommit_rollback"] = True
353
+ if autocommit_setting.engine:
354
+ kw["isolation_level"] = "AUTOCOMMIT"
355
+
356
+ engine = testing_engine(options=kw)
357
+
358
+ conn = engine.connect()
359
+ if autocommit_setting.option:
360
+ conn.execution_options(isolation_level="AUTOCOMMIT")
361
+ self._test_conn_autocommits(
362
+ conn,
363
+ autocommit_setting.engine or autocommit_setting.option,
364
+ ensure_table=True,
365
+ )
366
+ with mock.patch.object(
367
+ conn.connection, "rollback", wraps=conn.connection.rollback
368
+ ) as check_rollback:
369
+ conn.close()
370
+ if autocommit_setting.false or not block_rollback:
371
+ eq_(check_rollback.mock_calls, [mock.call()])
372
+ else:
373
+ eq_(check_rollback.mock_calls, [])
374
+
339
375
  @testing.requires.independent_readonly_connections
340
376
  @testing.variation("use_dialect_setting", [True, False])
341
377
  def test_dialect_autocommit_is_restored(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy
3
- Version: 2.0.42
3
+ Version: 2.0.43
4
4
  Summary: Database Abstraction Library
5
5
  Home-page: https://www.sqlalchemy.org
6
6
  Author: Mike Bayer
@@ -1,4 +1,4 @@
1
- sqlalchemy/__init__.py,sha256=B2OJyoEJaSOLgw9AIQNETdOyh7MVzj2sPZN_oWvk-1A,12659
1
+ sqlalchemy/__init__.py,sha256=Oi26seKKS4YLZt2VPHQvkIIoTKAcEWD4BCjHQZRG8BE,12659
2
2
  sqlalchemy/events.py,sha256=VBRvtckn9JS3tfUfi6UstqUrvQ15J2xamcDByFysIrI,525
3
3
  sqlalchemy/exc.py,sha256=AjFBCrOl_V4vQdGegn72Y951RSRMPL6T5qjxnFTGFbM,23978
4
4
  sqlalchemy/inspection.py,sha256=qKEKG37N1OjxpQeVzob1q9VwWjBbjI1x0movJG7fYJ4,5063
@@ -9,7 +9,7 @@ sqlalchemy/types.py,sha256=Iq_rKisaj_zhHtzD2R2cxvg3jkug5frikbkcKG0S4Lg,3166
9
9
  sqlalchemy/connectors/__init__.py,sha256=YeSHsOB0YhdM6jZUvHFQFwKqNXO02MlklmGW0yCywjI,476
10
10
  sqlalchemy/connectors/aioodbc.py,sha256=-OKbnvR-kLCKHyrOIBkAZwTASAbQZ5qmrozm0dwbtNE,5577
11
11
  sqlalchemy/connectors/asyncio.py,sha256=OPhwvKQo7l3CUSY7YsL3W8oBqc_zQIAytIvqLjZLwTA,10122
12
- sqlalchemy/connectors/pyodbc.py,sha256=VaNPMLmkEiNteyPUIzwVIGRSjN9DZVZdJakHDvx5jWI,8471
12
+ sqlalchemy/connectors/pyodbc.py,sha256=ZGWBmYYYVgqUHjex3d_lYHZyAhQJGowp9cWGYnj1200,8618
13
13
  sqlalchemy/cyextension/__init__.py,sha256=4npVIjitKfUs0NQ6f3UdQBDq4ipJ0_ZNB2mpKqtc5ik,244
14
14
  sqlalchemy/cyextension/collections.pyx,sha256=L7DZ3DGKpgw2MT2ZZRRxCnrcyE5pU1NAFowWgAzQPEc,12571
15
15
  sqlalchemy/cyextension/immutabledict.pxd,sha256=3x3-rXG5eRQ7bBnktZ-OJ9-6ft8zToPmTDOd92iXpB0,291
@@ -29,8 +29,8 @@ sqlalchemy/dialects/mssql/provision.py,sha256=P1tqxZ4f6Oeqn2gNi7dXl82LRLCg1-OB4e
29
29
  sqlalchemy/dialects/mssql/pymssql.py,sha256=C7yAs3Pw81W1KTVNc6_0sHQuYlJ5iH82vKByY4TkB1g,4097
30
30
  sqlalchemy/dialects/mssql/pyodbc.py,sha256=CnO7KDWxbxb7AoZhp_PMDBvVSMuzwq1h4Cav2IWFWDo,27173
31
31
  sqlalchemy/dialects/mysql/__init__.py,sha256=ropOMUWrAcL-Q7h-9jQ_tb3ISAFIsNRQ8YVXvn0URl0,2206
32
- sqlalchemy/dialects/mysql/aiomysql.py,sha256=nkmVq3twbeN2g0PMe0oBOy_UhK-MqYWqXTD4sC9aYTE,7624
33
- sqlalchemy/dialects/mysql/asyncmy.py,sha256=9hc8GoYzmjW7ypWJCJiwF6VSdl9kEX43c9b5SknERaw,6989
32
+ sqlalchemy/dialects/mysql/aiomysql.py,sha256=XpHS7KvZF_XQFlghvqyZfPuLD890M7GTgMLCaeXA67E,7728
33
+ sqlalchemy/dialects/mysql/asyncmy.py,sha256=kuX02tRZ-0kKbwRgs3dL5T-mRyc5oBSFoIzQDgaHgYk,7093
34
34
  sqlalchemy/dialects/mysql/base.py,sha256=V2CE2XB6eiFG3doNdzH3NZPhgXgt3OL7QN8F3dg_9Pg,137763
35
35
  sqlalchemy/dialects/mysql/cymysql.py,sha256=ihH4kZ273nvf0R0p8keD71ZIaTXRHyZePXMlobwgbpI,3215
36
36
  sqlalchemy/dialects/mysql/dml.py,sha256=VjnTobe_SBNF2RN6tvqa5LOn-9x4teVUyzUedZkOmdc,7768
@@ -38,27 +38,27 @@ sqlalchemy/dialects/mysql/enumerated.py,sha256=si2hGv5jMNGS78n_JDgswIhbBZuTqjwbx
38
38
  sqlalchemy/dialects/mysql/expression.py,sha256=C8LhU-CM6agqKCS1tl1_ChSqwZbqt3zP_dSGBqgBgLg,4241
39
39
  sqlalchemy/dialects/mysql/json.py,sha256=ckYT_lihvqr28iHJTUUwvPPUIoYVLL_wUXWFDTCna_M,2806
40
40
  sqlalchemy/dialects/mysql/mariadb.py,sha256=yaiZnnbjfrBqHm1ykaRSFYKrrYUqu-GBYvt97EGYSzs,1886
41
- sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=QgPYt1AmbG2PwpQkMdzLtQY3V8y8AQrg-N0Dg-qFsBk,10263
42
- sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=lcBprcbcs-XyfofG-BmNBAY3Dztp0vUmCHQbOQIanc4,9975
43
- sqlalchemy/dialects/mysql/mysqldb.py,sha256=zWerlr1AipnHjZbWG9wr-8k6lusPmQu_yWHuRmGCKNQ,9790
41
+ sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=lJuS3euMlVBbJDJ10ntqe3TnrjzneLEUlE8sLZl6Qoc,10385
42
+ sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=aaAiF32rQVoLNVIdgGKHMsnMei--0ig3OqmhWq45MrA,10097
43
+ sqlalchemy/dialects/mysql/mysqldb.py,sha256=8wIxcxQxT-X6nywLJkjg9_JdIKGYOhlrtVL8lP_WFcM,9943
44
44
  sqlalchemy/dialects/mysql/provision.py,sha256=MaQ9eeHnRL4EXAebIInwarCIiDbYcz_sMCss3wyV12Q,3717
45
45
  sqlalchemy/dialects/mysql/pymysql.py,sha256=Qlc9XToIqAfHz0c_ODs97uk1TlV1ZrEl_TidTjoeByU,4886
46
46
  sqlalchemy/dialects/mysql/pyodbc.py,sha256=v-Zo4M7blxdff--KJiIantCwbPO6H-GBkNCTN4nBgU4,5111
47
47
  sqlalchemy/dialects/mysql/reflection.py,sha256=CBxBiv1mCLLNHz-I8hgJKACTF3K0eYEpWd0ndCBCq5I,24690
48
48
  sqlalchemy/dialects/mysql/reserved_words.py,sha256=iG6zb78sn-RdqWQRk2F_Tuufk5tUodkcoHbxTdgZYkw,9236
49
49
  sqlalchemy/dialects/mysql/types.py,sha256=lAkkNRVPBHP8H7AQQ7NykfJ8YxgdUDAHkfd7qD-Lwvo,26459
50
- sqlalchemy/dialects/oracle/__init__.py,sha256=0CN5PU88L9wND0qZhAYF3H6Lv7m5tSauD1SgE3hzQV8,1782
51
- sqlalchemy/dialects/oracle/base.py,sha256=hNn633atVfNzEvuiX7P-dZ8nBY68zOWWCn4S7nMrEsk,137057
52
- sqlalchemy/dialects/oracle/cx_oracle.py,sha256=ohENTgLxGUfobRH3K8KdeZgBRPG1rX3vY-ph9blj-2g,56612
50
+ sqlalchemy/dialects/oracle/__init__.py,sha256=5qrJcFTF3vgB9B4PkwBJj3iXE7P57LdaHNkxMa1NXug,1898
51
+ sqlalchemy/dialects/oracle/base.py,sha256=zEl885-lRs07FGdWFuSzBfa1FqrUPT7l2wpcBr9joIs,139156
52
+ sqlalchemy/dialects/oracle/cx_oracle.py,sha256=mYrXD0nJzuTY1h878b50fNXIUBgjc9Q1LJjjY1VHx3w,56717
53
53
  sqlalchemy/dialects/oracle/dictionary.py,sha256=J7tGVE0KyUPZKpPLOary3HdDq1DWd29arF5udLgv8_o,19519
54
54
  sqlalchemy/dialects/oracle/oracledb.py,sha256=veqto1AUIbSxRmpUQin0ysMV8Y6sWAkzXt7W8IIl118,33771
55
55
  sqlalchemy/dialects/oracle/provision.py,sha256=ga1gNQZlXZKk7DYuYegllUejJxZXRKDGa7dbi_S_poc,8313
56
56
  sqlalchemy/dialects/oracle/types.py,sha256=axN6Yidx9tGRIUAbDpBrhMWXE-C8jSllFpTghpGOOzU,9058
57
- sqlalchemy/dialects/oracle/vector.py,sha256=kG78FYA4MKGmSdPrVduzht3PetJ1pewE3_7uPewPn-M,7861
57
+ sqlalchemy/dialects/oracle/vector.py,sha256=YtN7E5TbDIQR2FCICaSeeaOnvzHP_O0mXNq1gk02S4Q,10874
58
58
  sqlalchemy/dialects/postgresql/__init__.py,sha256=kD8W-SV5e2CesvWg2MQAtncXuZFwGPfR_UODvmRXE08,3892
59
- sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=znHkQBGgbR9fYgBHI2Qd9O-RhSe1bKkkX2zZQih_ba4,5674
59
+ sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=h4JmkHWxy_Nspn6Bi9YKpa9l0OkwInwQzYKue-fJnVA,5783
60
60
  sqlalchemy/dialects/postgresql/array.py,sha256=FyyJ1f3RSAhHtgxKydfMkUAGEh-LJyLOZ31jiAdDo74,16956
61
- sqlalchemy/dialects/postgresql/asyncpg.py,sha256=mEc7aj5wmtJNBF-xRCJ6PuoxJ0wpN_bcE99sct-kd4s,41443
61
+ sqlalchemy/dialects/postgresql/asyncpg.py,sha256=QPvyV6YYZ9--ULoMYC5pl7axct79H8DbYrKAUQASqzg,41548
62
62
  sqlalchemy/dialects/postgresql/base.py,sha256=RDuehOZL3hLPhq4_7G-91BgAM9LeToHiiIU-RjFGVmU,186421
63
63
  sqlalchemy/dialects/postgresql/dml.py,sha256=2SmyMeYveAgm7OnT_CJvwad2nh8BP37yT6gFs8dBYN8,12126
64
64
  sqlalchemy/dialects/postgresql/ext.py,sha256=voxpAz-zoCOO-fjpCzrw7UASzNIvdz2u4kFSuGcshlI,17347
@@ -66,7 +66,7 @@ sqlalchemy/dialects/postgresql/hstore.py,sha256=wR4gmvfQWPssHwYTXEsPJTb4LkBS6x4e
66
66
  sqlalchemy/dialects/postgresql/json.py,sha256=YO6yuDnUKh-mHNtc7DavFMpYNUrJ_dNb24gw333uH0M,12842
67
67
  sqlalchemy/dialects/postgresql/named_types.py,sha256=D1WFTcxE-PKYRaB75gWvnAvpgGJRTcFkW9nSGpC4WCo,17812
68
68
  sqlalchemy/dialects/postgresql/operators.py,sha256=ay3ckNsWtqDjxDseTdKMGGqYVzST6lmfhbbYHG_bxCw,2808
69
- sqlalchemy/dialects/postgresql/pg8000.py,sha256=RAykzZuO3Anr6AsyK2JYr7CPb2pru6WtkrX2phCyCGU,18638
69
+ sqlalchemy/dialects/postgresql/pg8000.py,sha256=r6Lg5tgwuf4FE_RA_kHcfHPW5GXUdNWWr3E846Z4aI0,18743
70
70
  sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=wnzFm9S0JFag1TBdySDJH3VOFSkJWmwAjVcIAQ25jHg,9999
71
71
  sqlalchemy/dialects/postgresql/provision.py,sha256=7pg9-nOnaK5XBzqByXNPuvi3rxtnRa3dJxdSPVq4eeA,5770
72
72
  sqlalchemy/dialects/postgresql/psycopg.py,sha256=k7zXsJj35aOXCrhsbMxwTQX5JWegrqirFJ1Hgbq-GjQ,23326
@@ -76,27 +76,27 @@ sqlalchemy/dialects/postgresql/ranges.py,sha256=rsvhfZ63OVtHHeBDXb_6hULg0HkVx18h
76
76
  sqlalchemy/dialects/postgresql/types.py,sha256=oKhDsFiITKbZcCP66L3dhif54pmsFvVfv-MZQWA3sYo,7629
77
77
  sqlalchemy/dialects/sqlite/__init__.py,sha256=6Xcz3nPsl8lqCcZ4-VzPRmkMrkKgAp2buKsClZelU7c,1182
78
78
  sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=eZW4NFpLS6z02keIHeJLI5tFUkzhn0MpS8r2kkl0G0I,14619
79
- sqlalchemy/dialects/sqlite/base.py,sha256=BrUrYfBlxus3cYsEhw0N_GdDRI5gQCxV6Bz_Y8APiWI,102569
79
+ sqlalchemy/dialects/sqlite/base.py,sha256=rRYahtQDySw-4v6ljEomUdvjigGTNXqaqPuiQ5eOpa4,102859
80
80
  sqlalchemy/dialects/sqlite/dml.py,sha256=4N8qh06RuMphLoQgWw7wv5nXIrka57jIFvK2x9xTZqg,9138
81
81
  sqlalchemy/dialects/sqlite/json.py,sha256=A62xPyLRZxl2hvgTMM92jd_7jlw9UE_4Y6Udqt-8g04,2777
82
82
  sqlalchemy/dialects/sqlite/provision.py,sha256=VhqDjDALqxKQY_3Z3hjzkmPQJ-vtk2Dkk1A4qLTs-G8,5596
83
83
  sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=di8rYryfL0KAn3pRGepmunHyIRGy-4Hhr-2q_ehPzss,5371
84
- sqlalchemy/dialects/sqlite/pysqlite.py,sha256=xUM3jL_VKVRLWeADFTPqETRE0KhSK7Gin3Two5hbG_c,25883
84
+ sqlalchemy/dialects/sqlite/pysqlite.py,sha256=42jPDi1nZ_9YVKKWaKnkurL8NOFUX_8Rbn7baqRw0J8,25999
85
85
  sqlalchemy/engine/__init__.py,sha256=EF4haWCPu95WtWx1GzcHRJ_bBmtJMznno3I2TQ-ZIHE,2818
86
86
  sqlalchemy/engine/_py_processors.py,sha256=7QxgkVOd5h1Qd22qFh-pPZdM7RBRzNjj8lWAMWrilcI,3744
87
87
  sqlalchemy/engine/_py_row.py,sha256=yNdrZe36yw6mO7x0OEbG0dGojH7CQkNReIwn9LMUPUs,3787
88
88
  sqlalchemy/engine/_py_util.py,sha256=Nvd4pVdXRs89khRevK-Ux4Y9p2f2vnALboNrSwhqS1U,2465
89
- sqlalchemy/engine/base.py,sha256=h2f-6jF3Jl-nPoKVDKMxQwsfMzMuUT7adnFCGkux8fc,122809
89
+ sqlalchemy/engine/base.py,sha256=aNp2tGNBWlBz2pHiOveJ3PeaJRDJlLknekUQ50MJDjU,123090
90
90
  sqlalchemy/engine/characteristics.py,sha256=PepmGApo1sL01dS1qtSbmHplu9ZCdtuSegiGI7L7NZY,4765
91
- sqlalchemy/engine/create.py,sha256=4gFkqV7fgJbI1906DC4zDgFFX1-xJQ96GIHIrQuc-w4,33217
91
+ sqlalchemy/engine/create.py,sha256=uIAiU-ANj7fk_6A3dbJw_SEU8Qfd0_YF8yEHGxD0r1g,33847
92
92
  sqlalchemy/engine/cursor.py,sha256=63KLS-IKKAYh2uADJytpT1i9-qpG9E0iVBIcKTtKkwI,76567
93
- sqlalchemy/engine/default.py,sha256=-KVoJGYTC2ypBG0CaNz7001rXOkyearAl8uyatFHDBk,85471
93
+ sqlalchemy/engine/default.py,sha256=PpySUqbAliGjw80ZxhDdZwyiFEMCpNPcC1XmyJynyEE,85721
94
94
  sqlalchemy/engine/events.py,sha256=4_e6Ip32ar2Eb27R4ipamiKC-7Tpg4lVz3txabhT5Rc,37400
95
- sqlalchemy/engine/interfaces.py,sha256=jOKv5vnSsXmWkImNldqviuHDfCnBEu6wpKuzTFyLaiY,114028
95
+ sqlalchemy/engine/interfaces.py,sha256=fNGMov1byIOkPxh7dJervp-UUNyHHm3jpIB0HrCMucc,115119
96
96
  sqlalchemy/engine/mock.py,sha256=L07bSIkgEbIkih-pYvFWh7k7adHVp5tBFBekKlD7GHs,4156
97
97
  sqlalchemy/engine/processors.py,sha256=XK32bULBkuVVRa703u4-SrTCDi_a18Dxq1M09QFBEPw,2379
98
98
  sqlalchemy/engine/reflection.py,sha256=QNOAXvKtdzVddpbkMOyM380y3olKdJKQkmF0Bfwia-Q,75565
99
- sqlalchemy/engine/result.py,sha256=9Uhu3rDK78kMZRvig7_fg_Z4uOlZgRq4n9HPusLNXaw,77806
99
+ sqlalchemy/engine/result.py,sha256=46J3rP0ZwDwsqU-4CAaEHXTpx8OqCEP9Dy4LQwtHUEg,77805
100
100
  sqlalchemy/engine/row.py,sha256=BPtAwsceiRxB9ANpDNM24uQ1M_Zs0xFkSXoKR_I8xyY,12031
101
101
  sqlalchemy/engine/strategies.py,sha256=3DixBdeTa824XjuID2o7UxIyg7GyNwdBI8hOOT0SQnc,439
102
102
  sqlalchemy/engine/url.py,sha256=GJfZo0KtbMtkOIHBPI_KcKASsyrI5UYkX-UoN62FQxc,31067
@@ -149,7 +149,7 @@ sqlalchemy/orm/context.py,sha256=9OOJxvXJ_01Sd5-wny-WqVGtak4IA78TyLG_zMOHYmA,115
149
149
  sqlalchemy/orm/decl_api.py,sha256=ViRNRYA1jXcxJCX2UPW7ugymozqbV55WbIj1c96XPpQ,65038
150
150
  sqlalchemy/orm/decl_base.py,sha256=N13zJJ0Yejcwu0yOWz8WI38ab56WTeHioYr2PlRCal0,83486
151
151
  sqlalchemy/orm/dependency.py,sha256=eiYTsSnW94uGXEFQWj6-KFn25ivz_a2dPN3P6_nMou4,47619
152
- sqlalchemy/orm/descriptor_props.py,sha256=XInDpnCcTu6urMleOFrikfTMjf-pi9mErOy-LQqM-rk,37680
152
+ sqlalchemy/orm/descriptor_props.py,sha256=dh97zKu5-OHDNEhHA3H2YHwdpT8wVT06faeHDzED4pk,37795
153
153
  sqlalchemy/orm/dynamic.py,sha256=Z4GpcVL8rM8gi0bytQOZXw-_kKi-sExbRWGjU30dK3g,9816
154
154
  sqlalchemy/orm/evaluator.py,sha256=PKrUW1zEOvmv1XEgc_hBdYqNcyk4zjWr_rJhCEQBFIc,12353
155
155
  sqlalchemy/orm/events.py,sha256=rdqxmaiaZ7MZ5LQwY5cz6irLkGpJzr1C66zkTsW-QgA,127780
@@ -157,12 +157,12 @@ sqlalchemy/orm/exc.py,sha256=V7cUPl9Kw4qZHLyjOvU1C5WMJ-0MKpNN10qM0C0YG5Y,7636
157
157
  sqlalchemy/orm/identity.py,sha256=5NFtF9ZPZWAOmtOqCPyVX2-_pQq9A5XeN2ns3Wirpv8,9249
158
158
  sqlalchemy/orm/instrumentation.py,sha256=WhElvvOWOn3Fuc-Asc5HmcKDX6EzFtBleLJKPZEc5A0,24321
159
159
  sqlalchemy/orm/interfaces.py,sha256=C0RL0aOVB7E14EVp7MD9C55F2yrOfuOMZ0X-oZg3FCg,49072
160
- sqlalchemy/orm/loading.py,sha256=6Rd1hWtBPm7SfCUpjPQrcoUg_DSCcfhO8Qhz7SScjRE,58277
160
+ sqlalchemy/orm/loading.py,sha256=SMv9Q5bC-kdvsBpOqBNGqNWlL3I75fxByUeEpLC3qtg,58488
161
161
  sqlalchemy/orm/mapped_collection.py,sha256=FAqaTlOUCYqdws2KR_fW0T8mMWIrLuAxJGU5f4W1aGs,19682
162
162
  sqlalchemy/orm/mapper.py,sha256=-7q3rHqj3x_acv6prq3sDEXZmHx7kGSV9G-gW_JwaX4,171834
163
163
  sqlalchemy/orm/path_registry.py,sha256=tRk3osC5BmU7kkcKJCeeibpg2witjyVzO0rX0pu8vmc,25914
164
- sqlalchemy/orm/persistence.py,sha256=Uz45Cwxi7FnNiSk2crbh3TzV7b9kb85vmcvOwy5NVmw,61701
165
- sqlalchemy/orm/properties.py,sha256=bGhcG0Rfz_unF1ixz6WCxl1svqhfcTHbTG8kOGdfmKo,29485
164
+ sqlalchemy/orm/persistence.py,sha256=laKaHW7XsVDYhXfDLnxqAJ5lPB8vhUZ0lEhLvtx-fb4,61812
165
+ sqlalchemy/orm/properties.py,sha256=yXxd40V25FIF9vSEev-AxH58yZie8mZMCGQtgFmoUe8,30127
166
166
  sqlalchemy/orm/query.py,sha256=hPLslLL50lThw--5G8l3GtPgEdIY07hqIDOEO-0-wT8,118724
167
167
  sqlalchemy/orm/relationships.py,sha256=t3yqixZ41chMVOnmelNaps7jwj5vwN9dZFSB0gKK9Pw,128763
168
168
  sqlalchemy/orm/scoping.py,sha256=I_-BL8xAFQsZraFtA1wf5wgZ1WywBwBk-9OwiSAjPTM,78600
@@ -198,7 +198,7 @@ sqlalchemy/sql/dml.py,sha256=hUubKQK2dT91uMXyWuK1OpdJ6L4R_VyBw_rKH82lt7U,66232
198
198
  sqlalchemy/sql/elements.py,sha256=E0lCoqQJbWwQ34xdhdxGXqGcFgrvla_xrnSpWgs4Uwo,178317
199
199
  sqlalchemy/sql/events.py,sha256=iWjc_nm1vClDBLg4ZhDnY75CkBdnlDPSPe0MGBSmbiM,18312
200
200
  sqlalchemy/sql/expression.py,sha256=CsOkmAQgaB-Rnwe7eK60FdBC5R9kY5pczCGrVw2BwGs,7583
201
- sqlalchemy/sql/functions.py,sha256=Vvbs2ZlNmfZKfsoTRAB9MZ-cdm1AUtYc4h017HjNVtg,64861
201
+ sqlalchemy/sql/functions.py,sha256=DQkV7asOlWaBtFTqRIC663oNkloy5EUhHexjo87GtUY,64826
202
202
  sqlalchemy/sql/lambdas.py,sha256=W5b75ojie3EOm7poR27qsnQHQYdz-NxfSrgb5ATT2H0,49401
203
203
  sqlalchemy/sql/naming.py,sha256=5Tk6nm4xqy8d9gzXzDvdiqqS7IptUaf1d7IuVdslplU,6855
204
204
  sqlalchemy/sql/operators.py,sha256=h5bgu31gukGdsYsN_0-1C7IGAdSCFpBxuRjOUnu1Two,76792
@@ -221,7 +221,7 @@ sqlalchemy/testing/exclusions.py,sha256=0Byf3DIMQXN0-HOS6M2MPJ-fOm_n5MzE1yIfHgE0
221
221
  sqlalchemy/testing/pickleable.py,sha256=G3L0xL9OtbX7wThfreRjWd0GW7q0kUKcTUuCN5ETGno,2833
222
222
  sqlalchemy/testing/profiling.py,sha256=SWhWiZImJvDsNn0rQyNki70xdNxZL53ZI98ihxiykbQ,10148
223
223
  sqlalchemy/testing/provision.py,sha256=6r2FTnm-t7u8MMbWo7eMhAH3qkL0w0WlmE29MUSEIu4,14702
224
- sqlalchemy/testing/requirements.py,sha256=v2nw1Agz6wuh4QR-2b0qRImhLeMCb4khWWwmW3d9HGw,55528
224
+ sqlalchemy/testing/requirements.py,sha256=3u8lfzSOLE-_QUD6iHkhzRRbXDyEucmz2T8VRO8QG08,55757
225
225
  sqlalchemy/testing/schema.py,sha256=IImFumAdpzOyoKAs0WnaGakq8D3sSU4snD9W4LVOV3s,6513
226
226
  sqlalchemy/testing/util.py,sha256=BuA4q-8cmNhrUVqPP35Rr15MnYGSjmW0hmUdS1SI0_I,14526
227
227
  sqlalchemy/testing/warnings.py,sha256=sj4vfTtjodcfoX6FPH_Zykb4fomjmgqIYj81QPpSwH8,1546
@@ -238,7 +238,7 @@ sqlalchemy/testing/suite/__init__.py,sha256=S8TLwTiif8xX67qlZUo5I9fl9UjZAFGSzvlp
238
238
  sqlalchemy/testing/suite/test_cte.py,sha256=_GnADXRnhm37RdSRBR5SthQenTeb5VVo3HoCuO0Vifw,7262
239
239
  sqlalchemy/testing/suite/test_ddl.py,sha256=MItp-votCzvahlRqHRagte2Omyq9XUOFdFsgzCb6_-g,12031
240
240
  sqlalchemy/testing/suite/test_deprecations.py,sha256=7C6IbxRmq7wg_DLq56f1V5RCS9iVrAv3epJZQTB-dOo,5337
241
- sqlalchemy/testing/suite/test_dialect.py,sha256=87MWsFLqHzIHd-6Yn2Dz3fEr-KzsR9LNHfbcj-KRPVA,22908
241
+ sqlalchemy/testing/suite/test_dialect.py,sha256=j3srr7k2aUd_kPtJPgqI1g1aYD6ko4MvuGu1a1HQgS8,24215
242
242
  sqlalchemy/testing/suite/test_insert.py,sha256=pR0VWMQ9JJPbnANE6634PzR0VFmWMF8im6OTahc4vsQ,18824
243
243
  sqlalchemy/testing/suite/test_reflection.py,sha256=oRqwm8ZUjDdXcE3mooIg5513FpNiwEl76IoJaa_aK-Q,114101
244
244
  sqlalchemy/testing/suite/test_results.py,sha256=S7Vqqh_Wuqf7uhM8h0cBVeV1GS5GJRO_ZTVYmT7kwuc,17042
@@ -262,8 +262,8 @@ sqlalchemy/util/queue.py,sha256=w1ufhuiC7lzyiZDhciRtRz1uyxU72jRI7SWhhL-p600,1018
262
262
  sqlalchemy/util/tool_support.py,sha256=e7lWu6o1QlKq4e6c9PyDsuyFyiWe79vO72UQ_YX2pUA,6135
263
263
  sqlalchemy/util/topological.py,sha256=tbkMRY0TTgNiq44NUJpnazXR4xb9v4Q4mQ8BygMp0vY,3451
264
264
  sqlalchemy/util/typing.py,sha256=iwyZIgOJUN2o9cRz8YTH093iY5iNvpXiDQG3pce0cc4,22466
265
- sqlalchemy-2.0.42.dist-info/licenses/LICENSE,sha256=mCFyC1jUpWW2EyEAeorUOraZGjlZ5mzV203Z6uacffw,1100
266
- sqlalchemy-2.0.42.dist-info/METADATA,sha256=lEWVC12ZcvgCCZM4H1suKUiHQYJhVOWVwGRrqDMycME,9577
267
- sqlalchemy-2.0.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
268
- sqlalchemy-2.0.42.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
269
- sqlalchemy-2.0.42.dist-info/RECORD,,
265
+ sqlalchemy-2.0.43.dist-info/licenses/LICENSE,sha256=mCFyC1jUpWW2EyEAeorUOraZGjlZ5mzV203Z6uacffw,1100
266
+ sqlalchemy-2.0.43.dist-info/METADATA,sha256=6StIsiY_vKcG9DPqObgaUSVms9cc12bYmm3KbXl9yMw,9577
267
+ sqlalchemy-2.0.43.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
268
+ sqlalchemy-2.0.43.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
269
+ sqlalchemy-2.0.43.dist-info/RECORD,,