SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,258 @@
1
+ # event/legacy.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
+ """Routines to handle adaption of legacy call signatures,
9
+ generation of deprecation notes and docstrings.
10
+
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import typing
15
+ from typing import Any
16
+ from typing import Callable
17
+ from typing import List
18
+ from typing import Optional
19
+ from typing import Tuple
20
+ from typing import Type
21
+ from typing import TypeVar
22
+
23
+ from .registry import _ET
24
+ from .registry import _ListenerFnType
25
+ from .. import util
26
+ from ..util.compat import FullArgSpec
27
+
28
+ if typing.TYPE_CHECKING:
29
+ from .attr import _ClsLevelDispatch
30
+ from .base import _HasEventsDispatch
31
+
32
+
33
+ _F = TypeVar("_F", bound=Callable[..., Any])
34
+
35
+ _LegacySignatureType = Tuple[str, List[str], Callable[..., Any]]
36
+
37
+
38
+ def _legacy_signature(
39
+ since: str,
40
+ argnames: List[str],
41
+ converter: Optional[Callable[..., Any]] = None,
42
+ ) -> Callable[[_F], _F]:
43
+ """legacy sig decorator
44
+
45
+
46
+ :param since: string version for deprecation warning
47
+ :param argnames: list of strings, which is *all* arguments that the legacy
48
+ version accepted, including arguments that are still there
49
+ :param converter: lambda that will accept tuple of this full arg signature
50
+ and return tuple of new arg signature.
51
+
52
+ """
53
+
54
+ def leg(fn: _F) -> _F:
55
+ if not hasattr(fn, "_legacy_signatures"):
56
+ fn._legacy_signatures = [] # type: ignore[attr-defined]
57
+ fn._legacy_signatures.append((since, argnames, converter)) # type: ignore[attr-defined] # noqa: E501
58
+ return fn
59
+
60
+ return leg
61
+
62
+
63
+ def _omit_standard_example(fn: _F) -> _F:
64
+ fn._omit_standard_example = True # type: ignore[attr-defined]
65
+ return fn
66
+
67
+
68
+ def _wrap_fn_for_legacy(
69
+ dispatch_collection: _ClsLevelDispatch[_ET],
70
+ fn: _ListenerFnType,
71
+ argspec: FullArgSpec,
72
+ ) -> _ListenerFnType:
73
+ for since, argnames, conv in dispatch_collection.legacy_signatures:
74
+ if argnames[-1] == "**kw":
75
+ has_kw = True
76
+ argnames = argnames[0:-1]
77
+ else:
78
+ has_kw = False
79
+
80
+ if len(argnames) == len(argspec.args) and has_kw is bool(
81
+ argspec.varkw
82
+ ):
83
+ formatted_def = "def %s(%s%s)" % (
84
+ dispatch_collection.name,
85
+ ", ".join(dispatch_collection.arg_names),
86
+ ", **kw" if has_kw else "",
87
+ )
88
+ warning_txt = (
89
+ 'The argument signature for the "%s.%s" event listener '
90
+ "has changed as of version %s, and conversion for "
91
+ "the old argument signature will be removed in a "
92
+ 'future release. The new signature is "%s"'
93
+ % (
94
+ dispatch_collection.clsname,
95
+ dispatch_collection.name,
96
+ since,
97
+ formatted_def,
98
+ )
99
+ )
100
+
101
+ if conv is not None:
102
+ assert not has_kw
103
+
104
+ def wrap_leg(*args: Any, **kw: Any) -> Any:
105
+ util.warn_deprecated(warning_txt, version=since)
106
+ assert conv is not None
107
+ return fn(*conv(*args))
108
+
109
+ else:
110
+
111
+ def wrap_leg(*args: Any, **kw: Any) -> Any:
112
+ util.warn_deprecated(warning_txt, version=since)
113
+ argdict = dict(zip(dispatch_collection.arg_names, args))
114
+ args_from_dict = [argdict[name] for name in argnames]
115
+ if has_kw:
116
+ return fn(*args_from_dict, **kw)
117
+ else:
118
+ return fn(*args_from_dict)
119
+
120
+ return wrap_leg
121
+ else:
122
+ return fn
123
+
124
+
125
+ def _indent(text: str, indent: str) -> str:
126
+ return "\n".join(indent + line for line in text.split("\n"))
127
+
128
+
129
+ def _standard_listen_example(
130
+ dispatch_collection: _ClsLevelDispatch[_ET],
131
+ sample_target: Any,
132
+ fn: _ListenerFnType,
133
+ ) -> str:
134
+ example_kw_arg = _indent(
135
+ "\n".join(
136
+ "%(arg)s = kw['%(arg)s']" % {"arg": arg}
137
+ for arg in dispatch_collection.arg_names[0:2]
138
+ ),
139
+ " ",
140
+ )
141
+ if dispatch_collection.legacy_signatures:
142
+ current_since = max(
143
+ since
144
+ for since, args, conv in dispatch_collection.legacy_signatures
145
+ )
146
+ else:
147
+ current_since = None
148
+ text = (
149
+ "from sqlalchemy import event\n\n\n"
150
+ "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
151
+ "def receive_%(event_name)s("
152
+ "%(named_event_arguments)s%(has_kw_arguments)s):\n"
153
+ " \"listen for the '%(event_name)s' event\"\n"
154
+ "\n # ... (event handling logic) ...\n"
155
+ )
156
+
157
+ text %= {
158
+ "current_since": (
159
+ " (arguments as of %s)" % current_since if current_since else ""
160
+ ),
161
+ "event_name": fn.__name__,
162
+ "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
163
+ "named_event_arguments": ", ".join(dispatch_collection.arg_names),
164
+ "example_kw_arg": example_kw_arg,
165
+ "sample_target": sample_target,
166
+ }
167
+ return text
168
+
169
+
170
+ def _legacy_listen_examples(
171
+ dispatch_collection: _ClsLevelDispatch[_ET],
172
+ sample_target: str,
173
+ fn: _ListenerFnType,
174
+ ) -> str:
175
+ text = ""
176
+ for since, args, conv in dispatch_collection.legacy_signatures:
177
+ text += (
178
+ "\n# DEPRECATED calling style (pre-%(since)s, "
179
+ "will be removed in a future release)\n"
180
+ "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
181
+ "def receive_%(event_name)s("
182
+ "%(named_event_arguments)s%(has_kw_arguments)s):\n"
183
+ " \"listen for the '%(event_name)s' event\"\n"
184
+ "\n # ... (event handling logic) ...\n"
185
+ % {
186
+ "since": since,
187
+ "event_name": fn.__name__,
188
+ "has_kw_arguments": (
189
+ " **kw" if dispatch_collection.has_kw else ""
190
+ ),
191
+ "named_event_arguments": ", ".join(args),
192
+ "sample_target": sample_target,
193
+ }
194
+ )
195
+ return text
196
+
197
+
198
+ def _version_signature_changes(
199
+ parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
200
+ dispatch_collection: _ClsLevelDispatch[_ET],
201
+ ) -> str:
202
+ since, args, conv = dispatch_collection.legacy_signatures[0]
203
+ return (
204
+ "\n.. versionchanged:: %(since)s\n"
205
+ " The :meth:`.%(clsname)s.%(event_name)s` event now accepts the \n"
206
+ " arguments %(named_event_arguments)s%(has_kw_arguments)s.\n"
207
+ " Support for listener functions which accept the previous \n"
208
+ ' argument signature(s) listed above as "deprecated" will be \n'
209
+ " removed in a future release."
210
+ % {
211
+ "since": since,
212
+ "clsname": parent_dispatch_cls.__name__,
213
+ "event_name": dispatch_collection.name,
214
+ "named_event_arguments": ", ".join(
215
+ ":paramref:`.%(clsname)s.%(event_name)s.%(param_name)s`"
216
+ % {
217
+ "clsname": parent_dispatch_cls.__name__,
218
+ "event_name": dispatch_collection.name,
219
+ "param_name": param_name,
220
+ }
221
+ for param_name in dispatch_collection.arg_names
222
+ ),
223
+ "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
224
+ }
225
+ )
226
+
227
+
228
+ def _augment_fn_docs(
229
+ dispatch_collection: _ClsLevelDispatch[_ET],
230
+ parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
231
+ fn: _ListenerFnType,
232
+ ) -> str:
233
+ if getattr(fn, "_omit_standard_example", False):
234
+ assert fn.__doc__
235
+ return fn.__doc__
236
+
237
+ header = (
238
+ ".. container:: event_signatures\n\n"
239
+ " Example argument forms::\n"
240
+ "\n"
241
+ )
242
+
243
+ sample_target = getattr(parent_dispatch_cls, "_target_class_doc", "obj")
244
+ text = header + _indent(
245
+ _standard_listen_example(dispatch_collection, sample_target, fn),
246
+ " " * 8,
247
+ )
248
+ if dispatch_collection.legacy_signatures:
249
+ text += _indent(
250
+ _legacy_listen_examples(dispatch_collection, sample_target, fn),
251
+ " " * 8,
252
+ )
253
+
254
+ text += _version_signature_changes(
255
+ parent_dispatch_cls, dispatch_collection
256
+ )
257
+
258
+ return util.inject_docstring_text(fn.__doc__, text, 1)
@@ -0,0 +1,390 @@
1
+ # event/registry.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
+ """Provides managed registration services on behalf of :func:`.listen`
9
+ arguments.
10
+
11
+ By "managed registration", we mean that event listening functions and
12
+ other objects can be added to various collections in such a way that their
13
+ membership in all those collections can be revoked at once, based on
14
+ an equivalent :class:`._EventKey`.
15
+
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import collections
20
+ import types
21
+ import typing
22
+ from typing import Any
23
+ from typing import Callable
24
+ from typing import cast
25
+ from typing import Deque
26
+ from typing import Dict
27
+ from typing import Generic
28
+ from typing import Iterable
29
+ from typing import Optional
30
+ from typing import Tuple
31
+ from typing import TypeVar
32
+ from typing import Union
33
+ import weakref
34
+
35
+ from .. import exc
36
+ from .. import util
37
+
38
+ if typing.TYPE_CHECKING:
39
+ from .attr import RefCollection
40
+ from .base import dispatcher
41
+
42
+ _ListenerFnType = Callable[..., Any]
43
+ _ListenerFnKeyType = Union[int, Tuple[int, int]]
44
+ _EventKeyTupleType = Tuple[int, str, _ListenerFnKeyType]
45
+
46
+
47
+ _ET = TypeVar("_ET", bound="EventTarget")
48
+
49
+
50
+ class EventTarget:
51
+ """represents an event target, that is, something we can listen on
52
+ either with that target as a class or as an instance.
53
+
54
+ Examples include: Connection, Mapper, Table, Session,
55
+ InstrumentedAttribute, Engine, Pool, Dialect.
56
+
57
+ """
58
+
59
+ __slots__ = ()
60
+
61
+ dispatch: dispatcher[Any]
62
+
63
+
64
+ _RefCollectionToListenerType = Dict[
65
+ "weakref.ref[RefCollection[Any]]",
66
+ "weakref.ref[_ListenerFnType]",
67
+ ]
68
+
69
+ _key_to_collection: Dict[_EventKeyTupleType, _RefCollectionToListenerType] = (
70
+ collections.defaultdict(dict)
71
+ )
72
+ """
73
+ Given an original listen() argument, can locate all
74
+ listener collections and the listener fn contained
75
+
76
+ (target, identifier, fn) -> {
77
+ ref(listenercollection) -> ref(listener_fn)
78
+ ref(listenercollection) -> ref(listener_fn)
79
+ ref(listenercollection) -> ref(listener_fn)
80
+ }
81
+ """
82
+
83
+ _ListenerToEventKeyType = Dict[
84
+ "weakref.ref[_ListenerFnType]",
85
+ _EventKeyTupleType,
86
+ ]
87
+ _collection_to_key: Dict[
88
+ weakref.ref[RefCollection[Any]],
89
+ _ListenerToEventKeyType,
90
+ ] = collections.defaultdict(dict)
91
+ """
92
+ Given a _ListenerCollection or _ClsLevelListener, can locate
93
+ all the original listen() arguments and the listener fn contained
94
+
95
+ ref(listenercollection) -> {
96
+ ref(listener_fn) -> (target, identifier, fn),
97
+ ref(listener_fn) -> (target, identifier, fn),
98
+ ref(listener_fn) -> (target, identifier, fn),
99
+ }
100
+ """
101
+
102
+
103
+ def _collection_gced(ref: weakref.ref[Any]) -> None:
104
+ # defaultdict, so can't get a KeyError
105
+ if not _collection_to_key or ref not in _collection_to_key:
106
+ return
107
+
108
+ ref = cast("weakref.ref[RefCollection[EventTarget]]", ref)
109
+
110
+ listener_to_key = _collection_to_key.pop(ref)
111
+ for key in listener_to_key.values():
112
+ if key in _key_to_collection:
113
+ # defaultdict, so can't get a KeyError
114
+ dispatch_reg = _key_to_collection[key]
115
+ dispatch_reg.pop(ref)
116
+ if not dispatch_reg:
117
+ _key_to_collection.pop(key)
118
+
119
+
120
+ def _stored_in_collection(
121
+ event_key: _EventKey[_ET], owner: RefCollection[_ET]
122
+ ) -> bool:
123
+ key = event_key._key
124
+
125
+ dispatch_reg = _key_to_collection[key]
126
+
127
+ owner_ref = owner.ref
128
+ listen_ref = weakref.ref(event_key._listen_fn)
129
+
130
+ if owner_ref in dispatch_reg:
131
+ return False
132
+
133
+ dispatch_reg[owner_ref] = listen_ref
134
+
135
+ listener_to_key = _collection_to_key[owner_ref]
136
+ listener_to_key[listen_ref] = key
137
+
138
+ return True
139
+
140
+
141
+ def _removed_from_collection(
142
+ event_key: _EventKey[_ET], owner: RefCollection[_ET]
143
+ ) -> None:
144
+ key = event_key._key
145
+
146
+ dispatch_reg = _key_to_collection[key]
147
+
148
+ listen_ref = weakref.ref(event_key._listen_fn)
149
+
150
+ owner_ref = owner.ref
151
+ dispatch_reg.pop(owner_ref, None)
152
+ if not dispatch_reg:
153
+ del _key_to_collection[key]
154
+
155
+ if owner_ref in _collection_to_key:
156
+ listener_to_key = _collection_to_key[owner_ref]
157
+ # see #12216 - this guards against a removal that already occurred
158
+ # here. however, I cannot come up with a test that shows any negative
159
+ # side effects occurring from this removal happening, even though an
160
+ # event key may still be referenced from a clsleveldispatch here
161
+ listener_to_key.pop(listen_ref, None)
162
+
163
+
164
+ def _stored_in_collection_multi(
165
+ newowner: RefCollection[_ET],
166
+ oldowner: RefCollection[_ET],
167
+ elements: Iterable[_ListenerFnType],
168
+ ) -> None:
169
+ if not elements:
170
+ return
171
+
172
+ oldowner_ref = oldowner.ref
173
+ newowner_ref = newowner.ref
174
+
175
+ old_listener_to_key = _collection_to_key[oldowner_ref]
176
+ new_listener_to_key = _collection_to_key[newowner_ref]
177
+
178
+ for listen_fn in elements:
179
+ listen_ref = weakref.ref(listen_fn)
180
+ try:
181
+ key = old_listener_to_key[listen_ref]
182
+ except KeyError:
183
+ # can occur during interpreter shutdown.
184
+ # see #6740
185
+ continue
186
+
187
+ try:
188
+ dispatch_reg = _key_to_collection[key]
189
+ except KeyError:
190
+ continue
191
+
192
+ if newowner_ref in dispatch_reg:
193
+ assert dispatch_reg[newowner_ref] == listen_ref
194
+ else:
195
+ dispatch_reg[newowner_ref] = listen_ref
196
+
197
+ new_listener_to_key[listen_ref] = key
198
+
199
+
200
+ def _clear(
201
+ owner: RefCollection[_ET],
202
+ elements: Iterable[_ListenerFnType],
203
+ ) -> None:
204
+ if not elements:
205
+ return
206
+
207
+ owner_ref = owner.ref
208
+ listener_to_key = _collection_to_key[owner_ref]
209
+ for listen_fn in elements:
210
+ listen_ref = weakref.ref(listen_fn)
211
+ key = listener_to_key[listen_ref]
212
+ dispatch_reg = _key_to_collection[key]
213
+ dispatch_reg.pop(owner_ref, None)
214
+
215
+ if not dispatch_reg:
216
+ del _key_to_collection[key]
217
+
218
+
219
+ class _EventKey(Generic[_ET]):
220
+ """Represent :func:`.listen` arguments."""
221
+
222
+ __slots__ = (
223
+ "target",
224
+ "identifier",
225
+ "fn",
226
+ "fn_key",
227
+ "fn_wrap",
228
+ "dispatch_target",
229
+ )
230
+
231
+ target: _ET
232
+ identifier: str
233
+ fn: _ListenerFnType
234
+ fn_key: _ListenerFnKeyType
235
+ dispatch_target: Any
236
+ _fn_wrap: Optional[_ListenerFnType]
237
+
238
+ def __init__(
239
+ self,
240
+ target: _ET,
241
+ identifier: str,
242
+ fn: _ListenerFnType,
243
+ dispatch_target: Any,
244
+ _fn_wrap: Optional[_ListenerFnType] = None,
245
+ ):
246
+ self.target = target
247
+ self.identifier = identifier
248
+ self.fn = fn
249
+ if isinstance(fn, types.MethodType):
250
+ self.fn_key = id(fn.__func__), id(fn.__self__)
251
+ else:
252
+ self.fn_key = id(fn)
253
+ self.fn_wrap = _fn_wrap
254
+ self.dispatch_target = dispatch_target
255
+
256
+ @property
257
+ def _key(self) -> _EventKeyTupleType:
258
+ return (id(self.target), self.identifier, self.fn_key)
259
+
260
+ def with_wrapper(self, fn_wrap: _ListenerFnType) -> _EventKey[_ET]:
261
+ if fn_wrap is self._listen_fn:
262
+ return self
263
+ else:
264
+ return _EventKey(
265
+ self.target,
266
+ self.identifier,
267
+ self.fn,
268
+ self.dispatch_target,
269
+ _fn_wrap=fn_wrap,
270
+ )
271
+
272
+ def with_dispatch_target(self, dispatch_target: Any) -> _EventKey[_ET]:
273
+ if dispatch_target is self.dispatch_target:
274
+ return self
275
+ else:
276
+ return _EventKey(
277
+ self.target,
278
+ self.identifier,
279
+ self.fn,
280
+ dispatch_target,
281
+ _fn_wrap=self.fn_wrap,
282
+ )
283
+
284
+ def listen(self, *args: Any, **kw: Any) -> None:
285
+ once = kw.pop("once", False)
286
+ once_unless_exception = kw.pop("_once_unless_exception", False)
287
+ named = kw.pop("named", False)
288
+
289
+ target, identifier, fn = (
290
+ self.dispatch_target,
291
+ self.identifier,
292
+ self._listen_fn,
293
+ )
294
+
295
+ dispatch_collection = getattr(target.dispatch, identifier)
296
+
297
+ adjusted_fn = dispatch_collection._adjust_fn_spec(fn, named)
298
+
299
+ self = self.with_wrapper(adjusted_fn)
300
+
301
+ stub_function = getattr(
302
+ self.dispatch_target.dispatch._events, self.identifier
303
+ )
304
+ if hasattr(stub_function, "_sa_warn"):
305
+ stub_function._sa_warn()
306
+
307
+ if once or once_unless_exception:
308
+ self.with_wrapper(
309
+ util.only_once(
310
+ self._listen_fn, retry_on_exception=once_unless_exception
311
+ )
312
+ ).listen(*args, **kw)
313
+ else:
314
+ self.dispatch_target.dispatch._listen(self, *args, **kw)
315
+
316
+ def remove(self) -> None:
317
+ key = self._key
318
+
319
+ if key not in _key_to_collection:
320
+ raise exc.InvalidRequestError(
321
+ "No listeners found for event %s / %r / %s "
322
+ % (self.target, self.identifier, self.fn)
323
+ )
324
+
325
+ dispatch_reg = _key_to_collection.pop(key)
326
+
327
+ for collection_ref, listener_ref in dispatch_reg.items():
328
+ collection = collection_ref()
329
+ listener_fn = listener_ref()
330
+ if collection is not None and listener_fn is not None:
331
+ collection.remove(self.with_wrapper(listener_fn))
332
+
333
+ def contains(self) -> bool:
334
+ """Return True if this event key is registered to listen."""
335
+ return self._key in _key_to_collection
336
+
337
+ def base_listen(
338
+ self,
339
+ propagate: bool = False,
340
+ insert: bool = False,
341
+ named: bool = False,
342
+ retval: Optional[bool] = None,
343
+ asyncio: bool = False,
344
+ ) -> None:
345
+ target, identifier = self.dispatch_target, self.identifier
346
+
347
+ dispatch_collection = getattr(target.dispatch, identifier)
348
+
349
+ for_modify = dispatch_collection.for_modify(target.dispatch)
350
+ if asyncio:
351
+ for_modify._set_asyncio()
352
+
353
+ if insert:
354
+ for_modify.insert(self, propagate)
355
+ else:
356
+ for_modify.append(self, propagate)
357
+
358
+ @property
359
+ def _listen_fn(self) -> _ListenerFnType:
360
+ return self.fn_wrap or self.fn
361
+
362
+ def append_to_list(
363
+ self,
364
+ owner: RefCollection[_ET],
365
+ list_: Deque[_ListenerFnType],
366
+ ) -> bool:
367
+ if _stored_in_collection(self, owner):
368
+ list_.append(self._listen_fn)
369
+ return True
370
+ else:
371
+ return False
372
+
373
+ def remove_from_list(
374
+ self,
375
+ owner: RefCollection[_ET],
376
+ list_: Deque[_ListenerFnType],
377
+ ) -> None:
378
+ _removed_from_collection(self, owner)
379
+ list_.remove(self._listen_fn)
380
+
381
+ def prepend_to_list(
382
+ self,
383
+ owner: RefCollection[_ET],
384
+ list_: Deque[_ListenerFnType],
385
+ ) -> bool:
386
+ if _stored_in_collection(self, owner):
387
+ list_.appendleft(self._listen_fn)
388
+ return True
389
+ else:
390
+ return False
sqlalchemy/events.py ADDED
@@ -0,0 +1,17 @@
1
+ # events.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
+ """Core event interfaces."""
9
+
10
+ from __future__ import annotations
11
+
12
+ from .engine.events import ConnectionEvents
13
+ from .engine.events import DialectEvents
14
+ from .pool import PoolResetState
15
+ from .pool.events import PoolEvents
16
+ from .sql.base import SchemaEventTarget
17
+ from .sql.events import DDLEvents