SQLAlchemy 2.0.52__py3-none-any.whl → 2.0.54__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 +10 -2
- sqlalchemy/dialects/mysql/base.py +2 -2
- sqlalchemy/dialects/oracle/oracledb.py +21 -1
- sqlalchemy/dialects/postgresql/base.py +5 -1
- sqlalchemy/dialects/sqlite/base.py +73 -13
- sqlalchemy/engine/reflection.py +9 -0
- sqlalchemy/ext/asyncio/engine.py +6 -6
- sqlalchemy/ext/asyncio/scoping.py +12 -9
- sqlalchemy/ext/asyncio/session.py +6 -5
- sqlalchemy/orm/relationships.py +11 -4
- sqlalchemy/orm/scoping.py +7 -5
- sqlalchemy/orm/strategies.py +10 -3
- sqlalchemy/orm/util.py +69 -3
- sqlalchemy/pool/base.py +29 -11
- sqlalchemy/sql/compiler.py +17 -1
- sqlalchemy/sql/elements.py +5 -0
- sqlalchemy/sql/selectable.py +19 -9
- sqlalchemy/testing/cancellation.py +237 -0
- sqlalchemy/testing/requirements.py +29 -0
- sqlalchemy/testing/suite/test_dialect.py +1 -0
- sqlalchemy/testing/suite/test_reflection.py +52 -2
- sqlalchemy/testing/suite/test_update_delete.py +37 -0
- sqlalchemy/util/langhelpers.py +12 -2
- {sqlalchemy-2.0.52.dist-info → sqlalchemy-2.0.54.dist-info}/METADATA +2 -1
- {sqlalchemy-2.0.52.dist-info → sqlalchemy-2.0.54.dist-info}/RECORD +30 -28
- sqlalchemy-2.0.54.dist-info/licenses/AUTHORS +30 -0
- {sqlalchemy-2.0.52.dist-info → sqlalchemy-2.0.54.dist-info}/WHEEL +0 -0
- {sqlalchemy-2.0.52.dist-info → sqlalchemy-2.0.54.dist-info}/licenses/LICENSE +0 -0
- {sqlalchemy-2.0.52.dist-info → sqlalchemy-2.0.54.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
|
@@ -3780,7 +3780,6 @@ order by
|
|
|
3780
3780
|
elif coltype in (
|
|
3781
3781
|
MSString,
|
|
3782
3782
|
MSChar,
|
|
3783
|
-
MSText,
|
|
3784
3783
|
):
|
|
3785
3784
|
kwargs["length"] = maxlen if maxlen != -1 else None
|
|
3786
3785
|
if collation:
|
|
@@ -3788,11 +3787,20 @@ order by
|
|
|
3788
3787
|
elif coltype in (
|
|
3789
3788
|
MSNVarchar,
|
|
3790
3789
|
MSNChar,
|
|
3791
|
-
MSNText,
|
|
3792
3790
|
):
|
|
3793
3791
|
kwargs["length"] = maxlen // 2 if maxlen != -1 else None
|
|
3794
3792
|
if collation:
|
|
3795
3793
|
kwargs["collation"] = collation
|
|
3794
|
+
elif coltype in (
|
|
3795
|
+
MSText,
|
|
3796
|
+
MSNText,
|
|
3797
|
+
):
|
|
3798
|
+
# TEXT / NTEXT are unlengthed LOB types.
|
|
3799
|
+
# sys.columns.max_length reports 16 for these, which is the
|
|
3800
|
+
# size of the in-row LOB pointer and not a character length,
|
|
3801
|
+
# so no length is applied.
|
|
3802
|
+
if collation:
|
|
3803
|
+
kwargs["collation"] = collation
|
|
3796
3804
|
|
|
3797
3805
|
if coltype is None:
|
|
3798
3806
|
if base_type is not None and base_type != type_:
|
|
@@ -2105,8 +2105,8 @@ class MySQLDDLCompiler(compiler.DDLCompiler):
|
|
|
2105
2105
|
"SUBPARTITION_BY",
|
|
2106
2106
|
]
|
|
2107
2107
|
|
|
2108
|
-
nonpart_options =
|
|
2109
|
-
part_options =
|
|
2108
|
+
nonpart_options = util.OrderedSet(opts).difference(partition_options)
|
|
2109
|
+
part_options = util.OrderedSet(opts).intersection(partition_options)
|
|
2110
2110
|
|
|
2111
2111
|
for opt in topological.sort(
|
|
2112
2112
|
[
|
|
@@ -602,6 +602,7 @@ from ... import pool
|
|
|
602
602
|
from ...connectors.asyncio import AsyncAdapt_dbapi_connection
|
|
603
603
|
from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
|
|
604
604
|
from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
|
|
605
|
+
from ...connectors.asyncio import AsyncAdapt_terminate
|
|
605
606
|
from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection
|
|
606
607
|
from ...engine import default
|
|
607
608
|
from ...util import asbool
|
|
@@ -795,7 +796,9 @@ class AsyncAdapt_oracledb_ss_cursor(
|
|
|
795
796
|
self._cursor = None # type: ignore
|
|
796
797
|
|
|
797
798
|
|
|
798
|
-
class AsyncAdapt_oracledb_connection(
|
|
799
|
+
class AsyncAdapt_oracledb_connection(
|
|
800
|
+
AsyncAdapt_terminate, AsyncAdapt_dbapi_connection
|
|
801
|
+
):
|
|
799
802
|
_connection: AsyncConnection
|
|
800
803
|
__slots__ = ()
|
|
801
804
|
|
|
@@ -860,6 +863,12 @@ class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection):
|
|
|
860
863
|
def tpc_rollback(self, *args: Any, **kwargs: Any) -> Any:
|
|
861
864
|
return self.await_(self._connection.tpc_rollback(*args, **kwargs))
|
|
862
865
|
|
|
866
|
+
async def _terminate_graceful_close(self) -> None:
|
|
867
|
+
await self._connection.close()
|
|
868
|
+
|
|
869
|
+
def _terminate_force_close(self) -> None:
|
|
870
|
+
self._connection.terminate()
|
|
871
|
+
|
|
863
872
|
|
|
864
873
|
class AsyncAdaptFallback_oracledb_connection(
|
|
865
874
|
AsyncAdaptFallback_dbapi_connection, AsyncAdapt_oracledb_connection
|
|
@@ -918,6 +927,11 @@ class OracleDialectAsync_oracledb(OracleDialect_oracledb):
|
|
|
918
927
|
|
|
919
928
|
_min_version = (2,)
|
|
920
929
|
|
|
930
|
+
def __init__(self, **kwargs):
|
|
931
|
+
super().__init__(**kwargs)
|
|
932
|
+
|
|
933
|
+
self.has_terminate = self.oracledb_ver >= (26, 0, 0)
|
|
934
|
+
|
|
921
935
|
# thick_mode mode is not supported by asyncio, oracledb will raise
|
|
922
936
|
@classmethod
|
|
923
937
|
def import_dbapi(cls):
|
|
@@ -937,6 +951,12 @@ class OracleDialectAsync_oracledb(OracleDialect_oracledb):
|
|
|
937
951
|
def get_driver_connection(self, connection):
|
|
938
952
|
return connection._connection
|
|
939
953
|
|
|
954
|
+
def do_terminate(self, dbapi_connection) -> None:
|
|
955
|
+
if self.has_terminate:
|
|
956
|
+
dbapi_connection.terminate()
|
|
957
|
+
else:
|
|
958
|
+
super().do_terminate(dbapi_connection)
|
|
959
|
+
|
|
940
960
|
|
|
941
961
|
dialect = OracleDialect_oracledb
|
|
942
962
|
dialect_async = OracleDialectAsync_oracledb
|
|
@@ -3754,7 +3754,11 @@ class PGDialect(default.DefaultDialect):
|
|
|
3754
3754
|
def get_schema_names(self, connection, **kw):
|
|
3755
3755
|
query = (
|
|
3756
3756
|
select(pg_catalog.pg_namespace.c.nspname)
|
|
3757
|
-
.where(
|
|
3757
|
+
.where(
|
|
3758
|
+
~pg_catalog.pg_namespace.c.nspname.startswith(
|
|
3759
|
+
"pg_", autoescape=True
|
|
3760
|
+
)
|
|
3761
|
+
)
|
|
3758
3762
|
.order_by(pg_catalog.pg_namespace.c.nspname)
|
|
3759
3763
|
)
|
|
3760
3764
|
return connection.scalars(query).all()
|
|
@@ -949,6 +949,15 @@ dialect in conjunction with the :class:`_schema.Table` construct:
|
|
|
949
949
|
|
|
950
950
|
.. versionadded:: 2.0.37
|
|
951
951
|
|
|
952
|
+
Both options are also reflected, so that a :class:`_schema.Table` which is
|
|
953
|
+
autoloaded from a database that was created using either keyword will render
|
|
954
|
+
that keyword again when the table is recreated. The reflected values are
|
|
955
|
+
also available directly from
|
|
956
|
+
:meth:`_engine.Inspector.get_table_options`.
|
|
957
|
+
|
|
958
|
+
.. versionadded:: 2.0.53 Added reflection support for the ``WITHOUT ROWID``
|
|
959
|
+
and ``STRICT`` table options.
|
|
960
|
+
|
|
952
961
|
.. seealso::
|
|
953
962
|
|
|
954
963
|
`SQLite CREATE TABLE options
|
|
@@ -2067,6 +2076,37 @@ class SQLiteExecutionContext(default.DefaultExecutionContext):
|
|
|
2067
2076
|
return colname, None
|
|
2068
2077
|
|
|
2069
2078
|
|
|
2079
|
+
# regexp that locates a FOREIGN KEY clause within the verbatim CREATE TABLE
|
|
2080
|
+
# text that sqlite stores in sqlite_master. the referred-columns group
|
|
2081
|
+
# requires a non-empty separator between column tokens; an earlier form made
|
|
2082
|
+
# the separator optional, which turned the repeat into a nested quantifier and
|
|
2083
|
+
# let a long word run backtrack exponentially. kept at module level so it can
|
|
2084
|
+
# be exercised directly from the tests.
|
|
2085
|
+
FK_PATTERN = re.compile(
|
|
2086
|
+
r'(?:CONSTRAINT\s+(?:"(.+?)"|(\w+))\s+)?'
|
|
2087
|
+
r"FOREIGN\s+KEY\s*\(\s*(.+?)\s*\)\s+"
|
|
2088
|
+
r'REFERENCES\s+(?:(?:"(.+?)")|([a-z0-9_]+))\s*\(\s*((?:"[^"]+"|[a-z0-9_]+)(?:(?:\s*,\s*|\s+)(?:"[^"]+"|[a-z0-9_]+))*\s*)\)\s*' # noqa: E501
|
|
2089
|
+
r"((?:ON\s+(?:DELETE|UPDATE)\s+"
|
|
2090
|
+
r"(?:SET\s+NULL|SET\s+DEFAULT|CASCADE|RESTRICT|"
|
|
2091
|
+
r"NO\s+ACTION)\s*)*)"
|
|
2092
|
+
r"((?:NOT\s+)?DEFERRABLE)?"
|
|
2093
|
+
r"(?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?",
|
|
2094
|
+
re.I,
|
|
2095
|
+
)
|
|
2096
|
+
|
|
2097
|
+
# regexp that locates the table option keywords which may trail the closing
|
|
2098
|
+
# paren of the column list in the verbatim CREATE TABLE text. the match is
|
|
2099
|
+
# anchored at the end of the statement, which makes the closing paren
|
|
2100
|
+
# unambiguous, as neither keyword can itself contain one. the keywords may
|
|
2101
|
+
# be given in either order; as each may appear only once, the repeated group
|
|
2102
|
+
# leaves one named group per keyword holding the text that was matched.
|
|
2103
|
+
TABLE_OPTIONS_PATTERN = re.compile(
|
|
2104
|
+
r"\)(?:\s*,?\s*(?:(?P<without_rowid>WITHOUT\s+ROWID)"
|
|
2105
|
+
r"|(?P<strict>STRICT)))*\s*$",
|
|
2106
|
+
re.I,
|
|
2107
|
+
)
|
|
2108
|
+
|
|
2109
|
+
|
|
2070
2110
|
class SQLiteDialect(default.DefaultDialect):
|
|
2071
2111
|
name = "sqlite"
|
|
2072
2112
|
supports_alter = False
|
|
@@ -2381,6 +2421,29 @@ class SQLiteDialect(default.DefaultDialect):
|
|
|
2381
2421
|
f"{schema}.{view_name}" if schema else view_name
|
|
2382
2422
|
)
|
|
2383
2423
|
|
|
2424
|
+
@reflection.cache
|
|
2425
|
+
def get_table_options(self, connection, table_name, schema=None, **kw):
|
|
2426
|
+
tablesql = self._get_table_sql(
|
|
2427
|
+
connection, table_name, schema=schema, **kw
|
|
2428
|
+
)
|
|
2429
|
+
|
|
2430
|
+
options = {}
|
|
2431
|
+
|
|
2432
|
+
# tablesql is None for the internal sqlite_ tables, which have no
|
|
2433
|
+
# entry in sqlite_master
|
|
2434
|
+
if tablesql is not None:
|
|
2435
|
+
match = TABLE_OPTIONS_PATTERN.search(tablesql.strip())
|
|
2436
|
+
if match:
|
|
2437
|
+
if match.group("without_rowid"):
|
|
2438
|
+
options["sqlite_with_rowid"] = False
|
|
2439
|
+
if match.group("strict"):
|
|
2440
|
+
options["sqlite_strict"] = True
|
|
2441
|
+
|
|
2442
|
+
if options:
|
|
2443
|
+
return options
|
|
2444
|
+
else:
|
|
2445
|
+
return ReflectionDefaults.table_options()
|
|
2446
|
+
|
|
2384
2447
|
@reflection.cache
|
|
2385
2448
|
def get_columns(self, connection, table_name, schema=None, **kw):
|
|
2386
2449
|
pragma = "table_info"
|
|
@@ -2549,7 +2612,7 @@ class SQLiteDialect(default.DefaultDialect):
|
|
|
2549
2612
|
constraint_name = None
|
|
2550
2613
|
table_data = self._get_table_sql(connection, table_name, schema=schema)
|
|
2551
2614
|
if table_data:
|
|
2552
|
-
PK_PATTERN = r'CONSTRAINT
|
|
2615
|
+
PK_PATTERN = r'CONSTRAINT\s+(?:"(.+?)"|(\w+))\s+PRIMARY\s+KEY'
|
|
2553
2616
|
result = re.search(PK_PATTERN, table_data, re.I)
|
|
2554
2617
|
if result:
|
|
2555
2618
|
constraint_name = result.group(1) or result.group(2)
|
|
@@ -2653,16 +2716,7 @@ class SQLiteDialect(default.DefaultDialect):
|
|
|
2653
2716
|
# FKs, namely the name of the constraint and other options.
|
|
2654
2717
|
# so parsing the columns is really about matching it up to what
|
|
2655
2718
|
# we already have.
|
|
2656
|
-
|
|
2657
|
-
r'(?:CONSTRAINT +(?:"(.+?)"|(\w+)) +)?'
|
|
2658
|
-
r"FOREIGN KEY *\( *(.+?) *\) +"
|
|
2659
|
-
r'REFERENCES +(?:(?:"(.+?)")|([a-z0-9_]+)) *\( *((?:(?:"[^"]+"|[a-z0-9_]+) *(?:, *)?)+)\) *' # noqa: E501
|
|
2660
|
-
r"((?:ON (?:DELETE|UPDATE) "
|
|
2661
|
-
r"(?:SET NULL|SET DEFAULT|CASCADE|RESTRICT|NO ACTION) *)*)"
|
|
2662
|
-
r"((?:NOT +)?DEFERRABLE)?"
|
|
2663
|
-
r"(?: +INITIALLY +(DEFERRED|IMMEDIATE))?"
|
|
2664
|
-
)
|
|
2665
|
-
for match in re.finditer(FK_PATTERN, table_data, re.I):
|
|
2719
|
+
for match in FK_PATTERN.finditer(table_data):
|
|
2666
2720
|
(
|
|
2667
2721
|
constraint_quoted_name,
|
|
2668
2722
|
constraint_name,
|
|
@@ -2687,7 +2741,13 @@ class SQLiteDialect(default.DefaultDialect):
|
|
|
2687
2741
|
referred_name = referred_quoted_name or referred_name
|
|
2688
2742
|
options = {}
|
|
2689
2743
|
|
|
2690
|
-
|
|
2744
|
+
# a newline may separate the words of an
|
|
2745
|
+
# ON DELETE / ON UPDATE clause; normalize to single
|
|
2746
|
+
# spaces so the tokens below compare correctly
|
|
2747
|
+
onupdatedelete = re.sub(
|
|
2748
|
+
r"\s+", " ", onupdatedelete.upper()
|
|
2749
|
+
).strip()
|
|
2750
|
+
for token in re.split(r" *\bON\b *", onupdatedelete):
|
|
2691
2751
|
if token.startswith("DELETE"):
|
|
2692
2752
|
ondelete = token[6:].strip()
|
|
2693
2753
|
if ondelete and ondelete != "NO ACTION":
|
|
@@ -2770,7 +2830,7 @@ class SQLiteDialect(default.DefaultDialect):
|
|
|
2770
2830
|
if table_data is None:
|
|
2771
2831
|
return
|
|
2772
2832
|
UNIQUE_PATTERN = (
|
|
2773
|
-
r'(?:CONSTRAINT
|
|
2833
|
+
r'(?:CONSTRAINT\s+(?:"(.+?)"|(\w+))\s+)?UNIQUE\s*\((.+?)\)'
|
|
2774
2834
|
)
|
|
2775
2835
|
INLINE_UNIQUE_PATTERN = (
|
|
2776
2836
|
r'(?:(".+?")|(?:[\[`])?([a-z0-9_]+)(?:[\]`])?)[\t ]'
|
sqlalchemy/engine/reflection.py
CHANGED
|
@@ -1758,6 +1758,15 @@ class Inspector(inspection.Inspectable["Inspector"]):
|
|
|
1758
1758
|
):
|
|
1759
1759
|
continue
|
|
1760
1760
|
|
|
1761
|
+
if len(set(constrained_columns)) != len(constrained_columns):
|
|
1762
|
+
util.warn(
|
|
1763
|
+
f"On reflected table {table.name}, skipping reflection of "
|
|
1764
|
+
f"foreign key constraint {conname}; duplicate source "
|
|
1765
|
+
f"columns within name(s) {', '.join(constrained_columns)} "
|
|
1766
|
+
"are not supported"
|
|
1767
|
+
)
|
|
1768
|
+
continue
|
|
1769
|
+
|
|
1761
1770
|
referred_schema = fkey_d["referred_schema"]
|
|
1762
1771
|
referred_table = fkey_d["referred_table"]
|
|
1763
1772
|
referred_columns = fkey_d["referred_columns"]
|
sqlalchemy/ext/asyncio/engine.py
CHANGED
|
@@ -902,7 +902,7 @@ class AsyncConnection( # type: ignore[misc]
|
|
|
902
902
|
# statically generated** by tools/generate_proxy_methods.py
|
|
903
903
|
|
|
904
904
|
@property
|
|
905
|
-
def closed(self) ->
|
|
905
|
+
def closed(self) -> bool:
|
|
906
906
|
r"""Return True if this connection is closed.
|
|
907
907
|
|
|
908
908
|
.. container:: class_bases
|
|
@@ -915,7 +915,7 @@ class AsyncConnection( # type: ignore[misc]
|
|
|
915
915
|
return self._proxied.closed
|
|
916
916
|
|
|
917
917
|
@property
|
|
918
|
-
def invalidated(self) ->
|
|
918
|
+
def invalidated(self) -> bool:
|
|
919
919
|
r"""Return True if this connection was invalidated.
|
|
920
920
|
|
|
921
921
|
.. container:: class_bases
|
|
@@ -945,7 +945,7 @@ class AsyncConnection( # type: ignore[misc]
|
|
|
945
945
|
self._proxied.dialect = attr
|
|
946
946
|
|
|
947
947
|
@property
|
|
948
|
-
def default_isolation_level(self) ->
|
|
948
|
+
def default_isolation_level(self) -> Optional[IsolationLevel]:
|
|
949
949
|
r"""The initial-connection time isolation level associated with the
|
|
950
950
|
:class:`_engine.Dialect` in use.
|
|
951
951
|
|
|
@@ -1258,7 +1258,7 @@ class AsyncEngine(ProxyComparable[Engine], AsyncConnectable): # type: ignore[mi
|
|
|
1258
1258
|
self._proxied.dialect = attr
|
|
1259
1259
|
|
|
1260
1260
|
@property
|
|
1261
|
-
def engine(self) ->
|
|
1261
|
+
def engine(self) -> Engine:
|
|
1262
1262
|
r"""Returns this :class:`.Engine`.
|
|
1263
1263
|
|
|
1264
1264
|
.. container:: class_bases
|
|
@@ -1275,7 +1275,7 @@ class AsyncEngine(ProxyComparable[Engine], AsyncConnectable): # type: ignore[mi
|
|
|
1275
1275
|
return self._proxied.engine
|
|
1276
1276
|
|
|
1277
1277
|
@property
|
|
1278
|
-
def name(self) ->
|
|
1278
|
+
def name(self) -> str:
|
|
1279
1279
|
r"""String name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
|
|
1280
1280
|
in use by this :class:`Engine`.
|
|
1281
1281
|
|
|
@@ -1290,7 +1290,7 @@ class AsyncEngine(ProxyComparable[Engine], AsyncConnectable): # type: ignore[mi
|
|
|
1290
1290
|
return self._proxied.name
|
|
1291
1291
|
|
|
1292
1292
|
@property
|
|
1293
|
-
def driver(self) ->
|
|
1293
|
+
def driver(self) -> str:
|
|
1294
1294
|
r"""Driver name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
|
|
1295
1295
|
in use by this :class:`Engine`.
|
|
1296
1296
|
|
|
@@ -48,15 +48,18 @@ if TYPE_CHECKING:
|
|
|
48
48
|
from ...orm._typing import _IdentityKeyType
|
|
49
49
|
from ...orm._typing import _O
|
|
50
50
|
from ...orm._typing import OrmExecuteOptionsParameter
|
|
51
|
+
from ...orm.identity import IdentityMap
|
|
51
52
|
from ...orm.interfaces import ORMOption
|
|
52
53
|
from ...orm.session import _BindArguments
|
|
53
54
|
from ...orm.session import _EntityBindKey
|
|
54
55
|
from ...orm.session import _PKIdentityArgument
|
|
55
56
|
from ...orm.session import _SessionBind
|
|
57
|
+
from ...sql._typing import _InfoType
|
|
56
58
|
from ...sql.base import Executable
|
|
57
59
|
from ...sql.elements import ClauseElement
|
|
58
60
|
from ...sql.selectable import ForUpdateParameter
|
|
59
61
|
from ...sql.selectable import TypedReturnsRows
|
|
62
|
+
from ...util import IdentitySet
|
|
60
63
|
|
|
61
64
|
_T = TypeVar("_T", bound=Any)
|
|
62
65
|
|
|
@@ -1305,7 +1308,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1305
1308
|
self._proxied.bind = attr
|
|
1306
1309
|
|
|
1307
1310
|
@property
|
|
1308
|
-
def dirty(self) ->
|
|
1311
|
+
def dirty(self) -> IdentitySet:
|
|
1309
1312
|
r"""The set of all persistent instances considered dirty.
|
|
1310
1313
|
|
|
1311
1314
|
.. container:: class_bases
|
|
@@ -1344,7 +1347,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1344
1347
|
return self._proxied.dirty
|
|
1345
1348
|
|
|
1346
1349
|
@property
|
|
1347
|
-
def deleted(self) ->
|
|
1350
|
+
def deleted(self) -> IdentitySet:
|
|
1348
1351
|
r"""The set of all instances marked as 'deleted' within this ``Session``
|
|
1349
1352
|
|
|
1350
1353
|
.. container:: class_bases
|
|
@@ -1363,7 +1366,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1363
1366
|
return self._proxied.deleted
|
|
1364
1367
|
|
|
1365
1368
|
@property
|
|
1366
|
-
def new(self) ->
|
|
1369
|
+
def new(self) -> IdentitySet:
|
|
1367
1370
|
r"""The set of all instances marked as 'new' within this ``Session``.
|
|
1368
1371
|
|
|
1369
1372
|
.. container:: class_bases
|
|
@@ -1382,7 +1385,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1382
1385
|
return self._proxied.new
|
|
1383
1386
|
|
|
1384
1387
|
@property
|
|
1385
|
-
def identity_map(self) ->
|
|
1388
|
+
def identity_map(self) -> IdentityMap:
|
|
1386
1389
|
r"""Proxy for the :attr:`_orm.Session.identity_map` attribute
|
|
1387
1390
|
on behalf of the :class:`_asyncio.AsyncSession` class.
|
|
1388
1391
|
|
|
@@ -1397,11 +1400,11 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1397
1400
|
return self._proxied.identity_map
|
|
1398
1401
|
|
|
1399
1402
|
@identity_map.setter
|
|
1400
|
-
def identity_map(self, attr:
|
|
1403
|
+
def identity_map(self, attr: IdentityMap) -> None:
|
|
1401
1404
|
self._proxied.identity_map = attr
|
|
1402
1405
|
|
|
1403
1406
|
@property
|
|
1404
|
-
def is_active(self) ->
|
|
1407
|
+
def is_active(self) -> bool:
|
|
1405
1408
|
r"""True if this :class:`.Session` not in "partial rollback" state.
|
|
1406
1409
|
|
|
1407
1410
|
.. container:: class_bases
|
|
@@ -1444,7 +1447,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1444
1447
|
return self._proxied.is_active
|
|
1445
1448
|
|
|
1446
1449
|
@property
|
|
1447
|
-
def autoflush(self) ->
|
|
1450
|
+
def autoflush(self) -> bool:
|
|
1448
1451
|
r"""Proxy for the :attr:`_orm.Session.autoflush` attribute
|
|
1449
1452
|
on behalf of the :class:`_asyncio.AsyncSession` class.
|
|
1450
1453
|
|
|
@@ -1459,7 +1462,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1459
1462
|
return self._proxied.autoflush
|
|
1460
1463
|
|
|
1461
1464
|
@autoflush.setter
|
|
1462
|
-
def autoflush(self, attr:
|
|
1465
|
+
def autoflush(self, attr: bool) -> None:
|
|
1463
1466
|
self._proxied.autoflush = attr
|
|
1464
1467
|
|
|
1465
1468
|
@property
|
|
@@ -1498,7 +1501,7 @@ class async_scoped_session(Generic[_AS]):
|
|
|
1498
1501
|
return self._proxied.no_autoflush
|
|
1499
1502
|
|
|
1500
1503
|
@property
|
|
1501
|
-
def info(self) ->
|
|
1504
|
+
def info(self) -> _InfoType:
|
|
1502
1505
|
r"""A user-modifiable dictionary.
|
|
1503
1506
|
|
|
1504
1507
|
.. container:: class_bases
|
|
@@ -68,6 +68,7 @@ if TYPE_CHECKING:
|
|
|
68
68
|
from ...sql.elements import ClauseElement
|
|
69
69
|
from ...sql.selectable import ForUpdateParameter
|
|
70
70
|
from ...sql.selectable import TypedReturnsRows
|
|
71
|
+
from ...util import IdentitySet
|
|
71
72
|
|
|
72
73
|
_AsyncSessionBind = Union["AsyncEngine", "AsyncConnection"]
|
|
73
74
|
|
|
@@ -1385,7 +1386,7 @@ class AsyncSession(ReversibleProxy[Session]):
|
|
|
1385
1386
|
return self._proxied.in_nested_transaction()
|
|
1386
1387
|
|
|
1387
1388
|
@property
|
|
1388
|
-
def dirty(self) ->
|
|
1389
|
+
def dirty(self) -> IdentitySet:
|
|
1389
1390
|
r"""The set of all persistent instances considered dirty.
|
|
1390
1391
|
|
|
1391
1392
|
.. container:: class_bases
|
|
@@ -1418,7 +1419,7 @@ class AsyncSession(ReversibleProxy[Session]):
|
|
|
1418
1419
|
return self._proxied.dirty
|
|
1419
1420
|
|
|
1420
1421
|
@property
|
|
1421
|
-
def deleted(self) ->
|
|
1422
|
+
def deleted(self) -> IdentitySet:
|
|
1422
1423
|
r"""The set of all instances marked as 'deleted' within this ``Session``
|
|
1423
1424
|
|
|
1424
1425
|
.. container:: class_bases
|
|
@@ -1431,7 +1432,7 @@ class AsyncSession(ReversibleProxy[Session]):
|
|
|
1431
1432
|
return self._proxied.deleted
|
|
1432
1433
|
|
|
1433
1434
|
@property
|
|
1434
|
-
def new(self) ->
|
|
1435
|
+
def new(self) -> IdentitySet:
|
|
1435
1436
|
r"""The set of all instances marked as 'new' within this ``Session``.
|
|
1436
1437
|
|
|
1437
1438
|
.. container:: class_bases
|
|
@@ -1457,7 +1458,7 @@ class AsyncSession(ReversibleProxy[Session]):
|
|
|
1457
1458
|
self._proxied.identity_map = attr
|
|
1458
1459
|
|
|
1459
1460
|
@property
|
|
1460
|
-
def is_active(self) ->
|
|
1461
|
+
def is_active(self) -> bool:
|
|
1461
1462
|
r"""True if this :class:`.Session` not in "partial rollback" state.
|
|
1462
1463
|
|
|
1463
1464
|
.. container:: class_bases
|
|
@@ -1536,7 +1537,7 @@ class AsyncSession(ReversibleProxy[Session]):
|
|
|
1536
1537
|
return self._proxied.no_autoflush
|
|
1537
1538
|
|
|
1538
1539
|
@property
|
|
1539
|
-
def info(self) ->
|
|
1540
|
+
def info(self) -> _InfoType:
|
|
1540
1541
|
r"""A user-modifiable dictionary.
|
|
1541
1542
|
|
|
1542
1543
|
.. container:: class_bases
|
sqlalchemy/orm/relationships.py
CHANGED
|
@@ -3115,10 +3115,12 @@ class JoinCondition:
|
|
|
3115
3115
|
secondary_sync_pairs
|
|
3116
3116
|
)
|
|
3117
3117
|
|
|
3118
|
+
# ensure all references are weak so that no self-referencing cycles
|
|
3119
|
+
# may form
|
|
3118
3120
|
_track_overlapping_sync_targets: weakref.WeakKeyDictionary[
|
|
3119
3121
|
ColumnElement[Any],
|
|
3120
3122
|
weakref.WeakKeyDictionary[
|
|
3121
|
-
RelationshipProperty[Any], ColumnElement[Any]
|
|
3123
|
+
RelationshipProperty[Any], weakref.ref[ColumnElement[Any]]
|
|
3122
3124
|
],
|
|
3123
3125
|
] = weakref.WeakKeyDictionary()
|
|
3124
3126
|
|
|
@@ -3145,13 +3147,16 @@ class JoinCondition:
|
|
|
3145
3147
|
|
|
3146
3148
|
if to_ not in self._track_overlapping_sync_targets:
|
|
3147
3149
|
self._track_overlapping_sync_targets[to_] = (
|
|
3148
|
-
weakref.WeakKeyDictionary({self.prop: from_})
|
|
3150
|
+
weakref.WeakKeyDictionary({self.prop: weakref.ref(from_)})
|
|
3149
3151
|
)
|
|
3150
3152
|
else:
|
|
3151
3153
|
other_props = []
|
|
3152
3154
|
prop_to_from = self._track_overlapping_sync_targets[to_]
|
|
3153
3155
|
|
|
3154
|
-
for pr,
|
|
3156
|
+
for pr, fr_ref in prop_to_from.items():
|
|
3157
|
+
fr_ = fr_ref()
|
|
3158
|
+
if fr_ is None:
|
|
3159
|
+
continue
|
|
3155
3160
|
if (
|
|
3156
3161
|
not pr.mapper._dispose_called
|
|
3157
3162
|
and pr not in self.prop._reverse_property
|
|
@@ -3206,7 +3211,9 @@ class JoinCondition:
|
|
|
3206
3211
|
),
|
|
3207
3212
|
code="qzyx",
|
|
3208
3213
|
)
|
|
3209
|
-
self._track_overlapping_sync_targets[to_][self.prop] =
|
|
3214
|
+
self._track_overlapping_sync_targets[to_][self.prop] = (
|
|
3215
|
+
weakref.ref(from_)
|
|
3216
|
+
)
|
|
3210
3217
|
|
|
3211
3218
|
@util.memoized_property
|
|
3212
3219
|
def remote_columns(self) -> Set[ColumnElement[Any]]:
|
sqlalchemy/orm/scoping.py
CHANGED
|
@@ -57,6 +57,7 @@ if TYPE_CHECKING:
|
|
|
57
57
|
from ..engine.interfaces import CoreExecuteOptionsParameter
|
|
58
58
|
from ..engine.result import ScalarResult
|
|
59
59
|
from ..sql._typing import _ColumnsClauseArgument
|
|
60
|
+
from ..sql._typing import _InfoType
|
|
60
61
|
from ..sql._typing import _T0
|
|
61
62
|
from ..sql._typing import _T1
|
|
62
63
|
from ..sql._typing import _T2
|
|
@@ -71,6 +72,7 @@ if TYPE_CHECKING:
|
|
|
71
72
|
from ..sql.roles import TypedColumnsClauseRole
|
|
72
73
|
from ..sql.selectable import ForUpdateParameter
|
|
73
74
|
from ..sql.selectable import TypedReturnsRows
|
|
75
|
+
from ..util import IdentitySet
|
|
74
76
|
|
|
75
77
|
_T = TypeVar("_T", bound=Any)
|
|
76
78
|
|
|
@@ -1909,7 +1911,7 @@ class scoped_session(Generic[_S]):
|
|
|
1909
1911
|
self._proxied.bind = attr
|
|
1910
1912
|
|
|
1911
1913
|
@property
|
|
1912
|
-
def dirty(self) ->
|
|
1914
|
+
def dirty(self) -> IdentitySet:
|
|
1913
1915
|
r"""The set of all persistent instances considered dirty.
|
|
1914
1916
|
|
|
1915
1917
|
.. container:: class_bases
|
|
@@ -1942,7 +1944,7 @@ class scoped_session(Generic[_S]):
|
|
|
1942
1944
|
return self._proxied.dirty
|
|
1943
1945
|
|
|
1944
1946
|
@property
|
|
1945
|
-
def deleted(self) ->
|
|
1947
|
+
def deleted(self) -> IdentitySet:
|
|
1946
1948
|
r"""The set of all instances marked as 'deleted' within this ``Session``
|
|
1947
1949
|
|
|
1948
1950
|
.. container:: class_bases
|
|
@@ -1955,7 +1957,7 @@ class scoped_session(Generic[_S]):
|
|
|
1955
1957
|
return self._proxied.deleted
|
|
1956
1958
|
|
|
1957
1959
|
@property
|
|
1958
|
-
def new(self) ->
|
|
1960
|
+
def new(self) -> IdentitySet:
|
|
1959
1961
|
r"""The set of all instances marked as 'new' within this ``Session``.
|
|
1960
1962
|
|
|
1961
1963
|
.. container:: class_bases
|
|
@@ -1981,7 +1983,7 @@ class scoped_session(Generic[_S]):
|
|
|
1981
1983
|
self._proxied.identity_map = attr
|
|
1982
1984
|
|
|
1983
1985
|
@property
|
|
1984
|
-
def is_active(self) ->
|
|
1986
|
+
def is_active(self) -> bool:
|
|
1985
1987
|
r"""True if this :class:`.Session` not in "partial rollback" state.
|
|
1986
1988
|
|
|
1987
1989
|
.. container:: class_bases
|
|
@@ -2060,7 +2062,7 @@ class scoped_session(Generic[_S]):
|
|
|
2060
2062
|
return self._proxied.no_autoflush
|
|
2061
2063
|
|
|
2062
2064
|
@property
|
|
2063
|
-
def info(self) ->
|
|
2065
|
+
def info(self) -> _InfoType:
|
|
2064
2066
|
r"""A user-modifiable dictionary.
|
|
2065
2067
|
|
|
2066
2068
|
.. container:: class_bases
|
sqlalchemy/orm/strategies.py
CHANGED
|
@@ -67,6 +67,13 @@ if TYPE_CHECKING:
|
|
|
67
67
|
from ..sql.elements import ColumnElement
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
# sentinel for _LoadLazyAttribute.extra_criteria indicating that the loader
|
|
71
|
+
# option had no additional criteria. the attribute is otherwise a single
|
|
72
|
+
# element returned by Load._generate_extra_criteria(), which has no
|
|
73
|
+
# meaningful boolean value of its own.
|
|
74
|
+
_NO_EXTRA_CRITERIA = None
|
|
75
|
+
|
|
76
|
+
|
|
70
77
|
def _register_attribute(
|
|
71
78
|
prop,
|
|
72
79
|
mapper,
|
|
@@ -1209,7 +1216,7 @@ class LazyLoader(
|
|
|
1209
1216
|
(
|
|
1210
1217
|
loadopt._generate_extra_criteria(context)
|
|
1211
1218
|
if loadopt._extra_criteria
|
|
1212
|
-
else
|
|
1219
|
+
else _NO_EXTRA_CRITERIA
|
|
1213
1220
|
),
|
|
1214
1221
|
),
|
|
1215
1222
|
key,
|
|
@@ -1257,7 +1264,7 @@ class LoadLazyAttribute:
|
|
|
1257
1264
|
self.extra_criteria = extra_criteria
|
|
1258
1265
|
|
|
1259
1266
|
def __getstate__(self):
|
|
1260
|
-
if self.extra_criteria is not
|
|
1267
|
+
if self.extra_criteria is not _NO_EXTRA_CRITERIA:
|
|
1261
1268
|
util.warn(
|
|
1262
1269
|
"Can't reliably serialize a lazyload() option that "
|
|
1263
1270
|
"contains additional criteria; please use eager loading "
|
|
@@ -1267,7 +1274,7 @@ class LoadLazyAttribute:
|
|
|
1267
1274
|
"key": self.key,
|
|
1268
1275
|
"strategy_key": self.strategy_key,
|
|
1269
1276
|
"loadopt": self.loadopt,
|
|
1270
|
-
"extra_criteria":
|
|
1277
|
+
"extra_criteria": _NO_EXTRA_CRITERIA,
|
|
1271
1278
|
}
|
|
1272
1279
|
|
|
1273
1280
|
def __call__(self, state, passive=attributes.PASSIVE_OFF):
|