SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.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 +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# engine/util.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import typing
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Callable
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import TypeVar
|
|
15
|
+
|
|
16
|
+
from .. import exc
|
|
17
|
+
from .. import util
|
|
18
|
+
from ..util._has_cy import HAS_CYEXTENSION
|
|
19
|
+
from ..util.typing import Protocol
|
|
20
|
+
from ..util.typing import Self
|
|
21
|
+
|
|
22
|
+
if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
|
|
23
|
+
from ._py_util import _distill_params_20 as _distill_params_20
|
|
24
|
+
from ._py_util import _distill_raw_params as _distill_raw_params
|
|
25
|
+
else:
|
|
26
|
+
from sqlalchemy.cyextension.util import ( # noqa: F401
|
|
27
|
+
_distill_params_20 as _distill_params_20,
|
|
28
|
+
)
|
|
29
|
+
from sqlalchemy.cyextension.util import ( # noqa: F401
|
|
30
|
+
_distill_raw_params as _distill_raw_params,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_C = TypeVar("_C", bound=Callable[[], Any])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def connection_memoize(key: str) -> Callable[[_C], _C]:
|
|
37
|
+
"""Decorator, memoize a function in a connection.info stash.
|
|
38
|
+
|
|
39
|
+
Only applicable to functions which take no arguments other than a
|
|
40
|
+
connection. The memo will be stored in ``connection.info[key]``.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@util.decorator
|
|
44
|
+
def decorated(fn, self, connection): # type: ignore
|
|
45
|
+
connection = connection.connect()
|
|
46
|
+
try:
|
|
47
|
+
return connection.info[key]
|
|
48
|
+
except KeyError:
|
|
49
|
+
connection.info[key] = val = fn(self, connection)
|
|
50
|
+
return val
|
|
51
|
+
|
|
52
|
+
return decorated
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _TConsSubject(Protocol):
|
|
56
|
+
_trans_context_manager: Optional[TransactionalContext]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TransactionalContext:
|
|
60
|
+
"""Apply Python context manager behavior to transaction objects.
|
|
61
|
+
|
|
62
|
+
Performs validation to ensure the subject of the transaction is not
|
|
63
|
+
used if the transaction were ended prematurely.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
__slots__ = ("_outer_trans_ctx", "_trans_subject", "__weakref__")
|
|
68
|
+
|
|
69
|
+
_trans_subject: Optional[_TConsSubject]
|
|
70
|
+
|
|
71
|
+
def _transaction_is_active(self) -> bool:
|
|
72
|
+
raise NotImplementedError()
|
|
73
|
+
|
|
74
|
+
def _transaction_is_closed(self) -> bool:
|
|
75
|
+
raise NotImplementedError()
|
|
76
|
+
|
|
77
|
+
def _rollback_can_be_called(self) -> bool:
|
|
78
|
+
"""indicates the object is in a state that is known to be acceptable
|
|
79
|
+
for rollback() to be called.
|
|
80
|
+
|
|
81
|
+
This does not necessarily mean rollback() will succeed or not raise
|
|
82
|
+
an error, just that there is currently no state detected that indicates
|
|
83
|
+
rollback() would fail or emit warnings.
|
|
84
|
+
|
|
85
|
+
It also does not mean that there's a transaction in progress, as
|
|
86
|
+
it is usually safe to call rollback() even if no transaction is
|
|
87
|
+
present.
|
|
88
|
+
|
|
89
|
+
.. versionadded:: 1.4.28
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
raise NotImplementedError()
|
|
93
|
+
|
|
94
|
+
def _get_subject(self) -> _TConsSubject:
|
|
95
|
+
raise NotImplementedError()
|
|
96
|
+
|
|
97
|
+
def commit(self) -> None:
|
|
98
|
+
raise NotImplementedError()
|
|
99
|
+
|
|
100
|
+
def rollback(self) -> None:
|
|
101
|
+
raise NotImplementedError()
|
|
102
|
+
|
|
103
|
+
def close(self) -> None:
|
|
104
|
+
raise NotImplementedError()
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def _trans_ctx_check(cls, subject: _TConsSubject) -> None:
|
|
108
|
+
trans_context = subject._trans_context_manager
|
|
109
|
+
if trans_context:
|
|
110
|
+
if not trans_context._transaction_is_active():
|
|
111
|
+
raise exc.InvalidRequestError(
|
|
112
|
+
"Can't operate on closed transaction inside context "
|
|
113
|
+
"manager. Please complete the context manager "
|
|
114
|
+
"before emitting further commands."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def __enter__(self) -> Self:
|
|
118
|
+
subject = self._get_subject()
|
|
119
|
+
|
|
120
|
+
# none for outer transaction, may be non-None for nested
|
|
121
|
+
# savepoint, legacy nesting cases
|
|
122
|
+
trans_context = subject._trans_context_manager
|
|
123
|
+
self._outer_trans_ctx = trans_context
|
|
124
|
+
|
|
125
|
+
self._trans_subject = subject
|
|
126
|
+
subject._trans_context_manager = self
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
|
|
130
|
+
subject = getattr(self, "_trans_subject", None)
|
|
131
|
+
|
|
132
|
+
# simplistically we could assume that
|
|
133
|
+
# "subject._trans_context_manager is self". However, any calling
|
|
134
|
+
# code that is manipulating __exit__ directly would break this
|
|
135
|
+
# assumption. alembic context manager
|
|
136
|
+
# is an example of partial use that just calls __exit__ and
|
|
137
|
+
# not __enter__ at the moment. it's safe to assume this is being done
|
|
138
|
+
# in the wild also
|
|
139
|
+
out_of_band_exit = (
|
|
140
|
+
subject is None or subject._trans_context_manager is not self
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if type_ is None and self._transaction_is_active():
|
|
144
|
+
try:
|
|
145
|
+
self.commit()
|
|
146
|
+
except:
|
|
147
|
+
with util.safe_reraise():
|
|
148
|
+
if self._rollback_can_be_called():
|
|
149
|
+
self.rollback()
|
|
150
|
+
finally:
|
|
151
|
+
if not out_of_band_exit:
|
|
152
|
+
assert subject is not None
|
|
153
|
+
subject._trans_context_manager = self._outer_trans_ctx
|
|
154
|
+
self._trans_subject = self._outer_trans_ctx = None
|
|
155
|
+
else:
|
|
156
|
+
try:
|
|
157
|
+
if not self._transaction_is_active():
|
|
158
|
+
if not self._transaction_is_closed():
|
|
159
|
+
self.close()
|
|
160
|
+
else:
|
|
161
|
+
if self._rollback_can_be_called():
|
|
162
|
+
self.rollback()
|
|
163
|
+
finally:
|
|
164
|
+
if not out_of_band_exit:
|
|
165
|
+
assert subject is not None
|
|
166
|
+
subject._trans_context_manager = self._outer_trans_ctx
|
|
167
|
+
self._trans_subject = self._outer_trans_ctx = None
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# event/__init__.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .api import CANCEL as CANCEL
|
|
11
|
+
from .api import contains as contains
|
|
12
|
+
from .api import listen as listen
|
|
13
|
+
from .api import listens_for as listens_for
|
|
14
|
+
from .api import NO_RETVAL as NO_RETVAL
|
|
15
|
+
from .api import remove as remove
|
|
16
|
+
from .attr import _InstanceLevelDispatch as _InstanceLevelDispatch
|
|
17
|
+
from .attr import RefCollection as RefCollection
|
|
18
|
+
from .base import _Dispatch as _Dispatch
|
|
19
|
+
from .base import _DispatchCommon as _DispatchCommon
|
|
20
|
+
from .base import dispatcher as dispatcher
|
|
21
|
+
from .base import Events as Events
|
|
22
|
+
from .legacy import _legacy_signature as _legacy_signature
|
|
23
|
+
from .legacy import _omit_standard_example as _omit_standard_example
|
|
24
|
+
from .registry import _EventKey as _EventKey
|
|
25
|
+
from .registry import _ListenerFnType as _ListenerFnType
|
|
26
|
+
from .registry import EventTarget as EventTarget
|
sqlalchemy/event/api.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# event/api.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
"""Public API functions for the event system."""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
from .base import _registrars
|
|
15
|
+
from .registry import _ET
|
|
16
|
+
from .registry import _EventKey
|
|
17
|
+
from .registry import _ListenerFnType
|
|
18
|
+
from .. import exc
|
|
19
|
+
from .. import util
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
CANCEL = util.symbol("CANCEL")
|
|
23
|
+
NO_RETVAL = util.symbol("NO_RETVAL")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _event_key(
|
|
27
|
+
target: _ET, identifier: str, fn: _ListenerFnType
|
|
28
|
+
) -> _EventKey[_ET]:
|
|
29
|
+
for evt_cls in _registrars[identifier]:
|
|
30
|
+
tgt = evt_cls._accept_with(target, identifier)
|
|
31
|
+
if tgt is not None:
|
|
32
|
+
return _EventKey(target, identifier, fn, tgt)
|
|
33
|
+
else:
|
|
34
|
+
raise exc.InvalidRequestError(
|
|
35
|
+
"No such event '%s' for target '%s'" % (identifier, target)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def listen(
|
|
40
|
+
target: Any, identifier: str, fn: Callable[..., Any], *args: Any, **kw: Any
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Register a listener function for the given target.
|
|
43
|
+
|
|
44
|
+
The :func:`.listen` function is part of the primary interface for the
|
|
45
|
+
SQLAlchemy event system, documented at :ref:`event_toplevel`.
|
|
46
|
+
|
|
47
|
+
e.g.::
|
|
48
|
+
|
|
49
|
+
from sqlalchemy import event
|
|
50
|
+
from sqlalchemy.schema import UniqueConstraint
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def unique_constraint_name(const, table):
|
|
54
|
+
const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
event.listen(
|
|
58
|
+
UniqueConstraint, "after_parent_attach", unique_constraint_name
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
:param bool insert: The default behavior for event handlers is to append
|
|
62
|
+
the decorated user defined function to an internal list of registered
|
|
63
|
+
event listeners upon discovery. If a user registers a function with
|
|
64
|
+
``insert=True``, SQLAlchemy will insert (prepend) the function to the
|
|
65
|
+
internal list upon discovery. This feature is not typically used or
|
|
66
|
+
recommended by the SQLAlchemy maintainers, but is provided to ensure
|
|
67
|
+
certain user defined functions can run before others, such as when
|
|
68
|
+
:ref:`Changing the sql_mode in MySQL <mysql_sql_mode>`.
|
|
69
|
+
|
|
70
|
+
:param bool named: When using named argument passing, the names listed in
|
|
71
|
+
the function argument specification will be used as keys in the
|
|
72
|
+
dictionary.
|
|
73
|
+
See :ref:`event_named_argument_styles`.
|
|
74
|
+
|
|
75
|
+
:param bool once: Private/Internal API usage. Deprecated. This parameter
|
|
76
|
+
would provide that an event function would run only once per given
|
|
77
|
+
target. It does not however imply automatic de-registration of the
|
|
78
|
+
listener function; associating an arbitrarily high number of listeners
|
|
79
|
+
without explicitly removing them will cause memory to grow unbounded even
|
|
80
|
+
if ``once=True`` is specified.
|
|
81
|
+
|
|
82
|
+
:param bool propagate: The ``propagate`` kwarg is available when working
|
|
83
|
+
with ORM instrumentation and mapping events.
|
|
84
|
+
See :class:`_ormevent.MapperEvents` and
|
|
85
|
+
:meth:`_ormevent.MapperEvents.before_mapper_configured` for examples.
|
|
86
|
+
|
|
87
|
+
:param bool retval: This flag applies only to specific event listeners,
|
|
88
|
+
each of which includes documentation explaining when it should be used.
|
|
89
|
+
By default, no listener ever requires a return value.
|
|
90
|
+
However, some listeners do support special behaviors for return values,
|
|
91
|
+
and include in their documentation that the ``retval=True`` flag is
|
|
92
|
+
necessary for a return value to be processed.
|
|
93
|
+
|
|
94
|
+
Event listener suites that make use of :paramref:`_event.listen.retval`
|
|
95
|
+
include :class:`_events.ConnectionEvents` and
|
|
96
|
+
:class:`_ormevent.AttributeEvents`.
|
|
97
|
+
|
|
98
|
+
.. note::
|
|
99
|
+
|
|
100
|
+
The :func:`.listen` function cannot be called at the same time
|
|
101
|
+
that the target event is being run. This has implications
|
|
102
|
+
for thread safety, and also means an event cannot be added
|
|
103
|
+
from inside the listener function for itself. The list of
|
|
104
|
+
events to be run are present inside of a mutable collection
|
|
105
|
+
that can't be changed during iteration.
|
|
106
|
+
|
|
107
|
+
Event registration and removal is not intended to be a "high
|
|
108
|
+
velocity" operation; it is a configurational operation. For
|
|
109
|
+
systems that need to quickly associate and deassociate with
|
|
110
|
+
events at high scale, use a mutable structure that is handled
|
|
111
|
+
from inside of a single listener.
|
|
112
|
+
|
|
113
|
+
.. seealso::
|
|
114
|
+
|
|
115
|
+
:func:`.listens_for`
|
|
116
|
+
|
|
117
|
+
:func:`.remove`
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
_event_key(target, identifier, fn).listen(*args, **kw)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def listens_for(
|
|
125
|
+
target: Any, identifier: str, *args: Any, **kw: Any
|
|
126
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
127
|
+
"""Decorate a function as a listener for the given target + identifier.
|
|
128
|
+
|
|
129
|
+
The :func:`.listens_for` decorator is part of the primary interface for the
|
|
130
|
+
SQLAlchemy event system, documented at :ref:`event_toplevel`.
|
|
131
|
+
|
|
132
|
+
This function generally shares the same kwargs as :func:`.listen`.
|
|
133
|
+
|
|
134
|
+
e.g.::
|
|
135
|
+
|
|
136
|
+
from sqlalchemy import event
|
|
137
|
+
from sqlalchemy.schema import UniqueConstraint
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@event.listens_for(UniqueConstraint, "after_parent_attach")
|
|
141
|
+
def unique_constraint_name(const, table):
|
|
142
|
+
const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)
|
|
143
|
+
|
|
144
|
+
A given function can also be invoked for only the first invocation
|
|
145
|
+
of the event using the ``once`` argument::
|
|
146
|
+
|
|
147
|
+
@event.listens_for(Mapper, "before_configure", once=True)
|
|
148
|
+
def on_config():
|
|
149
|
+
do_config()
|
|
150
|
+
|
|
151
|
+
.. warning:: The ``once`` argument does not imply automatic de-registration
|
|
152
|
+
of the listener function after it has been invoked a first time; a
|
|
153
|
+
listener entry will remain associated with the target object.
|
|
154
|
+
Associating an arbitrarily high number of listeners without explicitly
|
|
155
|
+
removing them will cause memory to grow unbounded even if ``once=True``
|
|
156
|
+
is specified.
|
|
157
|
+
|
|
158
|
+
.. seealso::
|
|
159
|
+
|
|
160
|
+
:func:`.listen` - general description of event listening
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def decorate(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
165
|
+
listen(target, identifier, fn, *args, **kw)
|
|
166
|
+
return fn
|
|
167
|
+
|
|
168
|
+
return decorate
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def remove(target: Any, identifier: str, fn: Callable[..., Any]) -> None:
|
|
172
|
+
"""Remove an event listener.
|
|
173
|
+
|
|
174
|
+
The arguments here should match exactly those which were sent to
|
|
175
|
+
:func:`.listen`; all the event registration which proceeded as a result
|
|
176
|
+
of this call will be reverted by calling :func:`.remove` with the same
|
|
177
|
+
arguments.
|
|
178
|
+
|
|
179
|
+
e.g.::
|
|
180
|
+
|
|
181
|
+
# if a function was registered like this...
|
|
182
|
+
@event.listens_for(SomeMappedClass, "before_insert", propagate=True)
|
|
183
|
+
def my_listener_function(*arg):
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ... it's removed like this
|
|
188
|
+
event.remove(SomeMappedClass, "before_insert", my_listener_function)
|
|
189
|
+
|
|
190
|
+
Above, the listener function associated with ``SomeMappedClass`` was also
|
|
191
|
+
propagated to subclasses of ``SomeMappedClass``; the :func:`.remove`
|
|
192
|
+
function will revert all of these operations.
|
|
193
|
+
|
|
194
|
+
.. note::
|
|
195
|
+
|
|
196
|
+
The :func:`.remove` function cannot be called at the same time
|
|
197
|
+
that the target event is being run. This has implications
|
|
198
|
+
for thread safety, and also means an event cannot be removed
|
|
199
|
+
from inside the listener function for itself. The list of
|
|
200
|
+
events to be run are present inside of a mutable collection
|
|
201
|
+
that can't be changed during iteration.
|
|
202
|
+
|
|
203
|
+
Event registration and removal is not intended to be a "high
|
|
204
|
+
velocity" operation; it is a configurational operation. For
|
|
205
|
+
systems that need to quickly associate and deassociate with
|
|
206
|
+
events at high scale, use a mutable structure that is handled
|
|
207
|
+
from inside of a single listener.
|
|
208
|
+
|
|
209
|
+
.. seealso::
|
|
210
|
+
|
|
211
|
+
:func:`.listen`
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
_event_key(target, identifier, fn).remove()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def contains(target: Any, identifier: str, fn: Callable[..., Any]) -> bool:
|
|
218
|
+
"""Return True if the given target/ident/fn is set up to listen."""
|
|
219
|
+
|
|
220
|
+
return _event_key(target, identifier, fn).contains()
|