SQLAlchemy 2.1.3__py3-none-any.whl → 2.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sqlalchemy/__init__.py CHANGED
@@ -285,7 +285,7 @@ from .types import Uuid as Uuid
285
285
  from .types import VARBINARY as VARBINARY
286
286
  from .types import VARCHAR as VARCHAR
287
287
 
288
- __version__ = "2.1.3"
288
+ __version__ = "2.1.4"
289
289
 
290
290
 
291
291
  def __go(lcls: Any) -> None:
@@ -2098,6 +2098,13 @@ class MSSQLCompiler(compiler.SQLCompiler):
2098
2098
  self.process(binary.right, **kw),
2099
2099
  )
2100
2100
 
2101
+ def visit_truediv_binary(self, binary, operator, **kw):
2102
+ return (
2103
+ self.process(binary.left, **kw)
2104
+ + " / "
2105
+ + f"({self.process(binary.right, **kw)} + 0.0)"
2106
+ )
2107
+
2101
2108
  def visit_true(self, expr, **kw):
2102
2109
  return "1"
2103
2110
 
@@ -886,14 +886,18 @@ class AsyncAdapt_asyncpg_connection(
886
886
 
887
887
  assert self._transaction is None
888
888
  try:
889
- self._transaction = self._connection.transaction(
889
+ transaction = self._connection.transaction(
890
890
  isolation=self.isolation_level,
891
891
  readonly=self.readonly,
892
892
  deferrable=self.deferrable,
893
893
  )
894
- await self._transaction.start()
894
+ await transaction.start()
895
895
  except Exception as error:
896
896
  self._handle_exception(error)
897
+ else:
898
+ # only track the transaction once BEGIN has succeeded; a
899
+ # transaction that failed to start has nothing to roll back
900
+ self._transaction = transaction
897
901
 
898
902
  async def _call_and_discard(self, fn: Callable[[], Awaitable[Any]]):
899
903
  try:
@@ -339,6 +339,9 @@ class ENUM(NamedType, type_api.NativeForEmulated, sqltypes.Enum):
339
339
  kw.setdefault("_create_events", False)
340
340
  kw.setdefault("values_callable", impl.values_callable)
341
341
  kw.setdefault("omit_aliases", impl._omit_aliases)
342
+ kw.setdefault("sort_key_function", impl._sort_key_function)
343
+ kw.setdefault("create_constraint", impl.create_constraint)
344
+ kw.setdefault("length", impl.length)
342
345
  kw.setdefault("_adapted_from", impl)
343
346
 
344
347
  return cls(**kw)
@@ -514,6 +517,19 @@ class DOMAIN(NamedType, sqltypes.SchemaType):
514
517
  def __test_init__(cls):
515
518
  return cls("name", sqltypes.Integer)
516
519
 
520
+ def adapt(self, cls, **kw):
521
+ # the DOMAIN-specific arguments are keyword-only, which are not
522
+ # picked up by util.constructor_copy(); pass them explicitly so
523
+ # that copy(), to_metadata() and dialect adaptation retain them
524
+ kw.setdefault("collation", self.collation)
525
+ kw.setdefault("collation_schema", self.collation_schema)
526
+ kw.setdefault("default", self.default)
527
+ kw.setdefault("constraint_name", self.constraint_name)
528
+ kw.setdefault("not_null", self.not_null)
529
+ kw.setdefault("check", self.check)
530
+ kw.setdefault("create_type", self.create_type)
531
+ return super().adapt(cls, **kw)
532
+
517
533
 
518
534
  class CreateEnumType(schema._CreateDropBase):
519
535
  __visit_name__ = "create_enum_type"
@@ -91,6 +91,37 @@ class _NoRow(Enum):
91
91
  _NO_ROW = _NoRow._NO_ROW
92
92
 
93
93
 
94
+ def _list_map(
95
+ fn: Callable[[Any], _T], /
96
+ ) -> Callable[[Sequence[Any]], list[_T]]:
97
+ """Return a callable that applies ``fn`` to each element of a sequence,
98
+ returning a list.
99
+
100
+ This function serves to prevent a reference cycle between ``fn`` and
101
+ the closure scope of the function that defines it, when compiled with
102
+ Cython.
103
+
104
+ """
105
+ if cython.compiled:
106
+
107
+ def list_map(rows: Sequence[Any], /) -> list[_T]:
108
+ size: cython.Py_hash_t = len(rows)
109
+ i: cython.Py_ssize_t
110
+ result: list = PyList_New(size)
111
+ for i in range(size):
112
+ row: object = fn(rows[i])
113
+ Py_INCREF(row)
114
+ PyList_SET_ITEM(result, i, row)
115
+ return result
116
+
117
+ else:
118
+
119
+ def list_map(rows: Sequence[Any], /) -> list[_T]:
120
+ return [fn(row) for row in rows]
121
+
122
+ return list_map
123
+
124
+
94
125
  class BaseResultInternal(Generic[_R]):
95
126
  __slots__ = ()
96
127
 
@@ -245,22 +276,7 @@ class BaseResultInternal(Generic[_R]):
245
276
  row = log_row(row)
246
277
  return row
247
278
 
248
- if cython.compiled:
249
-
250
- def many_rows(rows: Sequence[Any], /) -> list[Any]:
251
- size: cython.Py_hash_t = len(rows)
252
- i: cython.Py_ssize_t
253
- result: list = PyList_New(size)
254
- for i in range(size):
255
- row: object = single_row(rows[i])
256
- Py_INCREF(row)
257
- PyList_SET_ITEM(result, i, row)
258
- return result
259
-
260
- else:
261
-
262
- def many_rows(rows: Sequence[Any], /) -> list[Any]:
263
- return [single_row(row) for row in rows]
279
+ many_rows = _list_map(single_row)
264
280
 
265
281
  if flag == _FLAG_SCALAR_TO_TUPLE or has_log_row:
266
282
  # scalar-source rows have no tuple form; row logging requires
@@ -279,27 +295,12 @@ class BaseResultInternal(Generic[_R]):
279
295
  input_row = tuple(input_row)
280
296
  return input_row
281
297
 
282
- if cython.compiled:
283
-
284
- def interim_rows(rows: Sequence[Any], /) -> Sequence[Any]:
285
- size: cython.Py_hash_t = len(rows)
286
- i: cython.Py_ssize_t
287
- result: list = PyList_New(size)
288
- for i in range(size):
289
- row: object = single_interim_row(rows[i])
290
- Py_INCREF(row)
291
- PyList_SET_ITEM(result, i, row)
292
- return result
293
-
294
- else:
295
-
296
- def interim_rows(rows: Sequence[Any], /) -> Sequence[Any]:
297
- return [single_interim_row(row) for row in rows]
298
+ interim_rows = _list_map(single_interim_row)
298
299
 
299
300
  return single_row, many_rows, interim_rows # type: ignore[return-value] # noqa: E501
300
301
 
301
302
  @HasMemoized_ro_memoized_attribute
302
- def _iterator_getter(self) -> Callable[[], Iterator[_R]]:
303
+ def _iterator_getter(self) -> Callable[[Self], Iterator[_R]]:
303
304
  make_row = self._row_getter[0]
304
305
 
305
306
  post_creational_filter = self._post_creational_filter
@@ -308,7 +309,7 @@ class BaseResultInternal(Generic[_R]):
308
309
  uniques: set
309
310
  uniques, strategy = self._unique_strategy
310
311
 
311
- def iterrows() -> Iterator[_R]:
312
+ def iterrows(self: Self) -> Iterator[_R]:
312
313
  for raw_row in self._fetchiter_impl():
313
314
  row = (
314
315
  make_row(raw_row) if make_row is not None else raw_row
@@ -323,7 +324,7 @@ class BaseResultInternal(Generic[_R]):
323
324
 
324
325
  else:
325
326
 
326
- def iterrows() -> Iterator[_R]:
327
+ def iterrows(self: Self) -> Iterator[_R]:
327
328
  for raw_row in self._fetchiter_impl():
328
329
  row = (
329
330
  make_row(raw_row) if make_row is not None else raw_row
@@ -617,7 +618,7 @@ class BaseResultInternal(Generic[_R]):
617
618
  return row # type: ignore[return-value]
618
619
 
619
620
  def _iter_impl(self) -> Iterator[_R]:
620
- return self._iterator_getter()
621
+ return self._iterator_getter(self)
621
622
 
622
623
  def _next_impl(self) -> _R:
623
624
  row = self._onerow_getter(self)
sqlalchemy/engine/base.py CHANGED
@@ -1257,30 +1257,33 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
1257
1257
 
1258
1258
  """
1259
1259
 
1260
- if self._transaction:
1261
- self._transaction.close()
1262
- skip_reset = True
1263
- else:
1264
- skip_reset = False
1265
-
1266
- if self._dbapi_connection is not None:
1267
- conn = self._dbapi_connection
1268
-
1269
- # as we just closed the transaction, close the connection
1270
- # pool connection without doing an additional reset
1271
- if skip_reset:
1272
- cast("_ConnectionFairy", conn)._close_special(
1273
- transaction_reset=True
1274
- )
1275
- else:
1276
- conn.close()
1277
-
1278
- # There is a slight chance that conn.close() may have
1279
- # triggered an invalidation here in which case
1280
- # _dbapi_connection would already be None, however usually
1281
- # it will be non-None here and in a "closed" state.
1282
- self._dbapi_connection = None
1283
- self.__can_reconnect = False
1260
+ # if the rollback here raises, skip_reset stays False and the pool
1261
+ # does its own reset of the connection, invalidating it if that
1262
+ # fails also. see #13657
1263
+ skip_reset = False
1264
+ try:
1265
+ if self._transaction:
1266
+ self._transaction.close()
1267
+ skip_reset = True
1268
+ finally:
1269
+ if self._dbapi_connection is not None:
1270
+ conn = self._dbapi_connection
1271
+
1272
+ # as we just closed the transaction, close the connection
1273
+ # pool connection without doing an additional reset
1274
+ if skip_reset:
1275
+ cast("_ConnectionFairy", conn)._close_special(
1276
+ transaction_reset=True
1277
+ )
1278
+ else:
1279
+ conn.close()
1280
+
1281
+ # There is a slight chance that conn.close() may have
1282
+ # triggered an invalidation here in which case
1283
+ # _dbapi_connection would already be None, however usually
1284
+ # it will be non-None here and in a "closed" state.
1285
+ self._dbapi_connection = None
1286
+ self.__can_reconnect = False
1284
1287
 
1285
1288
  # special case to handle mypy issue:
1286
1289
  # https://github.com/python/mypy/issues/20651
@@ -142,7 +142,7 @@ class LoggingTokenCharacteristic(ConnectionCharacteristic):
142
142
  if value:
143
143
  conn._message_formatter = lambda msg: "[%s] %s" % (value, msg)
144
144
  else:
145
- del conn._message_formatter
145
+ conn._message_formatter = None
146
146
 
147
147
  def get_characteristic(
148
148
  self, dialect: Dialect, dbapi_conn: DBAPIConnection
@@ -2137,6 +2137,15 @@ class DefaultExecutionContext(ExecutionContext):
2137
2137
  ):
2138
2138
  self._setup_out_parameters(result)
2139
2139
 
2140
+ if not result._soft_closed and not result._metadata.returns_rows:
2141
+ # no results, get rowcount (which requires open cursor on
2142
+ # some drivers) and release the cursor. this applies to all
2143
+ # statements that don't return rows, including DML, text(),
2144
+ # DDL and SAVEPOINT
2145
+ if self._rowcount is None:
2146
+ self._rowcount = self.cursor.rowcount
2147
+ result._soft_close()
2148
+
2140
2149
  self._soft_closed = result._soft_closed
2141
2150
 
2142
2151
  if yp:
@@ -2272,12 +2281,6 @@ class DefaultExecutionContext(ExecutionContext):
2272
2281
  # the rows have all been fetched however.
2273
2282
  assert result._metadata.returns_rows
2274
2283
 
2275
- elif not result._metadata.returns_rows:
2276
- # no results, get rowcount
2277
- # (which requires open cursor on some drivers)
2278
- if self._rowcount is None:
2279
- self._rowcount = self.cursor.rowcount
2280
- result._soft_close()
2281
2284
  elif self.isupdate or self.isdelete:
2282
2285
  if self._rowcount is None:
2283
2286
  self._rowcount = self.cursor.rowcount
sqlalchemy/ext/mutable.py CHANGED
@@ -840,6 +840,12 @@ class MutableDict(Mutable, Dict[_KT, _VT]):
840
840
  dict.update(self, *a, **kw)
841
841
  self.changed()
842
842
 
843
+ def __ior__( # type: ignore[override,misc]
844
+ self, other: Any
845
+ ) -> MutableDict[_KT, _VT]:
846
+ self.update(other)
847
+ return self
848
+
843
849
  if TYPE_CHECKING:
844
850
 
845
851
  @overload
@@ -949,6 +955,11 @@ class MutableList(Mutable, List[_T]):
949
955
  self.extend(x)
950
956
  return self
951
957
 
958
+ def __imul__(self, n: SupportsIndex) -> MutableList[_T]:
959
+ list.__imul__(self, n)
960
+ self.changed()
961
+ return self
962
+
952
963
  def insert(self, i: SupportsIndex, x: _T) -> None:
953
964
  list.insert(self, i, x)
954
965
  self.changed()
@@ -67,6 +67,7 @@ from ..sql import expression
67
67
  from ..sql._annotated_cols import TypedColumns
68
68
  from ..sql.base import _NoArg
69
69
  from ..sql.schema import Column
70
+ from ..sql.schema import MetaData
70
71
  from ..sql.schema import Table
71
72
  from ..util import topological
72
73
  from ..util.typing import _AnnotationScanType
@@ -80,7 +81,6 @@ if TYPE_CHECKING:
80
81
  from .decl_api import declared_attr
81
82
  from .instrumentation import ClassManager
82
83
  from ..sql.elements import NamedColumn
83
- from ..sql.schema import MetaData
84
84
  from ..sql.selectable import FromClause
85
85
 
86
86
  _T = TypeVar("_T", bound=Any)
@@ -1525,7 +1525,9 @@ class _DeclarativeMapperConfig(_MapperConfig, _ClassScanAbstractConfig):
1525
1525
  value = SynonymProperty(value.key)
1526
1526
  setattr(cls, k, value)
1527
1527
 
1528
- if k in ("metadata", "registry"):
1528
+ if k == "registry" or (
1529
+ k == "metadata" and not isinstance(value, MetaData)
1530
+ ):
1529
1531
  noun = {
1530
1532
  "metadata": "MetaData instance",
1531
1533
  "registry": "Declarative class registry",
@@ -3136,12 +3136,20 @@ class SQLCompiler(Compiled):
3136
3136
  self.process(element.aggregate_order_by, **kw),
3137
3137
  )
3138
3138
 
3139
- def visit_aggregate_strings_func(self, fn, *, use_function_name, **kw):
3139
+ def visit_aggregate_strings_func(
3140
+ self, fn, *, use_function_name=None, **kw
3141
+ ):
3140
3142
  # aggreagate_order_by attribute is present if visit_function
3141
3143
  # gave us a Function with aggregate_orderby_inline() as the inner
3142
3144
  # contents
3143
3145
  order_by = getattr(fn.clauses, "aggregate_order_by", None)
3144
3146
 
3147
+ if use_function_name is None:
3148
+ raise exc.CompileError(
3149
+ "%s dialect does not support the aggregate_strings function"
3150
+ % self.dialect.name
3151
+ )
3152
+
3145
3153
  literal_exec = dict(kw)
3146
3154
  literal_exec["literal_execute"] = True
3147
3155
 
@@ -3338,7 +3346,7 @@ class SQLCompiler(Compiled):
3338
3346
  util.warn(
3339
3347
  "Column-expression-level unary distinct() "
3340
3348
  "should not be used outside of an aggregate "
3341
- "function. For general 'SELECT DISTINCT' support"
3349
+ "function. For general 'SELECT DISTINCT' support "
3342
3350
  "use select().distinct()."
3343
3351
  )
3344
3352
 
@@ -7092,6 +7100,11 @@ class StrSQLCompiler(SQLCompiler):
7092
7100
  def get_from_hint_text(self, table, text):
7093
7101
  return "[%s]" % text
7094
7102
 
7103
+ def visit_aggregate_strings_func(self, fn, **kw):
7104
+ return super().visit_aggregate_strings_func(
7105
+ fn, use_function_name=fn.name, **kw
7106
+ )
7107
+
7095
7108
  def visit_regexp_match_op_binary(self, binary, operator, **kw):
7096
7109
  return self._generate_generic_binary(binary, " <regexp> ", **kw)
7097
7110
 
sqlalchemy/sql/dml.py CHANGED
@@ -1263,6 +1263,10 @@ class Insert(ValuesBase, HasSyntaxExtensions[Literal["post_values"]]):
1263
1263
  "_return_defaults_columns",
1264
1264
  InternalTraversal.dp_clauseelement_tuple,
1265
1265
  ),
1266
+ (
1267
+ "_supplemental_returning",
1268
+ InternalTraversal.dp_clauseelement_tuple,
1269
+ ),
1266
1270
  ("_sort_by_parameter_order", InternalTraversal.dp_boolean),
1267
1271
  ]
1268
1272
  + HasPrefixes._has_prefixes_traverse_internals
@@ -1663,6 +1667,10 @@ class Update(
1663
1667
  "_return_defaults_columns",
1664
1668
  InternalTraversal.dp_clauseelement_tuple,
1665
1669
  ),
1670
+ (
1671
+ "_supplemental_returning",
1672
+ InternalTraversal.dp_clauseelement_tuple,
1673
+ ),
1666
1674
  ]
1667
1675
  + HasPrefixes._has_prefixes_traverse_internals
1668
1676
  + DialectKWArgs._dialect_kwargs_traverse_internals
@@ -1939,6 +1939,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
1939
1939
  kw.setdefault("create_constraint", self.create_constraint)
1940
1940
  kw.setdefault("length", self.length)
1941
1941
  kw.setdefault("omit_aliases", self._omit_aliases)
1942
+ kw.setdefault("sort_key_function", self._sort_key_function)
1942
1943
  return kw
1943
1944
 
1944
1945
  def adapt_to_emulated(self, impltype, **kw):
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
  import argparse
12
12
  import collections
13
13
  from functools import update_wrapper
14
+ import hashlib
14
15
  import inspect
15
16
  import itertools
16
17
  import operator
@@ -545,6 +546,31 @@ def pytest_runtest_call(item):
545
546
  _current_report = None
546
547
 
547
548
 
549
+ _MAX_PARAM_ID_LEN = 60
550
+
551
+
552
+ def _shorten_param_id(ident):
553
+ """shorten a very long parameter id for console / junit output.
554
+
555
+ keeps the leading portion of the string and appends a short digest of
556
+ the full value, so that ids remain stable and distinct.
557
+
558
+ """
559
+ if len(ident) <= _MAX_PARAM_ID_LEN:
560
+ return ident
561
+ digest = hashlib.md5(
562
+ ident.encode("utf-8"), usedforsecurity=False
563
+ ).hexdigest()[:8]
564
+ return f"{ident[:_MAX_PARAM_ID_LEN - 12]}...{digest}"
565
+
566
+
567
+ def pytest_make_parametrize_id(config, val, argname):
568
+ if isinstance(val, str) and len(val) > _MAX_PARAM_ID_LEN:
569
+ return _shorten_param_id(val.encode("unicode_escape").decode("ascii"))
570
+ else:
571
+ return None
572
+
573
+
548
574
  def pytest_runtest_logreport(report):
549
575
  global _current_report
550
576
  if report.when == "call":
@@ -768,7 +794,8 @@ class PytestFixtureFunctions(plugin_base.FixtureFunctions):
768
794
  parameters,
769
795
  param_exclusions,
770
796
  "-".join(
771
- comb_fn(getter(arg)) for getter, comb_fn in fns
797
+ _shorten_param_id(comb_fn(getter(arg)))
798
+ for getter, comb_fn in fns
772
799
  ),
773
800
  )
774
801
  )
@@ -119,7 +119,9 @@ class ProfileStatsFile:
119
119
  def platform_key(self) -> PlatformKey:
120
120
  db = config.db
121
121
  assert db is not None, "no database configured"
122
+ return self.platform_key_for(db)
122
123
 
124
+ def platform_key_for(self, db: Any) -> PlatformKey:
123
125
  dbapi_flags = []
124
126
  if db.dialect.is_async:
125
127
  dbapi_flags.append("async")
@@ -144,6 +146,27 @@ class ProfileStatsFile:
144
146
  cextensions=has_compiled_ext(),
145
147
  )
146
148
 
149
+ def has_platform(self, db: Any) -> bool:
150
+ """Return True if profiling tests should run against the given
151
+ engine.
152
+
153
+ This is the case when the profile file has counts for at least
154
+ one test on the engine's platform, or when profiles are being
155
+ written. Used to keep profiling test classes from being generated
156
+ at all for drivers / interpreters that would only have each
157
+ test skip from within :func:`.count_functions`, after the class
158
+ has already run its fixture setup.
159
+
160
+ """
161
+ if self.write:
162
+ return True
163
+
164
+ platform_key = self.platform_key_for(db)
165
+ return any(
166
+ platform_key in self.profiles.platforms_for(test_key)
167
+ for test_key in self.profiles.test_keys()
168
+ )
169
+
147
170
  def has_stats(self) -> bool:
148
171
  return (
149
172
  self.profiles.counts_for(_current_test, self.platform_key)
@@ -1271,6 +1271,11 @@ class SuiteRequirements(Requirements):
1271
1271
  to represent very large values."""
1272
1272
  return exclusions.open()
1273
1273
 
1274
+ @property
1275
+ def float_cast(self):
1276
+ """target backend supports CAST to a FLOAT type"""
1277
+ return exclusions.open()
1278
+
1274
1279
  @property
1275
1280
  def precision_numerics_many_significant_digits(self):
1276
1281
  """target backend supports values with many digits on both sides,
@@ -1646,6 +1646,12 @@ class LikeFunctionsTest(fixtures.TablesTest):
1646
1646
 
1647
1647
 
1648
1648
  class ComputedColumnTest(fixtures.TablesTest):
1649
+ """This test uses Computed without specifying the persisted mode.
1650
+ On some postgresql version the compiler may raise a warning.
1651
+ Use `warns_if` in the computed_columns requirement to catch the warning
1652
+ conditionally on the postgresql version.
1653
+ """
1654
+
1649
1655
  __sparse_driver_backend__ = True
1650
1656
  __requires__ = ("computed_columns",)
1651
1657
 
@@ -34,6 +34,7 @@ from ... import Date
34
34
  from ... import DateTime
35
35
  from ... import Enum
36
36
  from ... import Float
37
+ from ... import func
37
38
  from ... import Integer
38
39
  from ... import Interval
39
40
  from ... import JSON
@@ -1120,6 +1121,80 @@ class TrueDivTest(fixtures.TestBase):
1120
1121
  1,
1121
1122
  )
1122
1123
 
1124
+ @testing.combinations(
1125
+ ("typed", lambda divisor: divisor),
1126
+ ("untyped_function", lambda divisor: func.nullif(divisor, 0)),
1127
+ id_="ia",
1128
+ argnames="wrap_divisor",
1129
+ )
1130
+ @testing.combinations(
1131
+ (
1132
+ "float",
1133
+ "7.0",
1134
+ "3.5",
1135
+ Float(),
1136
+ "2",
1137
+ testing.requires.float_cast,
1138
+ ),
1139
+ ("numeric_whole_number", "1", "2.00", Numeric(10, 2), "0.5"),
1140
+ (
1141
+ "numeric_many_decimals",
1142
+ "5.02797279921331664759",
1143
+ "0.71828182845904523537",
1144
+ Numeric(22, 20),
1145
+ "7",
1146
+ (
1147
+ testing.requires.precision_numerics_many_significant_digits
1148
+ ).as_skips(),
1149
+ ),
1150
+ (
1151
+ "numeric_tiny_divisor",
1152
+ "0.00000000000049382716",
1153
+ "0.00000000000012345679",
1154
+ Numeric(38, 20),
1155
+ "4",
1156
+ ),
1157
+ (
1158
+ "numeric_38_digit_divisor",
1159
+ "30000000000000000000000000000000000000",
1160
+ "10000000000000000000000000000000000000",
1161
+ Numeric(38, 0),
1162
+ "3",
1163
+ ),
1164
+ id_="iaaaa",
1165
+ argnames="left, right, type_, expected",
1166
+ )
1167
+ def test_truediv_divisor(
1168
+ self, connection, wrap_divisor, left, right, type_, expected
1169
+ ):
1170
+ """test #13631
1171
+
1172
+ each pair of operands is divided with the divisor's type visible to
1173
+ SQLAlchemy ("typed") and hidden behind a function of unknown type
1174
+ ("untyped_function"). The operand pairs each rule out an approach
1175
+ to forcing true division that loses precision on some backend:
1176
+
1177
+ * float - a bare CAST(x AS NUMERIC) truncates 3.5 on SQL Server,
1178
+ where NUMERIC means NUMERIC(18, 0)
1179
+ * numeric_whole_number - SQLite stores 2.00 as the integer 2, so a
1180
+ CAST to NUMERIC alone gives integer division
1181
+ * numeric_many_decimals - CAST to FLOAT gives 7.000000000000001.
1182
+ skipped rather than xfailed on backends without decimal
1183
+ arithmetic such as SQLite, where whether the result comes out
1184
+ as 7 depends on how the platform parses the literals to floats
1185
+ * numeric_tiny_divisor - on SQL Server, multiplying a
1186
+ NUMERIC(38, 20) by 1.0 reduces the scale and rounds the divisor
1187
+ * numeric_38_digit_divisor - on SQL Server, multiplying a
1188
+ NUMERIC(38, 0) by 1.0 overflows
1189
+
1190
+ """
1191
+ expr = cast(literal_column(left), type_) / wrap_divisor(
1192
+ cast(literal_column(right), type_)
1193
+ )
1194
+ result = connection.scalar(select(expr))
1195
+
1196
+ eq_(decimal.Decimal(str(result)), decimal.Decimal(expected))
1197
+
1123
1198
 
1124
1199
  class NumericTest(_LiteralRoundTripFixture, fixtures.TestBase):
1125
1200
  __backend__ = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Database Abstraction Library
5
5
  Author-email: Mike Bayer <mike_mp@zzzcomputing.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- sqlalchemy/__init__.py,sha256=qa3skGcwWR4Z4U7px2QG0OA-nKAGNHLwsJ9q_9k52AI,13562
1
+ sqlalchemy/__init__.py,sha256=_uCHzwTy6ReaEDykCpWE3XmTA1iJqz7Xej7ma48rhS8,13562
2
2
  sqlalchemy/events.py,sha256=vEDW04X_PMhMw5y0_95tbEzht3wua8_gnkMBJstYNRw,525
3
3
  sqlalchemy/exc.py,sha256=CKyzx9WSeWTYxueZLAtTFpZTNKSon0YlpGt9v-C8dl8,27503
4
4
  sqlalchemy/inspection.py,sha256=2mcgybcOVuQDB2bSIhwidrMteP9qOpEbmehyT2lrnt8,5661
@@ -15,7 +15,7 @@ sqlalchemy/dialects/_typing.py,sha256=oUmFIxU_AVXmJyRXiE5IsDvKFh8HvAcyiGMQaWmY7M
15
15
  sqlalchemy/dialects/type_migration_guidelines.txt,sha256=-uHNdmYFGB7bzUNT6i8M5nb4j6j9YUKAtW4lcBZqsMg,8239
16
16
  sqlalchemy/dialects/mssql/__init__.py,sha256=j1pNh_cHkSRoTzCjUQcjRNZEVtIfdFCgkTT87mgSs5s,1913
17
17
  sqlalchemy/dialects/mssql/aioodbc.py,sha256=RS9TZI4cDa9My9YHQbWW2nD3lUBx-s8aQZ7G2080dDA,2005
18
- sqlalchemy/dialects/mssql/base.py,sha256=KiiuQ7DTUzovfE4ipKKU2C1D99xpBvUGQS6R6_FjfZY,160720
18
+ sqlalchemy/dialects/mssql/base.py,sha256=Jt_N-oZt4_uaqPqVDFvLKN1Xyj9eJSnlwKORu5S33vQ,160932
19
19
  sqlalchemy/dialects/mssql/information_schema.py,sha256=xoO2mKqq99GBN9osJaaYjLw3VQg4YB8rFrh5K6gGfaI,10544
20
20
  sqlalchemy/dialects/mssql/json.py,sha256=SgcLWK-xmy_5FCzgxoz2Xa8pnJemVFCuiNQ6aFApHyk,5338
21
21
  sqlalchemy/dialects/mssql/mssqlpython.py,sha256=Boe6dsrabesUauvFUDIHeFXDXLBh3qi2pgqYCqI__eI,7832
@@ -54,14 +54,14 @@ sqlalchemy/dialects/oracle/vector.py,sha256=v7zf88XX7ICORNuycw3da1d5G7KHmYytNtU6
54
54
  sqlalchemy/dialects/postgresql/__init__.py,sha256=uqvgSydDm7mbLeLtuARI8AXFkcHPCkqTgd8UNDKznxs,3988
55
55
  sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=3btoOFqAcFmkectNE1Vgs7cOv4ccF28xIQZIDEYwOWA,7178
56
56
  sqlalchemy/dialects/postgresql/array.py,sha256=fHA_e2ez1SGHXa4BrXxbwCKMEkrDxn2GFzWJOjZrM0c,17907
57
- sqlalchemy/dialects/postgresql/asyncpg.py,sha256=IE2FNLURUBGvsofgGpqCJ6sx8Qd_alUsOz2Mb0YkeVk,42376
57
+ sqlalchemy/dialects/postgresql/asyncpg.py,sha256=ne9Fr2-dWoD0qLjpsLxTvkRmYGD1CtBR-wSeG0E1Or8,42563
58
58
  sqlalchemy/dialects/postgresql/base.py,sha256=pMvzDesY5HsgmNnfPDa7HUD7rU_W11QlZfQDM4Y3A44,214444
59
59
  sqlalchemy/dialects/postgresql/bitstring.py,sha256=-AowJ4DqlcT0PcFXML44TU4VND_hFuvtkwqUgKBcE1I,10603
60
60
  sqlalchemy/dialects/postgresql/dml.py,sha256=ckxPBWuAXCRKkSFvdmwEppvirvczFY-voniqgC3X6DA,12959
61
61
  sqlalchemy/dialects/postgresql/ext.py,sha256=ZgdhLnWDYcm3RQ0XlUtrsW3WF1YRcGmzEK-8sdg16wc,19603
62
62
  sqlalchemy/dialects/postgresql/hstore.py,sha256=i14ZMZ2oml63W1OMfLiq_IAFAFbh-QUPKT1_vN5obY0,12728
63
63
  sqlalchemy/dialects/postgresql/json.py,sha256=LGa5zmfU5tqwcJSKpWwy9azQHklNqNLBuK5HXIUNEKc,14996
64
- sqlalchemy/dialects/postgresql/named_types.py,sha256=_ElGK3gHGMrgmClftppPF4Zlb-yRQ7lf16hIjwxqSh4,18617
64
+ sqlalchemy/dialects/postgresql/named_types.py,sha256=qwxZKnBtzujRe6lYJaH8v0kRSCmS--fT6UrHAwDPbXE,19459
65
65
  sqlalchemy/dialects/postgresql/operators.py,sha256=CndNmzeu8ytmDA497ZeTxcbHxSVlVrOCmGREuUfjWhU,2840
66
66
  sqlalchemy/dialects/postgresql/pg8000.py,sha256=Hb54csBYBbFZS5wkJFH63BDPxp1E1bQtaH_r_U-pSNQ,18392
67
67
  sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=7NWViTqCyWjgWiY3buavYo-CA2h-nxwfpc-vzYnNotg,10475
@@ -81,14 +81,14 @@ sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=k3qMGR2VvHHT29vsJ-eJYUe7jIXhgGS
81
81
  sqlalchemy/dialects/sqlite/pysqlite.py,sha256=_jD3LdPia8SV9ma3pz9zm9g46Rp9m4nQ4Hyd6yMDnVk,36592
82
82
  sqlalchemy/engine/__init__.py,sha256=Nn05AJVAfZn_sToGjt09vBcZET343-wYwl-oo2Fg6R8,2818
83
83
  sqlalchemy/engine/_processors_cy.py,sha256=Q68s_s1zINR4nFQCfUfMJqjdRWI-J2BIF01tpgP7LiE,2498
84
- sqlalchemy/engine/_result_cy.py,sha256=da5adBl8TpsBdL3dgT2L3HPv2R38376LraS2YqP7A9U,24230
84
+ sqlalchemy/engine/_result_cy.py,sha256=Lk65cmkNwSM0tDRBFHCMB8mdMmaAPjP-LY9L6qwdRqo,24006
85
85
  sqlalchemy/engine/_row_cy.py,sha256=GUX53624m7KpLOTQhsi5aMHv-ypdB4caWMQPGkuAIC4,7413
86
86
  sqlalchemy/engine/_util_cy.py,sha256=AicwKLv6IuJ46WNVhEkT0MUPEFS5z8ltxKKTIA_HyNI,4504
87
- sqlalchemy/engine/base.py,sha256=3knlkSPdTmEYeEwbgkFRJ_EgqK0474aXOrX8KGBTeNk,123741
88
- sqlalchemy/engine/characteristics.py,sha256=wRg6uE8NYSH38wvp0CMY2U5hv9u3VTOkTHyJeXYrkvQ,4765
87
+ sqlalchemy/engine/base.py,sha256=Aq3OSbeo-9re9IDDJLNwMUWfnJUfReIexBqHdXGeCDU,124009
88
+ sqlalchemy/engine/characteristics.py,sha256=QctD35XaPso4brk2W8zQmdomMkRRV6X7vbeo96eUvA4,4768
89
89
  sqlalchemy/engine/create.py,sha256=7G0aidSUYtghZjyv7C19TZcv_3o3pS62UXj-sRgsWVs,33661
90
90
  sqlalchemy/engine/cursor.py,sha256=B5NmeqIEsOiPU8QaE4dXh7s1cSp2_5erukysuKs7ipo,85474
91
- sqlalchemy/engine/default.py,sha256=mpIQfaHFhojYTJuMmxWpNvtvrZTYybjjZpC_Fp5eeVg,94592
91
+ sqlalchemy/engine/default.py,sha256=u4h11cJackuZ-ImOinv4JbSTiiQGO3I6Em9i8LjL7L8,94768
92
92
  sqlalchemy/engine/events.py,sha256=d-BzEd6M8dG8q22w3N6RX98cDcrANTgYyPjb5oFQznE,37475
93
93
  sqlalchemy/engine/interfaces.py,sha256=IKYAaYa-iA44O3bsxsmhyNWwIf3FToX7Wx8N-I9xD0E,123015
94
94
  sqlalchemy/engine/mock.py,sha256=i9J-z0NsL78VlqC85MRgo5SNNQU256UtEqficO3DCwY,4155
@@ -114,7 +114,7 @@ sqlalchemy/ext/horizontal_shard.py,sha256=uWbe4iP7pDzYR-8jBmrifyOEPTn7N1_a33YKma
114
114
  sqlalchemy/ext/hybrid.py,sha256=vfyfb54vRd6S_JpbWsswlketD6mFX-OeK9I06R2mN70,64744
115
115
  sqlalchemy/ext/indexable.py,sha256=MhqYUA_KuB95t3kzKTtUwEbh5OMhuNNujwyJ0ENKZrM,11761
116
116
  sqlalchemy/ext/instrumentation.py,sha256=4DL2RBcYE8bymiRNTjEDYevBpXTftECjFwg90DBqEuw,15708
117
- sqlalchemy/ext/mutable.py,sha256=xiKl9pl_l_YgoSV3L5p5c0m3lh3UfduYIrmAv7lUIL0,37115
117
+ sqlalchemy/ext/mutable.py,sha256=vjS-EhR2GW26fSb61woPucZEEzEJCxMYt6AcxxX-NBg,37404
118
118
  sqlalchemy/ext/orderinglist.py,sha256=ItEwaVvzeYdoSwj0Ipao0xi8BThXCeLZqFxChsQuCo8,15157
119
119
  sqlalchemy/ext/serializer.py,sha256=uOicJGYMBW5S1VXDGU61UIiSfLT3ricVVt5a9oKJJVI,6113
120
120
  sqlalchemy/ext/asyncio/__init__.py,sha256=0MnGUog_nAVA71tE0IAiyqVbOn2Dpt71QpMAf5Y1-Lk,1410
@@ -138,7 +138,7 @@ sqlalchemy/orm/clsregistry.py,sha256=dmIYdM2VTyLVLYiale_x9-JGSK6GpQ0JOqsZuxfSfQE
138
138
  sqlalchemy/orm/collections.py,sha256=sc-KrVjRZ1EKArBPn2lqmH9QX7HKr6d2oX0fPV5a4h8,50026
139
139
  sqlalchemy/orm/context.py,sha256=F1poIa-NftQTO_cNUfSnbJKjbPQs9kWy6tBEyznTD_8,120868
140
140
  sqlalchemy/orm/decl_api.py,sha256=gK5IGcbAXMdCN6SzprewMhfbXiZe1yO1Ghv2y2z3EHA,78721
141
- sqlalchemy/orm/decl_base.py,sha256=V30p5ILt8jRGhR3bv-LaQu2mZtSEys5TTMmSS9NUj8g,87679
141
+ sqlalchemy/orm/decl_base.py,sha256=DF132BFspNgbSrnVIA63RNk8b8MIzfOe4vk6wqKMQ8g,87748
142
142
  sqlalchemy/orm/dependency.py,sha256=gqUUJi7LMF2RIEvq7hw_w-WfbpW8CXzSaDW5QTW5NlQ,47739
143
143
  sqlalchemy/orm/descriptor_props.py,sha256=2R4NUuPIyinL5RrTbOAYebb3ZJNJLnT6_J2QCWy0nEM,42407
144
144
  sqlalchemy/orm/dynamic.py,sha256=7wyr0eM7xIRqWQk6jcoHjqGYatORwImRbrysJhQaGm8,9968
@@ -184,11 +184,11 @@ sqlalchemy/sql/annotation.py,sha256=gkFPOq3qGo6fahIxTwXgxCdcEyiD8W4CH0q01d1E0mo,
184
184
  sqlalchemy/sql/base.py,sha256=1xkh7mgC25GVZS8tczZbwDAxRvdumoaW3UPrJXbynbQ,98615
185
185
  sqlalchemy/sql/cache_key.py,sha256=OihJqOVsdU0F_IShr0FIptTRHtIlM1UIZ_5LR8LQd0Q,28872
186
186
  sqlalchemy/sql/coercions.py,sha256=XyV6wyiIHHD6ViGdZhLMQ8OVWNk4_LyBvv15t504UYI,39144
187
- sqlalchemy/sql/compiler.py,sha256=2GLICV4e506Y65tuiULpgjWXTSwO6LMYxHL8jntG1i4,301442
187
+ sqlalchemy/sql/compiler.py,sha256=9vNcKzGXY-gqOl_R42tzwQR0V0MjBNn79OmJmjiVjQo,301830
188
188
  sqlalchemy/sql/crud.py,sha256=1qtcYPM-p9DsjMaqixYgKVSPuQEK43QEsAdgmXYY_ok,61581
189
189
  sqlalchemy/sql/ddl.py,sha256=6vfeMHtVcV6vWBQLxDZ906CHBnnadG5C8vmQNbpY5k8,65048
190
190
  sqlalchemy/sql/default_comparator.py,sha256=MC1yWLbOArtLAsVWho_08Jk8ogP6mEgtRP-IxBYFtv4,18684
191
- sqlalchemy/sql/dml.py,sha256=m-rOUqw5SmcTXHVt92ICLiEoHuRLG85cAIZEUTNCSMA,71651
191
+ sqlalchemy/sql/dml.py,sha256=-j0vtiT_JPyZoxUxtgOwtSOT3BHK_S6BlBR5vzfrzcM,71911
192
192
  sqlalchemy/sql/elements.py,sha256=zPomd9yuJm5DJ9Jie87V5w2M8Zcv6mrd1nx174QKSmo,196427
193
193
  sqlalchemy/sql/events.py,sha256=xpqx3irkG8N3gysXAViLCqXXke9KCSU-6w4zZUdYXzw,18312
194
194
  sqlalchemy/sql/expression.py,sha256=SStPoDh7LSBTj6-4_KZSzyH0i5f493lqx4EnwcGPCZU,8342
@@ -199,7 +199,7 @@ sqlalchemy/sql/operators.py,sha256=91HJboxRkDFNHx9JlpO9hsss7wqTfeoANOxjMFszTt0,8
199
199
  sqlalchemy/sql/roles.py,sha256=QG4iNsYMCqjnMP3wt-5lEKxQZIh3uD2yVhurcS-z7VM,7857
200
200
  sqlalchemy/sql/schema.py,sha256=55oc5r8Mqtkcr2ker8kHjlBSUSO3QhwyyqrG2gaFgu4,262469
201
201
  sqlalchemy/sql/selectable.py,sha256=PcrGphf752_nMgFvA5d5LLUGhio9glG3nrCSEJtbvTs,257803
202
- sqlalchemy/sql/sqltypes.py,sha256=tCggHZBkFFn9JmH_eIbImgu07a-Y8aj9-QBrqsisKOU,138754
202
+ sqlalchemy/sql/sqltypes.py,sha256=zykH4bSbQRbmrdq9_lqnOqwwtgWAq0j-XnWa4zQPtQw,138822
203
203
  sqlalchemy/sql/traversals.py,sha256=xLBzv1qpTUjBTiTGMBvPUvl1UPffYSWMxV4wHgOM4i4,34355
204
204
  sqlalchemy/sql/type_api.py,sha256=nIjquO8VkktzgurEYoxPYDMShBRi_xfCyV0wsw8yEAQ,89578
205
205
  sqlalchemy/sql/util.py,sha256=2k520E59ONQEtChkhy7E8Dpmw1WPWbJePwDfG0rRi4A,48610
@@ -215,9 +215,9 @@ sqlalchemy/testing/entities.py,sha256=ZIE4OCzDlKE1OxVArkWF755gvYZDS8oUuX5fuwPAQg
215
215
  sqlalchemy/testing/exclusions.py,sha256=Iwz9xZABIW8BmD-3URvCV20-d0LHKncd8xMpBEHGEUE,14572
216
216
  sqlalchemy/testing/pickleable.py,sha256=gXgySNI39dUcuNY6e-vM9HFKxQ7Uva7NVZgeYyNY4Zw,2833
217
217
  sqlalchemy/testing/profiles_file.py,sha256=sxwF-c6Wd6IBEMHpU1QSdsouyCX7b-4EWWsTTdD8m4w,11349
218
- sqlalchemy/testing/profiling.py,sha256=pCeurQNGnsyMHotKuaT8GM8Aq_p1JADaIXPsWsvEJ9w,9159
218
+ sqlalchemy/testing/profiling.py,sha256=ES3F7pklXaQJXUMdXmcUmoyXvOC3koYYARsIMGyjtqI,10049
219
219
  sqlalchemy/testing/provision.py,sha256=pOMK51WaXJyWAh48HbBjgywDPLk02pSS7rHQqoMLulI,18628
220
- sqlalchemy/testing/requirements.py,sha256=XPJmgocHGf9vEbDgrCryxwEgSkxPZgve0FyzTLBKHAo,58360
220
+ sqlalchemy/testing/requirements.py,sha256=WShbncr8vR1oZKhoap3r3eZMmsnK70oUqtSCc6Ao5CQ,58493
221
221
  sqlalchemy/testing/schema.py,sha256=49x-TXARL2k8oIiaSzyGxXG5fh7vJOuBoUv5h9YTfKo,5360
222
222
  sqlalchemy/testing/util.py,sha256=vbd6v3shfmdxqnqbL0K_hg-yi-ou5Fe9FpkUZeCEyUk,15809
223
223
  sqlalchemy/testing/warnings.py,sha256=j8GWiP-yraCW0Ig5iJxAa5ekN82ZWqcQWyR-BUb4MZA,1546
@@ -229,7 +229,7 @@ sqlalchemy/testing/fixtures/sql.py,sha256=LAp9DhOJ7gInbcCBDqUaIhDCFIHt4ydh_g53v8
229
229
  sqlalchemy/testing/plugin/__init__.py,sha256=8LB-gN7jwvGVW4bPAM1b8MmAQQwBSKh_BGVS3HQc4EM,247
230
230
  sqlalchemy/testing/plugin/bootstrap.py,sha256=5hrNoDrExsOv6fUaa-4JPPEnTJJVJYlBMYGMPu7PjZ8,1684
231
231
  sqlalchemy/testing/plugin/plugin_base.py,sha256=aS9A5zoKaoGKMy8F2OIKbPOMe5SG49lChANZ_cd3UM0,23102
232
- sqlalchemy/testing/plugin/pytestplugin.py,sha256=swDvlVLofRYz3Q-w-svvHgdWIPoS_bvSMIVBkz0-9bM,28690
232
+ sqlalchemy/testing/plugin/pytestplugin.py,sha256=H_F1GFG8-NqaP-FgjEkZnyPY9F81OA-JygtiddZ0iQs,29468
233
233
  sqlalchemy/testing/suite/__init__.py,sha256=jLYLmFQ6k0Fn7N2iPrbXwzJMKDSe4oMdmD-AC6yu3ws,726
234
234
  sqlalchemy/testing/suite/test_cte.py,sha256=7AnV9dR9h76JWY0yWuyeQT-5GQsYbNMz3q2SG8IA_R4,7276
235
235
  sqlalchemy/testing/suite/test_ddl.py,sha256=03nV2s8lfcCw4Czq6pkHl0ZGho8KyFe_C-f0HthP3B0,13010
@@ -238,10 +238,10 @@ sqlalchemy/testing/suite/test_insert.py,sha256=tapEYecSPKqL8xrjlcQzDOfbWdKJz9ldT
238
238
  sqlalchemy/testing/suite/test_reflection.py,sha256=Em0XdladxD6Lk9YH6ZQUw0cdEvkaXJfEPs-1E21xVn0,128866
239
239
  sqlalchemy/testing/suite/test_results.py,sha256=Aw8RK5CX1y39wDK6U8ub8PX9OYe8aoBCBCohjVPvRks,22499
240
240
  sqlalchemy/testing/suite/test_rowcount.py,sha256=V6RQGwe9LO5tKzFPFoa29dDs8M0eY8JZ9tqgbzgtCYs,7900
241
- sqlalchemy/testing/suite/test_select.py,sha256=IPOqGE7N8avGLfmjxHEa2SKGwj5dIJc_g2RM1e-fAL8,65806
241
+ sqlalchemy/testing/suite/test_select.py,sha256=qNeb6RgssJZWur-R4TZ88xyLVi6Offo067It0el-K90,66071
242
242
  sqlalchemy/testing/suite/test_sequence.py,sha256=8a6UO3pjRFnQ23ELG9m5cMMRoD3iD7aSgul-HnkeLns,9979
243
243
  sqlalchemy/testing/suite/test_table_via_select.py,sha256=ZMJKUhAiqIzTKyK9J2cA_rvTij7CUt77d6o5sqDdrkc,21706
244
- sqlalchemy/testing/suite/test_types.py,sha256=FcKVoK0biEXoEAhmHTgLOOVrBFHYECh3zPhdmyQhLE4,76391
244
+ sqlalchemy/testing/suite/test_types.py,sha256=Uva7OT-mmdizJMunxpaaV-SbeK9hhMp799PIPTb5V10,79090
245
245
  sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=tVLf9RqCJe-CsyhCWL9d-WuWazDx5iWYzoBrSCbgjXs,6141
246
246
  sqlalchemy/testing/suite/test_update_delete.py,sha256=llAnxDGvbK-C7CzEXbSNEjczhOSL1sKTLuzGyJahqV8,5195
247
247
  sqlalchemy/util/__init__.py,sha256=2DHFcXP7fB86rfMhHWvngoIZMjKU9ur3L74jd-ROGaE,8671
@@ -260,9 +260,9 @@ sqlalchemy/util/queue.py,sha256=9cMV3Dea0l5tfWIHw3LUE0L3knt48gtUP_C8fBKLQeo,9740
260
260
  sqlalchemy/util/tool_support.py,sha256=iZSwvKdF5pA8UtXuwbtln6dEDgerKJWasELSi7q6Epw,6136
261
261
  sqlalchemy/util/topological.py,sha256=yMpaA2s0JYIekQF4XKwzJzy8lVo-8yeo8o1J2xzxq48,3451
262
262
  sqlalchemy/util/typing.py,sha256=p3vOTJ4F7i3eqeFw7W_tayq88U0e53ZgOsCu7eNQ3lA,21125
263
- sqlalchemy-2.1.3.dist-info/licenses/AUTHORS,sha256=3B2wtdF0Va3De2k_-OQJyagPYD5EFpWCVRjDb3hslj4,492
264
- sqlalchemy-2.1.3.dist-info/licenses/LICENSE,sha256=6GK7W5BPtVE8PhQojXoloS6w7MeEegqWXTEOmpVefLk,1100
265
- sqlalchemy-2.1.3.dist-info/METADATA,sha256=RV-yWu2uBDF_TUquRZTNlA2vl4dvPHsCP3vNX5o03b4,9680
266
- sqlalchemy-2.1.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
267
- sqlalchemy-2.1.3.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
268
- sqlalchemy-2.1.3.dist-info/RECORD,,
263
+ sqlalchemy-2.1.4.dist-info/licenses/AUTHORS,sha256=3B2wtdF0Va3De2k_-OQJyagPYD5EFpWCVRjDb3hslj4,492
264
+ sqlalchemy-2.1.4.dist-info/licenses/LICENSE,sha256=6GK7W5BPtVE8PhQojXoloS6w7MeEegqWXTEOmpVefLk,1100
265
+ sqlalchemy-2.1.4.dist-info/METADATA,sha256=VBbisMQ5oM7iGeIEldC9z43kICE7kpYZqYHIPQr5e_Y,9680
266
+ sqlalchemy-2.1.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
267
+ sqlalchemy-2.1.4.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
268
+ sqlalchemy-2.1.4.dist-info/RECORD,,