SQLAlchemy 2.0.45__cp314-cp314-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.cp314-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp314-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp314-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp314-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp314-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 +4089 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +168 -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 +3933 -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 +73 -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 +147 -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 +5328 -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 +395 -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 +175 -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 +446 -0
  76. sqlalchemy/dialects/sqlite/base.py +3045 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +223 -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 +3382 -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 +2387 -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 +1516 -0
  182. sqlalchemy/pool/events.py +372 -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 +481 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2219 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8034 -0
  198. sqlalchemy/sql/crud.py +1744 -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 +2139 -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 +478 -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 +366 -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 +327 -0
  236. sqlalchemy/testing/provision.py +597 -0
  237. sqlalchemy/testing/requirements.py +1940 -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 +717 -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 +316 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2306 -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.45.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.45.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.45.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.45.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.45.dist-info/top_level.txt +1 -0
@@ -0,0 +1,227 @@
1
+ # testing/fixtures/orm.py
2
+ # Copyright (C) 2005-2025 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
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import sqlalchemy as sa
13
+ from .base import TestBase
14
+ from .sql import TablesTest
15
+ from .. import assertions
16
+ from .. import config
17
+ from .. import schema
18
+ from ..entities import BasicEntity
19
+ from ..entities import ComparableEntity
20
+ from ..util import adict
21
+ from ... import orm
22
+ from ...orm import DeclarativeBase
23
+ from ...orm import events as orm_events
24
+ from ...orm import registry
25
+
26
+
27
+ class ORMTest(TestBase):
28
+ @config.fixture
29
+ def fixture_session(self):
30
+ return fixture_session()
31
+
32
+
33
+ class MappedTest(ORMTest, TablesTest, assertions.AssertsExecutionResults):
34
+ # 'once', 'each', None
35
+ run_setup_classes = "once"
36
+
37
+ # 'once', 'each', None
38
+ run_setup_mappers = "each"
39
+
40
+ classes: Any = None
41
+
42
+ @config.fixture(autouse=True, scope="class")
43
+ def _setup_tables_test_class(self):
44
+ cls = self.__class__
45
+ cls._init_class()
46
+
47
+ if cls.classes is None:
48
+ cls.classes = adict()
49
+
50
+ cls._setup_once_tables()
51
+ cls._setup_once_classes()
52
+ cls._setup_once_mappers()
53
+ cls._setup_once_inserts()
54
+
55
+ yield
56
+
57
+ cls._teardown_once_class()
58
+ cls._teardown_once_metadata_bind()
59
+
60
+ @config.fixture(autouse=True, scope="function")
61
+ def _setup_tables_test_instance(self):
62
+ self._setup_each_tables()
63
+ self._setup_each_classes()
64
+ self._setup_each_mappers()
65
+ self._setup_each_inserts()
66
+
67
+ yield
68
+
69
+ orm.session.close_all_sessions()
70
+ self._teardown_each_mappers()
71
+ self._teardown_each_classes()
72
+ self._teardown_each_tables()
73
+
74
+ @classmethod
75
+ def _teardown_once_class(cls):
76
+ cls.classes.clear()
77
+
78
+ @classmethod
79
+ def _setup_once_classes(cls):
80
+ if cls.run_setup_classes == "once":
81
+ cls._with_register_classes(cls.setup_classes)
82
+
83
+ @classmethod
84
+ def _setup_once_mappers(cls):
85
+ if cls.run_setup_mappers == "once":
86
+ cls.mapper_registry, cls.mapper = cls._generate_registry()
87
+ cls._with_register_classes(cls.setup_mappers)
88
+
89
+ def _setup_each_mappers(self):
90
+ if self.run_setup_mappers != "once":
91
+ (
92
+ self.__class__.mapper_registry,
93
+ self.__class__.mapper,
94
+ ) = self._generate_registry()
95
+
96
+ if self.run_setup_mappers == "each":
97
+ self._with_register_classes(self.setup_mappers)
98
+
99
+ def _setup_each_classes(self):
100
+ if self.run_setup_classes == "each":
101
+ self._with_register_classes(self.setup_classes)
102
+
103
+ @classmethod
104
+ def _generate_registry(cls):
105
+ decl = registry(metadata=cls._tables_metadata)
106
+ return decl, decl.map_imperatively
107
+
108
+ @classmethod
109
+ def _with_register_classes(cls, fn):
110
+ """Run a setup method, framing the operation with a Base class
111
+ that will catch new subclasses to be established within
112
+ the "classes" registry.
113
+
114
+ """
115
+ cls_registry = cls.classes
116
+
117
+ class _Base:
118
+ def __init_subclass__(cls) -> None:
119
+ assert cls_registry is not None
120
+ cls_registry[cls.__name__] = cls
121
+ super().__init_subclass__()
122
+
123
+ class Basic(BasicEntity, _Base):
124
+ pass
125
+
126
+ class Comparable(ComparableEntity, _Base):
127
+ pass
128
+
129
+ cls.Basic = Basic
130
+ cls.Comparable = Comparable
131
+ fn()
132
+
133
+ def _teardown_each_mappers(self):
134
+ # some tests create mappers in the test bodies
135
+ # and will define setup_mappers as None -
136
+ # clear mappers in any case
137
+ if self.run_setup_mappers != "once":
138
+ orm.clear_mappers()
139
+
140
+ def _teardown_each_classes(self):
141
+ if self.run_setup_classes != "once":
142
+ self.classes.clear()
143
+
144
+ @classmethod
145
+ def setup_classes(cls):
146
+ pass
147
+
148
+ @classmethod
149
+ def setup_mappers(cls):
150
+ pass
151
+
152
+
153
+ class DeclarativeMappedTest(MappedTest):
154
+ run_setup_classes = "once"
155
+ run_setup_mappers = "once"
156
+
157
+ @classmethod
158
+ def _setup_once_tables(cls):
159
+ pass
160
+
161
+ @classmethod
162
+ def _with_register_classes(cls, fn):
163
+ cls_registry = cls.classes
164
+
165
+ class _DeclBase(DeclarativeBase):
166
+ __table_cls__ = schema.Table
167
+ metadata = cls._tables_metadata
168
+ type_annotation_map = {
169
+ str: sa.String().with_variant(
170
+ sa.String(50), "mysql", "mariadb", "oracle"
171
+ )
172
+ }
173
+
174
+ def __init_subclass__(cls, **kw) -> None:
175
+ assert cls_registry is not None
176
+ cls_registry[cls.__name__] = cls
177
+ super().__init_subclass__(**kw)
178
+
179
+ cls.DeclarativeBasic = _DeclBase
180
+
181
+ # sets up cls.Basic which is helpful for things like composite
182
+ # classes
183
+ super()._with_register_classes(fn)
184
+
185
+ if cls._tables_metadata.tables and cls.run_create_tables:
186
+ cls._tables_metadata.create_all(config.db)
187
+
188
+
189
+ class RemoveORMEventsGlobally:
190
+ @config.fixture(autouse=True)
191
+ def _remove_listeners(self):
192
+ yield
193
+ orm_events.MapperEvents._clear()
194
+ orm_events.InstanceEvents._clear()
195
+ orm_events.SessionEvents._clear()
196
+ orm_events.InstrumentationEvents._clear()
197
+ orm_events.QueryEvents._clear()
198
+
199
+
200
+ _fixture_sessions = set()
201
+
202
+
203
+ def fixture_session(**kw):
204
+ kw.setdefault("autoflush", True)
205
+ kw.setdefault("expire_on_commit", True)
206
+
207
+ bind = kw.pop("bind", config.db)
208
+
209
+ sess = orm.Session(bind, **kw)
210
+ _fixture_sessions.add(sess)
211
+ return sess
212
+
213
+
214
+ def close_all_sessions():
215
+ # will close all still-referenced sessions
216
+ orm.close_all_sessions()
217
+ _fixture_sessions.clear()
218
+
219
+
220
+ def stop_test_class_inside_fixtures(cls):
221
+ close_all_sessions()
222
+ orm.clear_mappers()
223
+
224
+
225
+ def after_test():
226
+ if _fixture_sessions:
227
+ close_all_sessions()
@@ -0,0 +1,482 @@
1
+ # testing/fixtures/sql.py
2
+ # Copyright (C) 2005-2025 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
+ from __future__ import annotations
9
+
10
+ import itertools
11
+ import random
12
+ import re
13
+
14
+ import sqlalchemy as sa
15
+ from .base import TestBase
16
+ from .. import config
17
+ from .. import mock
18
+ from .. import provision
19
+ from ..assertions import eq_
20
+ from ..assertions import ne_
21
+ from ..util import adict
22
+ from ..util import drop_all_tables_from_metadata
23
+ from ... import event
24
+ from ... import util
25
+ from ...schema import sort_tables_and_constraints
26
+ from ...sql import visitors
27
+ from ...sql.elements import ClauseElement
28
+
29
+
30
+ class TablesTest(TestBase):
31
+ # 'once', None
32
+ run_setup_bind = "once"
33
+
34
+ # 'once', 'each', None
35
+ run_define_tables = "once"
36
+
37
+ # 'once', 'each', None
38
+ run_create_tables = "once"
39
+
40
+ # 'once', 'each', None
41
+ run_inserts = "each"
42
+
43
+ # 'each', None
44
+ run_deletes = "each"
45
+
46
+ # 'once', None
47
+ run_dispose_bind = None
48
+
49
+ bind = None
50
+ _tables_metadata = None
51
+ tables = None
52
+ other = None
53
+ sequences = None
54
+
55
+ @config.fixture(autouse=True, scope="class")
56
+ def _setup_tables_test_class(self):
57
+ cls = self.__class__
58
+ cls._init_class()
59
+
60
+ cls._setup_once_tables()
61
+
62
+ cls._setup_once_inserts()
63
+
64
+ yield
65
+
66
+ cls._teardown_once_metadata_bind()
67
+
68
+ @config.fixture(autouse=True, scope="function")
69
+ def _setup_tables_test_instance(self):
70
+ self._setup_each_tables()
71
+ self._setup_each_inserts()
72
+
73
+ yield
74
+
75
+ self._teardown_each_tables()
76
+
77
+ @property
78
+ def tables_test_metadata(self):
79
+ return self._tables_metadata
80
+
81
+ @classmethod
82
+ def _init_class(cls):
83
+ if cls.run_define_tables == "each":
84
+ if cls.run_create_tables == "once":
85
+ cls.run_create_tables = "each"
86
+ assert cls.run_inserts in ("each", None)
87
+
88
+ cls.other = adict()
89
+ cls.tables = adict()
90
+ cls.sequences = adict()
91
+
92
+ cls.bind = cls.setup_bind()
93
+ cls._tables_metadata = sa.MetaData()
94
+
95
+ @classmethod
96
+ def _setup_once_inserts(cls):
97
+ if cls.run_inserts == "once":
98
+ cls._load_fixtures()
99
+ with cls.bind.begin() as conn:
100
+ cls.insert_data(conn)
101
+
102
+ @classmethod
103
+ def _setup_once_tables(cls):
104
+ if cls.run_define_tables == "once":
105
+ cls.define_tables(cls._tables_metadata)
106
+ if cls.run_create_tables == "once":
107
+ cls._tables_metadata.create_all(cls.bind)
108
+ cls.tables.update(cls._tables_metadata.tables)
109
+ cls.sequences.update(cls._tables_metadata._sequences)
110
+
111
+ def _setup_each_tables(self):
112
+ if self.run_define_tables == "each":
113
+ self.define_tables(self._tables_metadata)
114
+ if self.run_create_tables == "each":
115
+ self._tables_metadata.create_all(self.bind)
116
+ self.tables.update(self._tables_metadata.tables)
117
+ self.sequences.update(self._tables_metadata._sequences)
118
+ elif self.run_create_tables == "each":
119
+ self._tables_metadata.create_all(self.bind)
120
+
121
+ def _setup_each_inserts(self):
122
+ if self.run_inserts == "each":
123
+ self._load_fixtures()
124
+ with self.bind.begin() as conn:
125
+ self.insert_data(conn)
126
+
127
+ def _teardown_each_tables(self):
128
+ if self.run_define_tables == "each":
129
+ self.tables.clear()
130
+ if self.run_create_tables == "each":
131
+ drop_all_tables_from_metadata(self._tables_metadata, self.bind)
132
+ self._tables_metadata.clear()
133
+ elif self.run_create_tables == "each":
134
+ drop_all_tables_from_metadata(self._tables_metadata, self.bind)
135
+
136
+ # no need to run deletes if tables are recreated on setup
137
+ if (
138
+ self.run_define_tables != "each"
139
+ and self.run_create_tables == "once"
140
+ and self.run_deletes == "each"
141
+ ):
142
+ with self.bind.begin() as conn:
143
+ provision.delete_from_all_tables(
144
+ conn, config, self._tables_metadata
145
+ )
146
+
147
+ @classmethod
148
+ def _teardown_once_metadata_bind(cls):
149
+ if cls.run_create_tables:
150
+ drop_all_tables_from_metadata(cls._tables_metadata, cls.bind)
151
+
152
+ if cls.run_dispose_bind == "once":
153
+ cls.dispose_bind(cls.bind)
154
+
155
+ cls._tables_metadata.bind = None
156
+
157
+ if cls.run_setup_bind is not None:
158
+ cls.bind = None
159
+
160
+ @classmethod
161
+ def setup_bind(cls):
162
+ return config.db
163
+
164
+ @classmethod
165
+ def dispose_bind(cls, bind):
166
+ if hasattr(bind, "dispose"):
167
+ bind.dispose()
168
+ elif hasattr(bind, "close"):
169
+ bind.close()
170
+
171
+ @classmethod
172
+ def define_tables(cls, metadata):
173
+ pass
174
+
175
+ @classmethod
176
+ def fixtures(cls):
177
+ return {}
178
+
179
+ @classmethod
180
+ def insert_data(cls, connection):
181
+ pass
182
+
183
+ def sql_count_(self, count, fn):
184
+ self.assert_sql_count(self.bind, fn, count)
185
+
186
+ def sql_eq_(self, callable_, statements):
187
+ self.assert_sql(self.bind, callable_, statements)
188
+
189
+ @classmethod
190
+ def _load_fixtures(cls):
191
+ """Insert rows as represented by the fixtures() method."""
192
+ headers, rows = {}, {}
193
+ for table, data in cls.fixtures().items():
194
+ if len(data) < 2:
195
+ continue
196
+ if isinstance(table, str):
197
+ table = cls.tables[table]
198
+ headers[table] = data[0]
199
+ rows[table] = data[1:]
200
+ for table, fks in sort_tables_and_constraints(
201
+ cls._tables_metadata.tables.values()
202
+ ):
203
+ if table is None:
204
+ continue
205
+ if table not in headers:
206
+ continue
207
+ with cls.bind.begin() as conn:
208
+ conn.execute(
209
+ table.insert(),
210
+ [
211
+ dict(zip(headers[table], column_values))
212
+ for column_values in rows[table]
213
+ ],
214
+ )
215
+
216
+
217
+ class NoCache:
218
+ @config.fixture(autouse=True, scope="function")
219
+ def _disable_cache(self):
220
+ _cache = config.db._compiled_cache
221
+ config.db._compiled_cache = None
222
+ yield
223
+ config.db._compiled_cache = _cache
224
+
225
+
226
+ class RemovesEvents:
227
+ @util.memoized_property
228
+ def _event_fns(self):
229
+ return set()
230
+
231
+ def event_listen(self, target, name, fn, **kw):
232
+ self._event_fns.add((target, name, fn))
233
+ event.listen(target, name, fn, **kw)
234
+
235
+ @config.fixture(autouse=True, scope="function")
236
+ def _remove_events(self):
237
+ yield
238
+ for key in self._event_fns:
239
+ event.remove(*key)
240
+
241
+
242
+ class ComputedReflectionFixtureTest(TablesTest):
243
+ run_inserts = run_deletes = None
244
+
245
+ __backend__ = True
246
+ __requires__ = ("computed_columns", "table_reflection")
247
+
248
+ regexp = re.compile(r"[\[\]\(\)\s`'\"]*")
249
+
250
+ def normalize(self, text):
251
+ return self.regexp.sub("", text).lower()
252
+
253
+ @classmethod
254
+ def define_tables(cls, metadata):
255
+ from ... import Integer
256
+ from ... import testing
257
+ from ...schema import Column
258
+ from ...schema import Computed
259
+ from ...schema import Table
260
+
261
+ Table(
262
+ "computed_default_table",
263
+ metadata,
264
+ Column("id", Integer, primary_key=True),
265
+ Column("normal", Integer),
266
+ Column("computed_col", Integer, Computed("normal + 42")),
267
+ Column("with_default", Integer, server_default="42"),
268
+ )
269
+
270
+ t = Table(
271
+ "computed_column_table",
272
+ metadata,
273
+ Column("id", Integer, primary_key=True),
274
+ Column("normal", Integer),
275
+ Column("computed_no_flag", Integer, Computed("normal + 42")),
276
+ )
277
+
278
+ if testing.requires.schemas.enabled:
279
+ t2 = Table(
280
+ "computed_column_table",
281
+ metadata,
282
+ Column("id", Integer, primary_key=True),
283
+ Column("normal", Integer),
284
+ Column("computed_no_flag", Integer, Computed("normal / 42")),
285
+ schema=config.test_schema,
286
+ )
287
+
288
+ if testing.requires.computed_columns_virtual.enabled:
289
+ t.append_column(
290
+ Column(
291
+ "computed_virtual",
292
+ Integer,
293
+ Computed("normal + 2", persisted=False),
294
+ )
295
+ )
296
+ if testing.requires.schemas.enabled:
297
+ t2.append_column(
298
+ Column(
299
+ "computed_virtual",
300
+ Integer,
301
+ Computed("normal / 2", persisted=False),
302
+ )
303
+ )
304
+ if testing.requires.computed_columns_stored.enabled:
305
+ t.append_column(
306
+ Column(
307
+ "computed_stored",
308
+ Integer,
309
+ Computed("normal - 42", persisted=True),
310
+ )
311
+ )
312
+ if testing.requires.schemas.enabled:
313
+ t2.append_column(
314
+ Column(
315
+ "computed_stored",
316
+ Integer,
317
+ Computed("normal * 42", persisted=True),
318
+ )
319
+ )
320
+
321
+
322
+ class CacheKeyFixture:
323
+ def _compare_equal(self, a, b, compare_values):
324
+ a_key = a._generate_cache_key()
325
+ b_key = b._generate_cache_key()
326
+
327
+ if a_key is None:
328
+ assert a._annotations.get("nocache")
329
+
330
+ assert b_key is None
331
+ else:
332
+ eq_(a_key.key, b_key.key)
333
+ eq_(hash(a_key.key), hash(b_key.key))
334
+
335
+ for a_param, b_param in zip(a_key.bindparams, b_key.bindparams):
336
+ assert a_param.compare(b_param, compare_values=compare_values)
337
+ return a_key, b_key
338
+
339
+ def _run_cache_key_fixture(self, fixture, compare_values):
340
+ case_a = fixture()
341
+ case_b = fixture()
342
+
343
+ for a, b in itertools.combinations_with_replacement(
344
+ range(len(case_a)), 2
345
+ ):
346
+ if a == b:
347
+ a_key, b_key = self._compare_equal(
348
+ case_a[a], case_b[b], compare_values
349
+ )
350
+ if a_key is None:
351
+ continue
352
+ else:
353
+ a_key = case_a[a]._generate_cache_key()
354
+ b_key = case_b[b]._generate_cache_key()
355
+
356
+ if a_key is None or b_key is None:
357
+ if a_key is None:
358
+ assert case_a[a]._annotations.get("nocache")
359
+ if b_key is None:
360
+ assert case_b[b]._annotations.get("nocache")
361
+ continue
362
+
363
+ if a_key.key == b_key.key:
364
+ for a_param, b_param in zip(
365
+ a_key.bindparams, b_key.bindparams
366
+ ):
367
+ if not a_param.compare(
368
+ b_param, compare_values=compare_values
369
+ ):
370
+ break
371
+ else:
372
+ # this fails unconditionally since we could not
373
+ # find bound parameter values that differed.
374
+ # Usually we intended to get two distinct keys here
375
+ # so the failure will be more descriptive using the
376
+ # ne_() assertion.
377
+ ne_(a_key.key, b_key.key)
378
+ else:
379
+ ne_(a_key.key, b_key.key)
380
+
381
+ # ClauseElement-specific test to ensure the cache key
382
+ # collected all the bound parameters that aren't marked
383
+ # as "literal execute"
384
+ if isinstance(case_a[a], ClauseElement) and isinstance(
385
+ case_b[b], ClauseElement
386
+ ):
387
+ assert_a_params = []
388
+ assert_b_params = []
389
+
390
+ for elem in visitors.iterate(case_a[a]):
391
+ if elem.__visit_name__ == "bindparam":
392
+ assert_a_params.append(elem)
393
+
394
+ for elem in visitors.iterate(case_b[b]):
395
+ if elem.__visit_name__ == "bindparam":
396
+ assert_b_params.append(elem)
397
+
398
+ # note we're asserting the order of the params as well as
399
+ # if there are dupes or not. ordering has to be
400
+ # deterministic and matches what a traversal would provide.
401
+ eq_(
402
+ sorted(a_key.bindparams, key=lambda b: b.key),
403
+ sorted(
404
+ util.unique_list(assert_a_params), key=lambda b: b.key
405
+ ),
406
+ )
407
+ eq_(
408
+ sorted(b_key.bindparams, key=lambda b: b.key),
409
+ sorted(
410
+ util.unique_list(assert_b_params), key=lambda b: b.key
411
+ ),
412
+ )
413
+
414
+ def _run_cache_key_equal_fixture(self, fixture, compare_values):
415
+ case_a = fixture()
416
+ case_b = fixture()
417
+
418
+ for a, b in itertools.combinations_with_replacement(
419
+ range(len(case_a)), 2
420
+ ):
421
+ self._compare_equal(case_a[a], case_b[b], compare_values)
422
+
423
+
424
+ def insertmanyvalues_fixture(
425
+ connection, randomize_rows=False, warn_on_downgraded=False
426
+ ):
427
+ dialect = connection.dialect
428
+ orig_dialect = dialect._deliver_insertmanyvalues_batches
429
+ orig_conn = connection._exec_insertmany_context
430
+
431
+ class RandomCursor:
432
+ __slots__ = ("cursor",)
433
+
434
+ def __init__(self, cursor):
435
+ self.cursor = cursor
436
+
437
+ # only this method is called by the deliver method.
438
+ # by not having the other methods we assert that those aren't being
439
+ # used
440
+
441
+ @property
442
+ def description(self):
443
+ return self.cursor.description
444
+
445
+ def fetchall(self):
446
+ rows = self.cursor.fetchall()
447
+ rows = list(rows)
448
+ random.shuffle(rows)
449
+ return rows
450
+
451
+ def _deliver_insertmanyvalues_batches(
452
+ connection,
453
+ cursor,
454
+ statement,
455
+ parameters,
456
+ generic_setinputsizes,
457
+ context,
458
+ ):
459
+ if randomize_rows:
460
+ cursor = RandomCursor(cursor)
461
+ for batch in orig_dialect(
462
+ connection,
463
+ cursor,
464
+ statement,
465
+ parameters,
466
+ generic_setinputsizes,
467
+ context,
468
+ ):
469
+ if warn_on_downgraded and batch.is_downgraded:
470
+ util.warn("Batches were downgraded for sorted INSERT")
471
+
472
+ yield batch
473
+
474
+ def _exec_insertmany_context(dialect, context):
475
+ with mock.patch.object(
476
+ dialect,
477
+ "_deliver_insertmanyvalues_batches",
478
+ new=_deliver_insertmanyvalues_batches,
479
+ ):
480
+ return orig_conn(dialect, context)
481
+
482
+ connection._exec_insertmany_context = _exec_insertmany_context