SQLAlchemy 2.1.0rc1__cp315-cp315-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 (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win32.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win32.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win32.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win32.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win32.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win32.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win32.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win32.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,259 @@
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
+
13
+ from __future__ import annotations
14
+
15
+ import typing
16
+ from typing import Any
17
+ from typing import Callable
18
+ from typing import List
19
+ from typing import Optional
20
+ from typing import Tuple
21
+ from typing import Type
22
+ from typing import TypeVar
23
+
24
+ from .registry import _ET
25
+ from .registry import _ListenerFnType
26
+ from .. import util
27
+ from ..util.compat import FullArgSpec
28
+
29
+ if typing.TYPE_CHECKING:
30
+ from .attr import _ClsLevelDispatch
31
+ from .base import _HasEventsDispatch
32
+
33
+
34
+ _F = TypeVar("_F", bound=Callable[..., Any])
35
+
36
+ _LegacySignatureType = Tuple[str, List[str], Callable[..., Any]]
37
+
38
+
39
+ def _legacy_signature(
40
+ since: str,
41
+ argnames: List[str],
42
+ converter: Optional[Callable[..., Any]] = None,
43
+ ) -> Callable[[_F], _F]:
44
+ """legacy sig decorator
45
+
46
+
47
+ :param since: string version for deprecation warning
48
+ :param argnames: list of strings, which is *all* arguments that the legacy
49
+ version accepted, including arguments that are still there
50
+ :param converter: lambda that will accept tuple of this full arg signature
51
+ and return tuple of new arg signature.
52
+
53
+ """
54
+
55
+ def leg(fn: _F) -> _F:
56
+ if not hasattr(fn, "_legacy_signatures"):
57
+ fn._legacy_signatures = [] # type: ignore[attr-defined]
58
+ fn._legacy_signatures.append((since, argnames, converter)) # type: ignore[attr-defined] # noqa: E501
59
+ return fn
60
+
61
+ return leg
62
+
63
+
64
+ def _omit_standard_example(fn: _F) -> _F:
65
+ fn._omit_standard_example = True # type: ignore[attr-defined]
66
+ return fn
67
+
68
+
69
+ def _wrap_fn_for_legacy(
70
+ dispatch_collection: _ClsLevelDispatch[_ET],
71
+ fn: _ListenerFnType,
72
+ argspec: FullArgSpec,
73
+ ) -> _ListenerFnType:
74
+ for since, argnames, conv in dispatch_collection.legacy_signatures:
75
+ if argnames[-1] == "**kw":
76
+ has_kw = True
77
+ argnames = argnames[0:-1]
78
+ else:
79
+ has_kw = False
80
+
81
+ if len(argnames) == len(argspec.args) and has_kw is bool(
82
+ argspec.varkw
83
+ ):
84
+ formatted_def = "def %s(%s%s)" % (
85
+ dispatch_collection.name,
86
+ ", ".join(dispatch_collection.arg_names),
87
+ ", **kw" if has_kw else "",
88
+ )
89
+ warning_txt = (
90
+ 'The argument signature for the "%s.%s" event listener '
91
+ "has changed as of version %s, and conversion for "
92
+ "the old argument signature will be removed in a "
93
+ 'future release. The new signature is "%s"'
94
+ % (
95
+ dispatch_collection.clsname,
96
+ dispatch_collection.name,
97
+ since,
98
+ formatted_def,
99
+ )
100
+ )
101
+
102
+ if conv is not None:
103
+ assert not has_kw
104
+
105
+ def wrap_leg(*args: Any, **kw: Any) -> Any:
106
+ util.warn_deprecated(warning_txt, version=since)
107
+ assert conv is not None
108
+ return fn(*conv(*args))
109
+
110
+ else:
111
+
112
+ def wrap_leg(*args: Any, **kw: Any) -> Any:
113
+ util.warn_deprecated(warning_txt, version=since)
114
+ argdict = dict(zip(dispatch_collection.arg_names, args))
115
+ args_from_dict = [argdict[name] for name in argnames]
116
+ if has_kw:
117
+ return fn(*args_from_dict, **kw)
118
+ else:
119
+ return fn(*args_from_dict)
120
+
121
+ return wrap_leg
122
+ else:
123
+ return fn
124
+
125
+
126
+ def _indent(text: str, indent: str) -> str:
127
+ return "\n".join(indent + line for line in text.split("\n"))
128
+
129
+
130
+ def _standard_listen_example(
131
+ dispatch_collection: _ClsLevelDispatch[_ET],
132
+ sample_target: Any,
133
+ fn: _ListenerFnType,
134
+ ) -> str:
135
+ example_kw_arg = _indent(
136
+ "\n".join(
137
+ "%(arg)s = kw['%(arg)s']" % {"arg": arg}
138
+ for arg in dispatch_collection.arg_names[0:2]
139
+ ),
140
+ " ",
141
+ )
142
+ if dispatch_collection.legacy_signatures:
143
+ current_since = max(
144
+ since
145
+ for since, args, conv in dispatch_collection.legacy_signatures
146
+ )
147
+ else:
148
+ current_since = None
149
+ text = (
150
+ "from sqlalchemy import event\n\n\n"
151
+ "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
152
+ "def receive_%(event_name)s("
153
+ "%(named_event_arguments)s%(has_kw_arguments)s):\n"
154
+ " \"listen for the '%(event_name)s' event\"\n"
155
+ "\n # ... (event handling logic) ...\n"
156
+ )
157
+
158
+ text %= {
159
+ "current_since": (
160
+ " (arguments as of %s)" % current_since if current_since else ""
161
+ ),
162
+ "event_name": fn.__name__,
163
+ "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
164
+ "named_event_arguments": ", ".join(dispatch_collection.arg_names),
165
+ "example_kw_arg": example_kw_arg,
166
+ "sample_target": sample_target,
167
+ }
168
+ return text
169
+
170
+
171
+ def _legacy_listen_examples(
172
+ dispatch_collection: _ClsLevelDispatch[_ET],
173
+ sample_target: str,
174
+ fn: _ListenerFnType,
175
+ ) -> str:
176
+ text = ""
177
+ for since, args, conv in dispatch_collection.legacy_signatures:
178
+ text += (
179
+ "\n# DEPRECATED calling style (pre-%(since)s, "
180
+ "will be removed in a future release)\n"
181
+ "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
182
+ "def receive_%(event_name)s("
183
+ "%(named_event_arguments)s%(has_kw_arguments)s):\n"
184
+ " \"listen for the '%(event_name)s' event\"\n"
185
+ "\n # ... (event handling logic) ...\n"
186
+ % {
187
+ "since": since,
188
+ "event_name": fn.__name__,
189
+ "has_kw_arguments": (
190
+ " **kw" if dispatch_collection.has_kw else ""
191
+ ),
192
+ "named_event_arguments": ", ".join(args),
193
+ "sample_target": sample_target,
194
+ }
195
+ )
196
+ return text
197
+
198
+
199
+ def _version_signature_changes(
200
+ parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
201
+ dispatch_collection: _ClsLevelDispatch[_ET],
202
+ ) -> str:
203
+ since, args, conv = dispatch_collection.legacy_signatures[0]
204
+ return (
205
+ "\n.. versionchanged:: %(since)s\n"
206
+ " The :meth:`.%(clsname)s.%(event_name)s` event now accepts the \n"
207
+ " arguments %(named_event_arguments)s%(has_kw_arguments)s.\n"
208
+ " Support for listener functions which accept the previous \n"
209
+ ' argument signature(s) listed above as "deprecated" will be \n'
210
+ " removed in a future release."
211
+ % {
212
+ "since": since,
213
+ "clsname": parent_dispatch_cls.__name__,
214
+ "event_name": dispatch_collection.name,
215
+ "named_event_arguments": ", ".join(
216
+ ":paramref:`.%(clsname)s.%(event_name)s.%(param_name)s`"
217
+ % {
218
+ "clsname": parent_dispatch_cls.__name__,
219
+ "event_name": dispatch_collection.name,
220
+ "param_name": param_name,
221
+ }
222
+ for param_name in dispatch_collection.arg_names
223
+ ),
224
+ "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
225
+ }
226
+ )
227
+
228
+
229
+ def _augment_fn_docs(
230
+ dispatch_collection: _ClsLevelDispatch[_ET],
231
+ parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
232
+ fn: _ListenerFnType,
233
+ ) -> str:
234
+ if getattr(fn, "_omit_standard_example", False):
235
+ assert fn.__doc__
236
+ return fn.__doc__
237
+
238
+ header = (
239
+ ".. container:: event_signatures\n\n"
240
+ " Example argument forms::\n"
241
+ "\n"
242
+ )
243
+
244
+ sample_target = getattr(parent_dispatch_cls, "_target_class_doc", "obj")
245
+ text = header + _indent(
246
+ _standard_listen_example(dispatch_collection, sample_target, fn),
247
+ " " * 8,
248
+ )
249
+ if dispatch_collection.legacy_signatures:
250
+ text += _indent(
251
+ _legacy_listen_examples(dispatch_collection, sample_target, fn),
252
+ " " * 8,
253
+ )
254
+
255
+ text += _version_signature_changes(
256
+ parent_dispatch_cls, dispatch_collection
257
+ )
258
+
259
+ return util.inject_docstring_text(fn.__doc__, text, 1)
@@ -0,0 +1,391 @@
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
+
18
+ from __future__ import annotations
19
+
20
+ import collections
21
+ import types
22
+ import typing
23
+ from typing import Any
24
+ from typing import Callable
25
+ from typing import cast
26
+ from typing import Deque
27
+ from typing import Dict
28
+ from typing import Generic
29
+ from typing import Iterable
30
+ from typing import Optional
31
+ from typing import Tuple
32
+ from typing import TypeVar
33
+ from typing import Union
34
+ import weakref
35
+
36
+ from .. import exc
37
+ from .. import util
38
+
39
+ if typing.TYPE_CHECKING:
40
+ from .attr import RefCollection
41
+ from .base import dispatcher
42
+
43
+ _ListenerFnType = Callable[..., Any]
44
+ _ListenerFnKeyType = Union[int, Tuple[int, int]]
45
+ _EventKeyTupleType = Tuple[int, str, _ListenerFnKeyType]
46
+
47
+
48
+ _ET = TypeVar("_ET", bound="EventTarget")
49
+
50
+
51
+ class EventTarget:
52
+ """represents an event target, that is, something we can listen on
53
+ either with that target as a class or as an instance.
54
+
55
+ Examples include: Connection, Mapper, Table, Session,
56
+ InstrumentedAttribute, Engine, Pool, Dialect.
57
+
58
+ """
59
+
60
+ __slots__ = ()
61
+
62
+ dispatch: dispatcher[Any]
63
+
64
+
65
+ _RefCollectionToListenerType = Dict[
66
+ "weakref.ref[RefCollection[Any]]",
67
+ "weakref.ref[_ListenerFnType]",
68
+ ]
69
+
70
+ _key_to_collection: Dict[_EventKeyTupleType, _RefCollectionToListenerType] = (
71
+ collections.defaultdict(dict)
72
+ )
73
+ """
74
+ Given an original listen() argument, can locate all
75
+ listener collections and the listener fn contained
76
+
77
+ (target, identifier, fn) -> {
78
+ ref(listenercollection) -> ref(listener_fn)
79
+ ref(listenercollection) -> ref(listener_fn)
80
+ ref(listenercollection) -> ref(listener_fn)
81
+ }
82
+ """
83
+
84
+ _ListenerToEventKeyType = Dict[
85
+ "weakref.ref[_ListenerFnType]",
86
+ _EventKeyTupleType,
87
+ ]
88
+ _collection_to_key: Dict[
89
+ weakref.ref[RefCollection[Any]],
90
+ _ListenerToEventKeyType,
91
+ ] = collections.defaultdict(dict)
92
+ """
93
+ Given a _ListenerCollection or _ClsLevelListener, can locate
94
+ all the original listen() arguments and the listener fn contained
95
+
96
+ ref(listenercollection) -> {
97
+ ref(listener_fn) -> (target, identifier, fn),
98
+ ref(listener_fn) -> (target, identifier, fn),
99
+ ref(listener_fn) -> (target, identifier, fn),
100
+ }
101
+ """
102
+
103
+
104
+ def _collection_gced(ref: weakref.ref[Any]) -> None:
105
+ # defaultdict, so can't get a KeyError
106
+ if not _collection_to_key or ref not in _collection_to_key:
107
+ return
108
+
109
+ ref = cast("weakref.ref[RefCollection[EventTarget]]", ref)
110
+
111
+ listener_to_key = _collection_to_key.pop(ref)
112
+ for key in listener_to_key.values():
113
+ if key in _key_to_collection:
114
+ # defaultdict, so can't get a KeyError
115
+ dispatch_reg = _key_to_collection[key]
116
+ dispatch_reg.pop(ref)
117
+ if not dispatch_reg:
118
+ _key_to_collection.pop(key)
119
+
120
+
121
+ def _stored_in_collection(
122
+ event_key: _EventKey[_ET], owner: RefCollection[_ET]
123
+ ) -> bool:
124
+ key = event_key._key
125
+
126
+ dispatch_reg = _key_to_collection[key]
127
+
128
+ owner_ref = owner.ref
129
+ listen_ref = weakref.ref(event_key._listen_fn)
130
+
131
+ if owner_ref in dispatch_reg:
132
+ return False
133
+
134
+ dispatch_reg[owner_ref] = listen_ref
135
+
136
+ listener_to_key = _collection_to_key[owner_ref]
137
+ listener_to_key[listen_ref] = key
138
+
139
+ return True
140
+
141
+
142
+ def _removed_from_collection(
143
+ event_key: _EventKey[_ET], owner: RefCollection[_ET]
144
+ ) -> None:
145
+ key = event_key._key
146
+
147
+ dispatch_reg = _key_to_collection[key]
148
+
149
+ listen_ref = weakref.ref(event_key._listen_fn)
150
+
151
+ owner_ref = owner.ref
152
+ dispatch_reg.pop(owner_ref, None)
153
+ if not dispatch_reg:
154
+ del _key_to_collection[key]
155
+
156
+ if owner_ref in _collection_to_key:
157
+ listener_to_key = _collection_to_key[owner_ref]
158
+ # see #12216 - this guards against a removal that already occurred
159
+ # here. however, I cannot come up with a test that shows any negative
160
+ # side effects occurring from this removal happening, even though an
161
+ # event key may still be referenced from a clsleveldispatch here
162
+ listener_to_key.pop(listen_ref, None)
163
+
164
+
165
+ def _stored_in_collection_multi(
166
+ newowner: RefCollection[_ET],
167
+ oldowner: RefCollection[_ET],
168
+ elements: Iterable[_ListenerFnType],
169
+ ) -> None:
170
+ if not elements:
171
+ return
172
+
173
+ oldowner_ref = oldowner.ref
174
+ newowner_ref = newowner.ref
175
+
176
+ old_listener_to_key = _collection_to_key[oldowner_ref]
177
+ new_listener_to_key = _collection_to_key[newowner_ref]
178
+
179
+ for listen_fn in elements:
180
+ listen_ref = weakref.ref(listen_fn)
181
+ try:
182
+ key = old_listener_to_key[listen_ref]
183
+ except KeyError:
184
+ # can occur during interpreter shutdown.
185
+ # see #6740
186
+ continue
187
+
188
+ try:
189
+ dispatch_reg = _key_to_collection[key]
190
+ except KeyError:
191
+ continue
192
+
193
+ if newowner_ref in dispatch_reg:
194
+ assert dispatch_reg[newowner_ref] == listen_ref
195
+ else:
196
+ dispatch_reg[newowner_ref] = listen_ref
197
+
198
+ new_listener_to_key[listen_ref] = key
199
+
200
+
201
+ def _clear(
202
+ owner: RefCollection[_ET],
203
+ elements: Iterable[_ListenerFnType],
204
+ ) -> None:
205
+ if not elements:
206
+ return
207
+
208
+ owner_ref = owner.ref
209
+ listener_to_key = _collection_to_key[owner_ref]
210
+ for listen_fn in elements:
211
+ listen_ref = weakref.ref(listen_fn)
212
+ key = listener_to_key[listen_ref]
213
+ dispatch_reg = _key_to_collection[key]
214
+ dispatch_reg.pop(owner_ref, None)
215
+
216
+ if not dispatch_reg:
217
+ del _key_to_collection[key]
218
+
219
+
220
+ class _EventKey(Generic[_ET]):
221
+ """Represent :func:`.listen` arguments."""
222
+
223
+ __slots__ = (
224
+ "target",
225
+ "identifier",
226
+ "fn",
227
+ "fn_key",
228
+ "fn_wrap",
229
+ "dispatch_target",
230
+ )
231
+
232
+ target: _ET
233
+ identifier: str
234
+ fn: _ListenerFnType
235
+ fn_key: _ListenerFnKeyType
236
+ dispatch_target: Any
237
+ _fn_wrap: Optional[_ListenerFnType]
238
+
239
+ def __init__(
240
+ self,
241
+ target: _ET,
242
+ identifier: str,
243
+ fn: _ListenerFnType,
244
+ dispatch_target: Any,
245
+ _fn_wrap: Optional[_ListenerFnType] = None,
246
+ ):
247
+ self.target = target
248
+ self.identifier = identifier
249
+ self.fn = fn
250
+ if isinstance(fn, types.MethodType):
251
+ self.fn_key = id(fn.__func__), id(fn.__self__)
252
+ else:
253
+ self.fn_key = id(fn)
254
+ self.fn_wrap = _fn_wrap
255
+ self.dispatch_target = dispatch_target
256
+
257
+ @property
258
+ def _key(self) -> _EventKeyTupleType:
259
+ return (id(self.target), self.identifier, self.fn_key)
260
+
261
+ def with_wrapper(self, fn_wrap: _ListenerFnType) -> _EventKey[_ET]:
262
+ if fn_wrap is self._listen_fn:
263
+ return self
264
+ else:
265
+ return _EventKey(
266
+ self.target,
267
+ self.identifier,
268
+ self.fn,
269
+ self.dispatch_target,
270
+ _fn_wrap=fn_wrap,
271
+ )
272
+
273
+ def with_dispatch_target(self, dispatch_target: Any) -> _EventKey[_ET]:
274
+ if dispatch_target is self.dispatch_target:
275
+ return self
276
+ else:
277
+ return _EventKey(
278
+ self.target,
279
+ self.identifier,
280
+ self.fn,
281
+ dispatch_target,
282
+ _fn_wrap=self.fn_wrap,
283
+ )
284
+
285
+ def listen(self, *args: Any, **kw: Any) -> None:
286
+ once = kw.pop("once", False)
287
+ once_unless_exception = kw.pop("_once_unless_exception", False)
288
+ named = kw.pop("named", False)
289
+
290
+ target, identifier, fn = (
291
+ self.dispatch_target,
292
+ self.identifier,
293
+ self._listen_fn,
294
+ )
295
+
296
+ dispatch_collection = getattr(target.dispatch, identifier)
297
+
298
+ adjusted_fn = dispatch_collection._adjust_fn_spec(fn, named)
299
+
300
+ self = self.with_wrapper(adjusted_fn)
301
+
302
+ stub_function = getattr(
303
+ self.dispatch_target.dispatch._events, self.identifier
304
+ )
305
+ if hasattr(stub_function, "_sa_warn"):
306
+ stub_function._sa_warn()
307
+
308
+ if once or once_unless_exception:
309
+ self.with_wrapper(
310
+ util.only_once(
311
+ self._listen_fn, retry_on_exception=once_unless_exception
312
+ )
313
+ ).listen(*args, **kw)
314
+ else:
315
+ self.dispatch_target.dispatch._listen(self, *args, **kw)
316
+
317
+ def remove(self) -> None:
318
+ key = self._key
319
+
320
+ if key not in _key_to_collection:
321
+ raise exc.InvalidRequestError(
322
+ "No listeners found for event %s / %r / %s "
323
+ % (self.target, self.identifier, self.fn)
324
+ )
325
+
326
+ dispatch_reg = _key_to_collection.pop(key)
327
+
328
+ for collection_ref, listener_ref in dispatch_reg.items():
329
+ collection = collection_ref()
330
+ listener_fn = listener_ref()
331
+ if collection is not None and listener_fn is not None:
332
+ collection.remove(self.with_wrapper(listener_fn))
333
+
334
+ def contains(self) -> bool:
335
+ """Return True if this event key is registered to listen."""
336
+ return self._key in _key_to_collection
337
+
338
+ def base_listen(
339
+ self,
340
+ propagate: bool = False,
341
+ insert: bool = False,
342
+ named: bool = False,
343
+ retval: Optional[bool] = None,
344
+ asyncio: bool = False,
345
+ ) -> None:
346
+ target, identifier = self.dispatch_target, self.identifier
347
+
348
+ dispatch_collection = getattr(target.dispatch, identifier)
349
+
350
+ for_modify = dispatch_collection.for_modify(target.dispatch)
351
+ if asyncio:
352
+ for_modify._set_asyncio()
353
+
354
+ if insert:
355
+ for_modify.insert(self, propagate)
356
+ else:
357
+ for_modify.append(self, propagate)
358
+
359
+ @property
360
+ def _listen_fn(self) -> _ListenerFnType:
361
+ return self.fn_wrap or self.fn
362
+
363
+ def append_to_list(
364
+ self,
365
+ owner: RefCollection[_ET],
366
+ list_: Deque[_ListenerFnType],
367
+ ) -> bool:
368
+ if _stored_in_collection(self, owner):
369
+ list_.append(self._listen_fn)
370
+ return True
371
+ else:
372
+ return False
373
+
374
+ def remove_from_list(
375
+ self,
376
+ owner: RefCollection[_ET],
377
+ list_: Deque[_ListenerFnType],
378
+ ) -> None:
379
+ _removed_from_collection(self, owner)
380
+ list_.remove(self._listen_fn)
381
+
382
+ def prepend_to_list(
383
+ self,
384
+ owner: RefCollection[_ET],
385
+ list_: Deque[_ListenerFnType],
386
+ ) -> bool:
387
+ if _stored_in_collection(self, owner):
388
+ list_.appendleft(self._listen_fn)
389
+ return True
390
+ else:
391
+ 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