SQLAlchemy 2.1.3__py3-none-any.whl → 2.1.5__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 +1 -1
- sqlalchemy/dialects/mssql/base.py +7 -0
- sqlalchemy/dialects/oracle/base.py +4 -1
- sqlalchemy/dialects/postgresql/asyncpg.py +6 -2
- sqlalchemy/dialects/postgresql/base.py +12 -2
- sqlalchemy/dialects/postgresql/named_types.py +16 -0
- sqlalchemy/engine/_result_cy.py +37 -36
- sqlalchemy/engine/base.py +40 -30
- sqlalchemy/engine/characteristics.py +1 -1
- sqlalchemy/engine/create.py +20 -10
- sqlalchemy/engine/default.py +9 -6
- sqlalchemy/ext/mutable.py +11 -0
- sqlalchemy/orm/__init__.py +2 -0
- sqlalchemy/orm/_orm_constructors.py +134 -0
- sqlalchemy/orm/base.py +54 -0
- sqlalchemy/orm/bulk_persistence.py +5 -3
- sqlalchemy/orm/decl_api.py +22 -5
- sqlalchemy/orm/decl_base.py +110 -24
- sqlalchemy/orm/mapper.py +56 -2
- sqlalchemy/orm/properties.py +75 -1
- sqlalchemy/orm/util.py +9 -2
- sqlalchemy/pool/base.py +47 -29
- sqlalchemy/sql/compiler.py +15 -2
- sqlalchemy/sql/dml.py +8 -0
- sqlalchemy/sql/schema.py +2 -0
- sqlalchemy/sql/sqltypes.py +1 -0
- sqlalchemy/testing/cancellation.py +77 -6
- sqlalchemy/testing/plugin/pytestplugin.py +28 -1
- sqlalchemy/testing/profiling.py +23 -0
- sqlalchemy/testing/requirements.py +5 -0
- sqlalchemy/testing/suite/test_select.py +6 -0
- sqlalchemy/testing/suite/test_types.py +75 -0
- sqlalchemy/util/queue.py +11 -0
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/METADATA +1 -1
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/RECORD +39 -39
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/WHEEL +0 -0
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/licenses/AUTHORS +0 -0
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.1.3.dist-info → sqlalchemy-2.1.5.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
|
@@ -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
|
|
|
@@ -2279,7 +2279,10 @@ class OracleDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
2279
2279
|
return self.get_isolation_level(dbapi_conn)
|
|
2280
2280
|
except NotImplementedError:
|
|
2281
2281
|
raise
|
|
2282
|
-
except:
|
|
2282
|
+
except Exception:
|
|
2283
|
+
# Exception only; a bare except here also caught
|
|
2284
|
+
# asyncio.CancelledError and KeyboardInterrupt, returning the
|
|
2285
|
+
# default in place of propagating them
|
|
2283
2286
|
return "READ COMMITTED"
|
|
2284
2287
|
|
|
2285
2288
|
def _execute_reflection(
|
|
@@ -886,14 +886,18 @@ class AsyncAdapt_asyncpg_connection(
|
|
|
886
886
|
|
|
887
887
|
assert self._transaction is None
|
|
888
888
|
try:
|
|
889
|
-
|
|
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
|
|
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:
|
|
@@ -5070,8 +5070,9 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5070
5070
|
|
|
5071
5071
|
# https://www.postgresql.org/docs/current/static/sql-createtable.html
|
|
5072
5072
|
return re.compile(
|
|
5073
|
-
|
|
5074
|
-
rf"REFERENCES (?:({qtoken})\.)?({qtoken})
|
|
5073
|
+
rf"FOREIGN KEY \((.*?)(?:, PERIOD ({qtoken}))?\) "
|
|
5074
|
+
rf"REFERENCES (?:({qtoken})\.)?({qtoken})"
|
|
5075
|
+
rf"\(((?:{qtoken}(?: *, *)?)+)(?:, PERIOD ({qtoken}))?\)"
|
|
5075
5076
|
r"[\s]?(MATCH (FULL|PARTIAL|SIMPLE)+)?"
|
|
5076
5077
|
r"[\s]?(?:ON (UPDATE|DELETE) "
|
|
5077
5078
|
r"(CASCADE|RESTRICT|NO ACTION|"
|
|
@@ -5089,9 +5090,11 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5089
5090
|
|
|
5090
5091
|
(
|
|
5091
5092
|
constrained_columns,
|
|
5093
|
+
constrained_period_column,
|
|
5092
5094
|
referred_schema,
|
|
5093
5095
|
referred_table,
|
|
5094
5096
|
referred_columns,
|
|
5097
|
+
referred_period_column,
|
|
5095
5098
|
_,
|
|
5096
5099
|
match,
|
|
5097
5100
|
upddelkey1,
|
|
@@ -5103,6 +5106,13 @@ class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
|
|
|
5103
5106
|
initially,
|
|
5104
5107
|
) = m
|
|
5105
5108
|
|
|
5109
|
+
# TODO: the PERIOD designation of a temporal foreign key
|
|
5110
|
+
# (PostgreSQL 18+) is not yet reflected
|
|
5111
|
+
if constrained_period_column:
|
|
5112
|
+
constrained_columns += f", {constrained_period_column}"
|
|
5113
|
+
if referred_period_column:
|
|
5114
|
+
referred_columns += f", {referred_period_column}"
|
|
5115
|
+
|
|
5106
5116
|
onupdate = (
|
|
5107
5117
|
upddelval1
|
|
5108
5118
|
if upddelkey1 == "UPDATE"
|
|
@@ -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"
|
sqlalchemy/engine/_result_cy.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
@@ -758,12 +758,19 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
|
|
|
758
758
|
if self.closed:
|
|
759
759
|
raise exc.ResourceClosedError("This Connection is closed")
|
|
760
760
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
761
|
+
try:
|
|
762
|
+
if self._still_open_and_dbapi_connection_is_valid:
|
|
763
|
+
pool_proxied_connection = self._dbapi_connection
|
|
764
|
+
assert pool_proxied_connection is not None
|
|
765
|
+
pool_proxied_connection.invalidate(exception)
|
|
766
|
+
finally:
|
|
767
|
+
# this is always a hard invalidation, which closes the DBAPI
|
|
768
|
+
# connection; for an asyncio driver that close is an awaited
|
|
769
|
+
# terminate() which a cancellation can interrupt. this
|
|
770
|
+
# Connection is invalidated regardless, so that nothing
|
|
771
|
+
# further, such as a rollback, is emitted on the DBAPI
|
|
772
|
+
# connection
|
|
773
|
+
self._dbapi_connection = None
|
|
767
774
|
|
|
768
775
|
def detach(self) -> None:
|
|
769
776
|
"""Detach the underlying DB-API connection from its connection pool.
|
|
@@ -1257,30 +1264,33 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
|
|
|
1257
1264
|
|
|
1258
1265
|
"""
|
|
1259
1266
|
|
|
1260
|
-
if
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1267
|
+
# if the rollback here raises, skip_reset stays False and the pool
|
|
1268
|
+
# does its own reset of the connection, invalidating it if that
|
|
1269
|
+
# fails also. see #13657
|
|
1270
|
+
skip_reset = False
|
|
1271
|
+
try:
|
|
1272
|
+
if self._transaction:
|
|
1273
|
+
self._transaction.close()
|
|
1274
|
+
skip_reset = True
|
|
1275
|
+
finally:
|
|
1276
|
+
if self._dbapi_connection is not None:
|
|
1277
|
+
conn = self._dbapi_connection
|
|
1278
|
+
|
|
1279
|
+
# as we just closed the transaction, close the connection
|
|
1280
|
+
# pool connection without doing an additional reset
|
|
1281
|
+
if skip_reset:
|
|
1282
|
+
cast("_ConnectionFairy", conn)._close_special(
|
|
1283
|
+
transaction_reset=True
|
|
1284
|
+
)
|
|
1285
|
+
else:
|
|
1286
|
+
conn.close()
|
|
1287
|
+
|
|
1288
|
+
# There is a slight chance that conn.close() may have
|
|
1289
|
+
# triggered an invalidation here in which case
|
|
1290
|
+
# _dbapi_connection would already be None, however usually
|
|
1291
|
+
# it will be non-None here and in a "closed" state.
|
|
1292
|
+
self._dbapi_connection = None
|
|
1293
|
+
self.__can_reconnect = False
|
|
1284
1294
|
|
|
1285
1295
|
# special case to handle mypy issue:
|
|
1286
1296
|
# 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
|
-
|
|
145
|
+
conn._message_formatter = None
|
|
146
146
|
|
|
147
147
|
def get_characteristic(
|
|
148
148
|
self, dialect: Dialect, dbapi_conn: DBAPIConnection
|
sqlalchemy/engine/create.py
CHANGED
|
@@ -747,16 +747,26 @@ def create_engine(url: Union[str, _url.URL], **kwargs: Any) -> Engine:
|
|
|
747
747
|
)
|
|
748
748
|
c._execution_options = util.EMPTY_DICT
|
|
749
749
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
750
|
+
dialect.initialize(c)
|
|
751
|
+
|
|
752
|
+
# roll back only once initialize() has succeeded, rather than
|
|
753
|
+
# in a finally: block. when initialize() raises, the DBAPI
|
|
754
|
+
# connection may be partway through an operation that c
|
|
755
|
+
# doesn't know about, such as psycopg's TypeInfo.fetch()
|
|
756
|
+
# interrupted by a cancellation; the rollback then fails
|
|
757
|
+
# and its error replaces the original one. the pool closes
|
|
758
|
+
# the connection in that case, so there's nothing to roll
|
|
759
|
+
# back.
|
|
760
|
+
#
|
|
761
|
+
# note that "invalidated" and "closed" are mutually
|
|
762
|
+
# exclusive in 1.4 Connection. c may be invalidated here if
|
|
763
|
+
# initialize() caught an error on a statement that was a
|
|
764
|
+
# disconnect.
|
|
765
|
+
if not c.invalidated and not c.closed:
|
|
766
|
+
# transaction is rolled back otherwise, tested by
|
|
767
|
+
# test/dialect/postgresql/test_dialect.py
|
|
768
|
+
# ::MiscBackendTest::test_initial_transaction_state
|
|
769
|
+
dialect.do_rollback(c.connection)
|
|
760
770
|
|
|
761
771
|
# previously, the "first_connect" event was used here, which was then
|
|
762
772
|
# scaled back if the "on_connect" handler were present. now,
|
sqlalchemy/engine/default.py
CHANGED
|
@@ -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()
|
sqlalchemy/orm/__init__.py
CHANGED
|
@@ -37,6 +37,7 @@ from ._orm_constructors import outerjoin as outerjoin
|
|
|
37
37
|
from ._orm_constructors import query_expression as query_expression
|
|
38
38
|
from ._orm_constructors import relationship as relationship
|
|
39
39
|
from ._orm_constructors import synonym as synonym
|
|
40
|
+
from ._orm_constructors import unmapped_column as unmapped_column
|
|
40
41
|
from ._orm_constructors import with_loader_criteria as with_loader_criteria
|
|
41
42
|
from ._orm_constructors import with_polymorphic as with_polymorphic
|
|
42
43
|
from .attributes import AttributeEventToken as AttributeEventToken
|
|
@@ -51,6 +52,7 @@ from .base import NotExtension as NotExtension
|
|
|
51
52
|
from .base import ORMDescriptor as ORMDescriptor
|
|
52
53
|
from .base import PassiveFlag as PassiveFlag
|
|
53
54
|
from .base import SQLORMExpression as SQLORMExpression
|
|
55
|
+
from .base import Unmapped as Unmapped
|
|
54
56
|
from .base import WriteOnlyMapped as WriteOnlyMapped
|
|
55
57
|
from .context import FromStatement as FromStatement
|
|
56
58
|
from .context import QueryContext as QueryContext
|
|
@@ -29,6 +29,7 @@ from .descriptor_props import Synonym
|
|
|
29
29
|
from .interfaces import _AttributeOptions
|
|
30
30
|
from .properties import MappedColumn
|
|
31
31
|
from .properties import MappedSQLExpression
|
|
32
|
+
from .properties import UnmappedColumn
|
|
32
33
|
from .query import AliasOption
|
|
33
34
|
from .relationships import _RelationshipArgumentType
|
|
34
35
|
from .relationships import _RelationshipBackPopulatesArgument
|
|
@@ -395,6 +396,139 @@ def mapped_column(
|
|
|
395
396
|
)
|
|
396
397
|
|
|
397
398
|
|
|
399
|
+
def unmapped_column(
|
|
400
|
+
__name_pos: Optional[
|
|
401
|
+
Union[str, _TypeEngineArgument[Any], SchemaEventTarget]
|
|
402
|
+
] = None,
|
|
403
|
+
__type_pos: Optional[
|
|
404
|
+
Union[_TypeEngineArgument[Any], SchemaEventTarget]
|
|
405
|
+
] = None,
|
|
406
|
+
/,
|
|
407
|
+
*args: SchemaEventTarget,
|
|
408
|
+
init: Literal[False] = False,
|
|
409
|
+
nullable: Optional[
|
|
410
|
+
Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]]
|
|
411
|
+
] = SchemaConst.NULL_UNSPECIFIED,
|
|
412
|
+
primary_key: Optional[bool] = False,
|
|
413
|
+
autoincrement: _AutoIncrementType = "auto",
|
|
414
|
+
use_existing_column: bool = False,
|
|
415
|
+
name: Optional[str] = None,
|
|
416
|
+
type_: Optional[_TypeEngineArgument[Any]] = None,
|
|
417
|
+
index: Optional[bool] = None,
|
|
418
|
+
unique: Optional[bool] = None,
|
|
419
|
+
info: Optional[_InfoType] = None,
|
|
420
|
+
default: Optional[Any] = _NoArg.NO_ARG,
|
|
421
|
+
insert_default: Optional[Any] = _NoArg.NO_ARG,
|
|
422
|
+
onupdate: Optional[Any] = None,
|
|
423
|
+
server_default: Optional[_ServerDefaultArgument] = None,
|
|
424
|
+
server_onupdate: Optional[_ServerOnUpdateArgument] = None,
|
|
425
|
+
quote: Optional[bool] = None,
|
|
426
|
+
system: bool = False,
|
|
427
|
+
comment: Optional[str] = None,
|
|
428
|
+
sort_order: Union[_NoArg, int] = _NoArg.NO_ARG,
|
|
429
|
+
**kw: Any,
|
|
430
|
+
) -> UnmappedColumn[Any]:
|
|
431
|
+
r"""declare a :class:`_schema.Column` that is part of the
|
|
432
|
+
:class:`_schema.Table` for a :ref:`Declarative Table
|
|
433
|
+
<orm_declarative_table>` mapping, but which is not mapped by the ORM.
|
|
434
|
+
|
|
435
|
+
:func:`_orm.unmapped_column` is used with the :class:`_orm.Unmapped`
|
|
436
|
+
annotation::
|
|
437
|
+
|
|
438
|
+
class User(Base):
|
|
439
|
+
__tablename__ = "user"
|
|
440
|
+
|
|
441
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
442
|
+
name: Mapped[str]
|
|
443
|
+
|
|
444
|
+
legacy_name: Unmapped[str | None] = unmapped_column()
|
|
445
|
+
|
|
446
|
+
The resulting :class:`_schema.Column` is added to the
|
|
447
|
+
:class:`_schema.Table` in the same way as one produced by
|
|
448
|
+
:func:`_orm.mapped_column`, deriving its type and nullability from the
|
|
449
|
+
annotation, so that the column is present in DDL as well as for schema
|
|
450
|
+
comparison tools such as Alembic autogenerate. However, the column is
|
|
451
|
+
excluded from the mapping, the attribute is removed from the class, and
|
|
452
|
+
when using :ref:`orm_declarative_native_dataclasses` it is not part of
|
|
453
|
+
the dataclass.
|
|
454
|
+
|
|
455
|
+
A bare ``Unmapped[]`` annotation with no right-hand side is equivalent
|
|
456
|
+
to ``unmapped_column()`` with no arguments; however, see the note at
|
|
457
|
+
:class:`_orm.Unmapped` regarding typing tools and dataclasses.
|
|
458
|
+
|
|
459
|
+
As the attribute is not part of the Python model, ORM and dataclass
|
|
460
|
+
oriented arguments of :func:`_orm.mapped_column`, such as
|
|
461
|
+
``default_factory`` and ``deferred``, are not part of
|
|
462
|
+
:func:`_orm.unmapped_column`.
|
|
463
|
+
|
|
464
|
+
.. versionadded:: 2.1.5
|
|
465
|
+
|
|
466
|
+
.. seealso::
|
|
467
|
+
|
|
468
|
+
:ref:`orm_declarative_unmapped_column`
|
|
469
|
+
|
|
470
|
+
:param __name: String name to give to the :class:`_schema.Column`;
|
|
471
|
+
defaults to the attribute name.
|
|
472
|
+
:param __type: :class:`_types.TypeEngine` type or instance for the
|
|
473
|
+
:class:`_schema.Column`; if omitted, it is derived from the
|
|
474
|
+
annotation, or from a :class:`_schema.ForeignKey` if present.
|
|
475
|
+
:param \*args: Additional positional arguments such as
|
|
476
|
+
:class:`_schema.ForeignKey` and :class:`_schema.CheckConstraint`,
|
|
477
|
+
passed through to the :class:`_schema.Column`.
|
|
478
|
+
:param init: Always ``False``; this parameter is present so that
|
|
479
|
+
:pep:`681` typing tools omit the attribute from the ``__init__()``
|
|
480
|
+
method of :ref:`ORM mapped dataclasses
|
|
481
|
+
<orm_declarative_native_dataclasses>`.
|
|
482
|
+
:param nullable: whether the column is "NULL" or "NOT NULL"; if omitted,
|
|
483
|
+
derived from the annotation as with :func:`_orm.mapped_column`.
|
|
484
|
+
:param primary_key: indicates the :class:`_schema.Column` is part of the
|
|
485
|
+
table's primary key. As the mapper requires that its primary key
|
|
486
|
+
columns be mapped, the columns that the mapper should use for the
|
|
487
|
+
identity of the class must then be indicated using the
|
|
488
|
+
:paramref:`_orm.Mapper.primary_key` parameter; see
|
|
489
|
+
:ref:`orm_declarative_unmapped_column` for an example.
|
|
490
|
+
:param autoincrement: Passed directly to the
|
|
491
|
+
:paramref:`_schema.Column.autoincrement` parameter.
|
|
492
|
+
:param use_existing_column: see
|
|
493
|
+
:paramref:`_orm.mapped_column.use_existing_column`.
|
|
494
|
+
:param default: Passed directly to the :paramref:`_schema.Column.default`
|
|
495
|
+
parameter. As the attribute is not part of a dataclass, this is
|
|
496
|
+
strictly a Core column default.
|
|
497
|
+
:param insert_default: synonym for
|
|
498
|
+
:paramref:`_orm.unmapped_column.default`.
|
|
499
|
+
:param sort_order: see :paramref:`_orm.mapped_column.sort_order`.
|
|
500
|
+
:param \**kw: All remaining keyword arguments are passed through to the
|
|
501
|
+
constructor for the :class:`_schema.Column`, including dialect-specific
|
|
502
|
+
arguments.
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
return UnmappedColumn(
|
|
507
|
+
__name_pos,
|
|
508
|
+
__type_pos,
|
|
509
|
+
*args,
|
|
510
|
+
name=name,
|
|
511
|
+
type_=type_,
|
|
512
|
+
default=default,
|
|
513
|
+
insert_default=insert_default,
|
|
514
|
+
index=index,
|
|
515
|
+
unique=unique,
|
|
516
|
+
info=info,
|
|
517
|
+
nullable=nullable,
|
|
518
|
+
primary_key=primary_key,
|
|
519
|
+
autoincrement=autoincrement,
|
|
520
|
+
onupdate=onupdate,
|
|
521
|
+
server_default=server_default,
|
|
522
|
+
server_onupdate=server_onupdate,
|
|
523
|
+
use_existing_column=use_existing_column,
|
|
524
|
+
quote=quote,
|
|
525
|
+
comment=comment,
|
|
526
|
+
system=system,
|
|
527
|
+
sort_order=sort_order,
|
|
528
|
+
**kw,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
398
532
|
def orm_insert_sentinel(
|
|
399
533
|
name: Optional[str] = None,
|
|
400
534
|
type_: Optional[_TypeEngineArgument[Any]] = None,
|
sqlalchemy/orm/base.py
CHANGED
|
@@ -881,6 +881,60 @@ class _DeclarativeMapped(Mapped[_T_co], _MappedAttribute[_T_co]):
|
|
|
881
881
|
return NotImplemented
|
|
882
882
|
|
|
883
883
|
|
|
884
|
+
class Unmapped(_MappedAnnotationBase[_T_co]):
|
|
885
|
+
"""Represent a database column that is part of the mapped
|
|
886
|
+
:class:`_schema.Table`, but is not mapped by the ORM.
|
|
887
|
+
|
|
888
|
+
The :class:`_orm.Unmapped` type annotation is used in an
|
|
889
|
+
:ref:`Annotated Declarative Table <orm_declarative_mapped_column>`
|
|
890
|
+
mapping, in conjunction with the :func:`_orm.unmapped_column`
|
|
891
|
+
construct, to indicate a column that should be present in the
|
|
892
|
+
:class:`_schema.Table` generated for the class, while not being part
|
|
893
|
+
of the Python model at all. The type and nullability of the
|
|
894
|
+
:class:`_schema.Column` are derived from the annotation in the same
|
|
895
|
+
way as for :class:`_orm.Mapped`.
|
|
896
|
+
|
|
897
|
+
E.g.::
|
|
898
|
+
|
|
899
|
+
class User(Base):
|
|
900
|
+
__tablename__ = "user"
|
|
901
|
+
|
|
902
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
903
|
+
name: Mapped[str]
|
|
904
|
+
|
|
905
|
+
legacy_name: Unmapped[str | None] = unmapped_column()
|
|
906
|
+
|
|
907
|
+
The ``legacy_name`` column is part of ``User.__table__``, but there is
|
|
908
|
+
no ``User.legacy_name`` attribute, and when using
|
|
909
|
+
:ref:`orm_declarative_native_dataclasses`, it is not part of the
|
|
910
|
+
dataclass.
|
|
911
|
+
|
|
912
|
+
A bare ``Unmapped[]`` annotation with no right-hand side is equivalent to
|
|
913
|
+
``unmapped_column()`` with no arguments. When using
|
|
914
|
+
:ref:`orm_declarative_native_dataclasses`, the attribute is omitted from
|
|
915
|
+
the dataclass and its ``__init__()`` method in both cases; however,
|
|
916
|
+
:pep:`681` typing tools are only able to determine this when
|
|
917
|
+
:func:`_orm.unmapped_column` is present, and otherwise see the attribute
|
|
918
|
+
as a required ``__init__()`` argument, so it should always be included
|
|
919
|
+
in that case.
|
|
920
|
+
|
|
921
|
+
.. versionadded:: 2.1.5
|
|
922
|
+
|
|
923
|
+
.. seealso::
|
|
924
|
+
|
|
925
|
+
:ref:`orm_declarative_unmapped_column`
|
|
926
|
+
|
|
927
|
+
:func:`_orm.unmapped_column`
|
|
928
|
+
|
|
929
|
+
"""
|
|
930
|
+
|
|
931
|
+
__slots__ = ()
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def _is_unmapped_container(mapped_container: Optional[type]) -> bool:
|
|
935
|
+
return mapped_container is Unmapped
|
|
936
|
+
|
|
937
|
+
|
|
884
938
|
class DynamicMapped(_MappedAnnotationBase[_T_co]):
|
|
885
939
|
"""Represent the ORM mapped attribute type for a "dynamic" relationship.
|
|
886
940
|
|