SQLAlchemy 2.1.0__cp313-cp313-win_arm64.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 +251 -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 +921 -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 +5977 -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 +3156 -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.cp313-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp313-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313-win_arm64.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 +3647 -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 +1489 -0
  112. sqlalchemy/ext/asyncio/exc.py +30 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1787 -0
  115. sqlalchemy/ext/asyncio/session.py +2268 -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 +2711 -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 +2335 -0
  142. sqlalchemy/orm/decl_base.py +2300 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1205 -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 +3615 -0
  160. sqlalchemy/orm/scoping.py +2251 -0
  161. sqlalchemy/orm/session.py +5493 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3559 -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 +2550 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1544 -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.cp313-win_arm64.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.cp313-win_arm64.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 +2852 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8469 -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 +6062 -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 +7080 -0
  206. sqlalchemy/sql/selectable.py +7644 -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 +496 -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 +1990 -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 +777 -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 +176 -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.cp313-win_arm64.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.cp313-win_arm64.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 +2803 -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.0.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,496 @@
1
+ # testing/engines.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
+ # mypy: ignore-errors
8
+
9
+
10
+ from __future__ import annotations
11
+
12
+ import collections
13
+ import re
14
+ import typing
15
+ from typing import Any
16
+ from typing import Dict
17
+ from typing import Literal
18
+ from typing import Optional
19
+ from typing import Union
20
+ import warnings
21
+ import weakref
22
+
23
+ from . import config
24
+ from .util import decorator
25
+ from .util import gc_collect
26
+ from .. import event
27
+ from .. import pool
28
+ from ..util import await_
29
+
30
+ if typing.TYPE_CHECKING:
31
+ from ..engine import Engine
32
+ from ..engine.url import URL
33
+ from ..ext.asyncio import AsyncEngine
34
+
35
+
36
+ class ConnectionKiller:
37
+ def __init__(self):
38
+ self.proxy_refs = weakref.WeakKeyDictionary()
39
+ self.testing_engines = collections.defaultdict(set)
40
+ self.dbapi_connections = set()
41
+
42
+ def add_pool(self, pool):
43
+ event.listen(pool, "checkout", self._add_conn)
44
+ event.listen(pool, "checkin", self._remove_conn)
45
+ event.listen(pool, "close", self._remove_conn)
46
+ event.listen(pool, "close_detached", self._remove_conn)
47
+ # note we are keeping "invalidated" here, as those are still
48
+ # opened connections we would like to roll back
49
+
50
+ def _add_conn(self, dbapi_con, con_record, con_proxy):
51
+ self.dbapi_connections.add(dbapi_con)
52
+ self.proxy_refs[con_proxy] = True
53
+
54
+ def _remove_conn(self, dbapi_conn, *arg):
55
+ self.dbapi_connections.discard(dbapi_conn)
56
+
57
+ def add_engine(self, engine, scope):
58
+ self.add_pool(engine.pool)
59
+
60
+ assert scope in ("class", "global", "function", "fixture")
61
+ self.testing_engines[scope].add(engine)
62
+
63
+ def _safe(self, fn):
64
+ try:
65
+ fn()
66
+ except Exception as e:
67
+ warnings.warn(
68
+ "testing_reaper couldn't rollback/close connection: %s" % e
69
+ )
70
+
71
+ def _checked_out_proxies(self):
72
+ """Return the connection fairies we've seen checked out that still
73
+ hold onto a DBAPI connection.
74
+
75
+ ``proxy_refs`` keeps a weak reference to every fairy handed out by a
76
+ pool we're watching; a fairy that's been checked in has had its
77
+ ``dbapi_connection`` dropped, so those that remain "valid" are the
78
+ ones still checked out (or detached and left open).
79
+
80
+ """
81
+ return [
82
+ rec
83
+ for rec in list(self.proxy_refs)
84
+ if rec is not None and rec.is_valid
85
+ ]
86
+
87
+ def rollback_all(self):
88
+ for rec in self._checked_out_proxies():
89
+ self._safe(rec.rollback)
90
+
91
+ def checkin_all(self):
92
+ # run pool.checkin() for all ConnectionFairy instances we have
93
+ # tracked.
94
+
95
+ for rec in self._checked_out_proxies():
96
+ self.dbapi_connections.discard(rec.dbapi_connection)
97
+ self._safe(rec._checkin)
98
+
99
+ # for fairy refs that were GCed and could not close the connection,
100
+ # such as asyncio, roll back those remaining connections
101
+ for con in self.dbapi_connections:
102
+ self._safe(con.rollback)
103
+ self.dbapi_connections.clear()
104
+
105
+ def close_all(self):
106
+ self.checkin_all()
107
+
108
+ def prepare_for_drop_tables(self, connection):
109
+ # don't do aggressive checks for third party test suites
110
+ if not config.bootstrapped_as_sqlalchemy:
111
+ return
112
+
113
+ from . import provision
114
+
115
+ provision.prepare_for_drop_tables(connection.engine.url, connection)
116
+
117
+ def _drop_testing_engines(self, scope):
118
+ eng = self.testing_engines[scope]
119
+ for rec in list(eng):
120
+ for proxy_ref in list(self.proxy_refs):
121
+ if proxy_ref is not None and proxy_ref.is_valid:
122
+ if (
123
+ proxy_ref._pool is not None
124
+ and proxy_ref._pool is rec.pool
125
+ ):
126
+ self._safe(proxy_ref._checkin)
127
+
128
+ if hasattr(rec, "sync_engine"):
129
+ await_(rec.dispose())
130
+ else:
131
+ rec.dispose()
132
+
133
+ eng.clear()
134
+
135
+ def _dispose_testing_engines(self, scope):
136
+ eng = self.testing_engines[scope]
137
+ for rec in list(eng):
138
+ if hasattr(rec, "sync_engine"):
139
+ await_(rec.dispose())
140
+ else:
141
+ rec.dispose()
142
+
143
+ def after_test(self):
144
+ self._drop_testing_engines("function")
145
+
146
+ def after_test_outside_fixtures(self, test):
147
+ # don't do aggressive checks for third party test suites
148
+ if not config.bootstrapped_as_sqlalchemy:
149
+ return
150
+
151
+ if test.__class__.__leave_connections_for_teardown__:
152
+ return
153
+
154
+ self.checkin_all()
155
+
156
+ # on PostgreSQL, this will test for any "idle in transaction"
157
+ # connections. useful to identify tests with unusual patterns
158
+ # that can't be cleaned up correctly.
159
+ from . import provision
160
+
161
+ with config.db.connect() as conn:
162
+ provision.prepare_for_drop_tables(conn.engine.url, conn)
163
+
164
+ def stop_test_class_inside_fixtures(self):
165
+ self.checkin_all()
166
+ self._drop_testing_engines("function")
167
+ self._drop_testing_engines("class")
168
+
169
+ def stop_test_class_outside_fixtures(self):
170
+ # ensure no refs to checked out connections at all.
171
+
172
+ if self._checked_out_proxies():
173
+ gc_collect()
174
+
175
+ ln = len(self._checked_out_proxies())
176
+ if ln:
177
+ self.checkin_all()
178
+
179
+ if ln > 2:
180
+ # allow two connections to linger, as on loaded down
181
+ # CI hardware there seem to be occasional GC lapses that
182
+ # are not easily preventable
183
+ assert (
184
+ False
185
+ ), "%d connection recs not cleared after test suite" % (ln)
186
+ if config.options and config.options.low_connections:
187
+ # for suites running with --low-connections, dispose the "global"
188
+ # engines to disconnect everything before making a testing engine
189
+ self._dispose_testing_engines("global")
190
+
191
+ def final_cleanup(self):
192
+ self.checkin_all()
193
+ for scope in self.testing_engines:
194
+ self._drop_testing_engines(scope)
195
+
196
+ def assert_all_closed(self):
197
+ for rec in self.proxy_refs:
198
+ if rec.is_valid:
199
+ assert False
200
+
201
+
202
+ testing_reaper = ConnectionKiller()
203
+
204
+
205
+ @decorator
206
+ def assert_conns_closed(fn, *args, **kw):
207
+ try:
208
+ fn(*args, **kw)
209
+ finally:
210
+ testing_reaper.assert_all_closed()
211
+
212
+
213
+ @decorator
214
+ def rollback_open_connections(fn, *args, **kw):
215
+ """Decorator that rolls back all open connections after fn execution."""
216
+
217
+ try:
218
+ fn(*args, **kw)
219
+ finally:
220
+ testing_reaper.rollback_all()
221
+
222
+
223
+ @decorator
224
+ def close_first(fn, *args, **kw):
225
+ """Decorator that closes all connections before fn execution."""
226
+
227
+ testing_reaper.checkin_all()
228
+ fn(*args, **kw)
229
+
230
+
231
+ @decorator
232
+ def close_open_connections(fn, *args, **kw):
233
+ """Decorator that closes all connections after fn execution."""
234
+ try:
235
+ fn(*args, **kw)
236
+ finally:
237
+ testing_reaper.checkin_all()
238
+
239
+
240
+ def all_dialects(exclude=None):
241
+ import sqlalchemy.dialects as d
242
+
243
+ for name in d.__all__:
244
+ # TEMPORARY
245
+ if exclude and name in exclude:
246
+ continue
247
+ mod = getattr(d, name, None)
248
+ if not mod:
249
+ mod = getattr(
250
+ __import__("sqlalchemy.dialects.%s" % name).dialects, name
251
+ )
252
+ yield mod.dialect()
253
+
254
+
255
+ class ReconnectFixture:
256
+ def __init__(self, dbapi):
257
+ self.dbapi = dbapi
258
+ self.connections = []
259
+ self.is_stopped = False
260
+
261
+ def __getattr__(self, key):
262
+ return getattr(self.dbapi, key)
263
+
264
+ def connect(self, *args, **kwargs):
265
+ conn = self.dbapi.connect(*args, **kwargs)
266
+ if self.is_stopped:
267
+ self._safe(conn.close)
268
+ curs = conn.cursor() # should fail on Oracle etc.
269
+ # should fail for everything that didn't fail
270
+ # above, connection is closed
271
+ curs.execute("select 1")
272
+ assert False, "simulated connect failure didn't work"
273
+ else:
274
+ self.connections.append(conn)
275
+ return conn
276
+
277
+ def _safe(self, fn):
278
+ try:
279
+ fn()
280
+ except Exception as e:
281
+ warnings.warn("ReconnectFixture couldn't close connection: %s" % e)
282
+
283
+ def shutdown(self, stop=False):
284
+ # TODO: this doesn't cover all cases
285
+ # as nicely as we'd like, namely MySQLdb.
286
+ # would need to implement R. Brewer's
287
+ # proxy server idea to get better
288
+ # coverage.
289
+ self.is_stopped = stop
290
+ for c in list(self.connections):
291
+ self._safe(c.close)
292
+ self.connections = []
293
+
294
+ def restart(self):
295
+ self.is_stopped = False
296
+
297
+
298
+ def reconnecting_engine(url=None, options=None):
299
+ url = url or config.db.url
300
+ dbapi = config.db.dialect.dbapi
301
+ if not options:
302
+ options = {}
303
+ options["module"] = ReconnectFixture(dbapi)
304
+ engine = testing_engine(url, options)
305
+ _dispose = engine.dispose
306
+
307
+ def dispose():
308
+ engine.dialect.dbapi.shutdown()
309
+ engine.dialect.dbapi.is_stopped = False
310
+ _dispose()
311
+
312
+ engine.test_shutdown = engine.dialect.dbapi.shutdown
313
+ engine.test_restart = engine.dialect.dbapi.restart
314
+ engine.dispose = dispose
315
+ return engine
316
+
317
+
318
+ @typing.overload
319
+ def testing_engine(
320
+ url: Optional[URL] = ...,
321
+ options: Optional[Dict[str, Any]] = ...,
322
+ *,
323
+ asyncio: Literal[False],
324
+ ) -> Engine: ...
325
+
326
+
327
+ @typing.overload
328
+ def testing_engine(
329
+ url: Optional[URL] = ...,
330
+ options: Optional[Dict[str, Any]] = ...,
331
+ *,
332
+ asyncio: Literal[True],
333
+ ) -> AsyncEngine: ...
334
+
335
+
336
+ def testing_engine(
337
+ url: Optional[URL] = None,
338
+ options: Optional[Dict[str, Any]] = None,
339
+ *,
340
+ asyncio: bool = False,
341
+ ) -> Union[Engine, AsyncEngine]:
342
+
343
+ if asyncio:
344
+ from sqlalchemy.ext.asyncio import (
345
+ create_async_engine as create_engine,
346
+ )
347
+ else:
348
+ from sqlalchemy import create_engine
349
+ from sqlalchemy.engine.url import make_url
350
+
351
+ url = make_url(url if url else config.db.url)
352
+
353
+ if not options:
354
+ options = {}
355
+
356
+ use_options = {}
357
+
358
+ for opt_dict in (config.db_opts, options):
359
+ if not opt_dict:
360
+ continue
361
+ use_options.update(
362
+ {
363
+ opt: value
364
+ for opt, value in opt_dict.items()
365
+ if opt not in ("scope", "use_reaper")
366
+ and not opt.startswith("sqlite_")
367
+ }
368
+ )
369
+
370
+ engine = create_engine(url, **use_options)
371
+
372
+ if config.options and config.options.low_connections:
373
+ # for suites running with --low-connections, dispose the "global"
374
+ # engines to disconnect everything before making a testing engine
375
+ testing_reaper._dispose_testing_engines("global")
376
+
377
+ scope = options.get("scope", "function")
378
+ if scope == "global":
379
+ if asyncio:
380
+ engine.sync_engine._has_events = True
381
+ else:
382
+ engine._has_events = (
383
+ True # enable event blocks, helps with profiling
384
+ )
385
+
386
+ from . import provision
387
+
388
+ provision.post_configure_testing_engine(engine.url, engine, options, scope)
389
+
390
+ # post_configure_testing_engine may have modified the options dictionary
391
+ # in place; consume additional post arguments afterwards
392
+
393
+ use_reaper = options.get("use_reaper", True)
394
+ if use_reaper:
395
+ testing_reaper.add_engine(engine, scope)
396
+
397
+ if (
398
+ isinstance(engine.pool, pool.QueuePool)
399
+ and "pool" not in options
400
+ and "pool_timeout" not in options
401
+ and "max_overflow" not in options
402
+ ):
403
+ engine.pool._timeout = 0
404
+ engine.pool._max_overflow = 0
405
+
406
+ return engine
407
+
408
+
409
+ def mock_engine(dialect_name=None):
410
+ """Provides a mocking engine based on the current testing.db.
411
+
412
+ This is normally used to test DDL generation flow as emitted
413
+ by an Engine.
414
+
415
+ It should not be used in other cases, as assert_compile() and
416
+ assert_sql_execution() are much better choices with fewer
417
+ moving parts.
418
+
419
+ """
420
+
421
+ from sqlalchemy import create_mock_engine
422
+
423
+ if not dialect_name:
424
+ dialect_name = config.db.name
425
+
426
+ buffer = []
427
+
428
+ def executor(sql, *a, **kw):
429
+ buffer.append(sql)
430
+
431
+ def assert_sql(stmts):
432
+ recv = [re.sub(r"[\n\t]", "", str(s)) for s in buffer]
433
+ assert recv == stmts, recv
434
+
435
+ def print_sql():
436
+ d = engine.dialect
437
+ return "\n".join(str(s.compile(dialect=d)) for s in engine.mock)
438
+
439
+ engine = create_mock_engine(dialect_name + "://", executor)
440
+ assert not hasattr(engine, "mock")
441
+ engine.mock = buffer
442
+ engine.assert_sql = assert_sql
443
+ engine.print_sql = print_sql
444
+ return engine
445
+
446
+
447
+ class DBAPIProxyCursor:
448
+ """Proxy a DBAPI cursor.
449
+
450
+ Tests can provide subclasses of this to intercept
451
+ DBAPI-level cursor operations.
452
+
453
+ """
454
+
455
+ def __init__(self, engine, conn, *args, **kwargs):
456
+ self.engine = engine
457
+ self.connection = conn
458
+ self.cursor = conn.cursor(*args, **kwargs)
459
+
460
+ def execute(self, stmt, parameters=None, **kw):
461
+ if parameters:
462
+ return self.cursor.execute(stmt, parameters, **kw)
463
+ else:
464
+ return self.cursor.execute(stmt, **kw)
465
+
466
+ def executemany(self, stmt, params, **kw):
467
+ return self.cursor.executemany(stmt, params, **kw)
468
+
469
+ def __iter__(self):
470
+ return iter(self.cursor)
471
+
472
+ def __getattr__(self, key):
473
+ return getattr(self.cursor, key)
474
+
475
+
476
+ class DBAPIProxyConnection:
477
+ """Proxy a DBAPI connection.
478
+
479
+ Tests can provide subclasses of this to intercept
480
+ DBAPI-level connection operations.
481
+
482
+ """
483
+
484
+ def __init__(self, engine, conn, cursor_cls):
485
+ self.conn = conn
486
+ self.engine = engine
487
+ self.cursor_cls = cursor_cls
488
+
489
+ def cursor(self, *args, **kwargs):
490
+ return self.cursor_cls(self.engine, self.conn, *args, **kwargs)
491
+
492
+ def close(self):
493
+ self.conn.close()
494
+
495
+ def __getattr__(self, key):
496
+ return getattr(self.conn, key)
@@ -0,0 +1,117 @@
1
+ # testing/entities.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
+ # mypy: ignore-errors
8
+
9
+
10
+ from __future__ import annotations
11
+
12
+ import sqlalchemy as sa
13
+ from .. import exc as sa_exc
14
+ from ..orm.writeonly import WriteOnlyCollection
15
+
16
+ _repr_stack = set()
17
+
18
+
19
+ class BasicEntity:
20
+ def __init__(self, **kw):
21
+ for key, value in kw.items():
22
+ setattr(self, key, value)
23
+
24
+ def __repr__(self):
25
+ if id(self) in _repr_stack:
26
+ return object.__repr__(self)
27
+ _repr_stack.add(id(self))
28
+ try:
29
+ return "%s(%s)" % (
30
+ (self.__class__.__name__),
31
+ ", ".join(
32
+ [
33
+ "%s=%r" % (key, getattr(self, key))
34
+ for key in sorted(self.__dict__.keys())
35
+ if not key.startswith("_")
36
+ ]
37
+ ),
38
+ )
39
+ finally:
40
+ _repr_stack.remove(id(self))
41
+
42
+
43
+ _recursion_stack = set()
44
+
45
+
46
+ class ComparableMixin:
47
+ def __ne__(self, other):
48
+ return not self.__eq__(other)
49
+
50
+ def __eq__(self, other):
51
+ """'Deep, sparse compare.
52
+
53
+ Deeply compare two entities, following the non-None attributes of the
54
+ non-persisted object, if possible.
55
+
56
+ """
57
+ if other is self:
58
+ return True
59
+ elif not self.__class__ == other.__class__:
60
+ return False
61
+
62
+ if id(self) in _recursion_stack:
63
+ return True
64
+ _recursion_stack.add(id(self))
65
+
66
+ try:
67
+ # pick the entity that's not SA persisted as the source
68
+ try:
69
+ self_key = sa.orm.attributes.instance_state(self).key
70
+ except sa.orm.exc.NO_STATE:
71
+ self_key = None
72
+
73
+ if other is None:
74
+ a = self
75
+ b = other
76
+ elif self_key is not None:
77
+ a = other
78
+ b = self
79
+ else:
80
+ a = self
81
+ b = other
82
+
83
+ for attr in list(a.__dict__):
84
+ if attr.startswith("_"):
85
+ continue
86
+
87
+ value = getattr(a, attr)
88
+
89
+ if isinstance(value, WriteOnlyCollection):
90
+ continue
91
+
92
+ try:
93
+ # handle lazy loader errors
94
+ battr = getattr(b, attr)
95
+ except (AttributeError, sa_exc.UnboundExecutionError):
96
+ return False
97
+
98
+ if hasattr(value, "__iter__") and not isinstance(value, str):
99
+ if hasattr(value, "__getitem__") and not hasattr(
100
+ value, "keys"
101
+ ):
102
+ if list(value) != list(battr):
103
+ return False
104
+ else:
105
+ if set(value) != set(battr):
106
+ return False
107
+ else:
108
+ if value is not None and value != battr:
109
+ return False
110
+ return True
111
+ finally:
112
+ _recursion_stack.remove(id(self))
113
+
114
+
115
+ class ComparableEntity(ComparableMixin, BasicEntity):
116
+ def __hash__(self):
117
+ return hash(self.__class__)