SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,332 @@
1
+ # testing/fixtures/mypy.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
+ from __future__ import annotations
10
+
11
+ import inspect
12
+ import os
13
+ from pathlib import Path
14
+ import re
15
+ import shutil
16
+ import sys
17
+ import tempfile
18
+
19
+ from .base import TestBase
20
+ from .. import config
21
+ from ..assertions import eq_
22
+ from ... import util
23
+
24
+
25
+ @config.add_to_marker.mypy
26
+ class MypyTest(TestBase):
27
+ __requires__ = ("no_sqlalchemy2_stubs",)
28
+
29
+ @config.fixture(scope="function")
30
+ def per_func_cachedir(self):
31
+ yield from self._cachedir()
32
+
33
+ @config.fixture(scope="class")
34
+ def cachedir(self):
35
+ yield from self._cachedir()
36
+
37
+ def _cachedir(self):
38
+ # as of mypy 0.971 i think we need to keep mypy_path empty
39
+ mypy_path = ""
40
+
41
+ with tempfile.TemporaryDirectory() as cachedir:
42
+ with open(
43
+ Path(cachedir) / "sqla_mypy_config.cfg", "w"
44
+ ) as config_file:
45
+ config_file.write(
46
+ f"""
47
+ [mypy]\n
48
+ plugins = sqlalchemy.ext.mypy.plugin\n
49
+ show_error_codes = True\n
50
+ {mypy_path}
51
+ disable_error_code = no-untyped-call
52
+
53
+ [mypy-sqlalchemy.*]
54
+ ignore_errors = True
55
+
56
+ """
57
+ )
58
+ with open(
59
+ Path(cachedir) / "plain_mypy_config.cfg", "w"
60
+ ) as config_file:
61
+ config_file.write(
62
+ f"""
63
+ [mypy]\n
64
+ show_error_codes = True\n
65
+ {mypy_path}
66
+ disable_error_code = var-annotated,no-untyped-call
67
+ [mypy-sqlalchemy.*]
68
+ ignore_errors = True
69
+
70
+ """
71
+ )
72
+ yield cachedir
73
+
74
+ @config.fixture()
75
+ def mypy_runner(self, cachedir):
76
+ from mypy import api
77
+
78
+ def run(path, use_plugin=False, use_cachedir=None):
79
+ if use_cachedir is None:
80
+ use_cachedir = cachedir
81
+ args = [
82
+ "--strict",
83
+ "--raise-exceptions",
84
+ "--cache-dir",
85
+ use_cachedir,
86
+ "--config-file",
87
+ os.path.join(
88
+ use_cachedir,
89
+ (
90
+ "sqla_mypy_config.cfg"
91
+ if use_plugin
92
+ else "plain_mypy_config.cfg"
93
+ ),
94
+ ),
95
+ ]
96
+
97
+ # mypy as of 0.990 is more aggressively blocking messaging
98
+ # for paths that are in sys.path, and as pytest puts currdir,
99
+ # test/ etc in sys.path, just copy the source file to the
100
+ # tempdir we are working in so that we don't have to try to
101
+ # manipulate sys.path and/or guess what mypy is doing
102
+ filename = os.path.basename(path)
103
+ test_program = os.path.join(use_cachedir, filename)
104
+ if path != test_program:
105
+ shutil.copyfile(path, test_program)
106
+ args.append(test_program)
107
+
108
+ # I set this locally but for the suite here needs to be
109
+ # disabled
110
+ os.environ.pop("MYPY_FORCE_COLOR", None)
111
+
112
+ stdout, stderr, exitcode = api.run(args)
113
+ return stdout, stderr, exitcode
114
+
115
+ return run
116
+
117
+ @config.fixture
118
+ def mypy_typecheck_file(self, mypy_runner):
119
+ def run(path, use_plugin=False):
120
+ expected_messages = self._collect_messages(path)
121
+ stdout, stderr, exitcode = mypy_runner(path, use_plugin=use_plugin)
122
+ self._check_output(
123
+ path, expected_messages, stdout, stderr, exitcode
124
+ )
125
+
126
+ return run
127
+
128
+ @staticmethod
129
+ def file_combinations(dirname):
130
+ if os.path.isabs(dirname):
131
+ path = dirname
132
+ else:
133
+ caller_path = inspect.stack()[1].filename
134
+ path = os.path.join(os.path.dirname(caller_path), dirname)
135
+ files = list(Path(path).glob("**/*.py"))
136
+
137
+ for extra_dir in config.options.mypy_extra_test_paths:
138
+ if extra_dir and os.path.isdir(extra_dir):
139
+ files.extend((Path(extra_dir) / dirname).glob("**/*.py"))
140
+ return files
141
+
142
+ def _collect_messages(self, path):
143
+ from sqlalchemy.ext.mypy.util import mypy_14
144
+
145
+ expected_messages = []
146
+ expected_re = re.compile(
147
+ r"\s*# EXPECTED(_MYPY)?(_RE)?(_ROW)?(_TYPE)?: (.+)"
148
+ )
149
+ py_ver_re = re.compile(r"^#\s*PYTHON_VERSION\s?>=\s?(\d+\.\d+)")
150
+ with open(path) as file_:
151
+ current_assert_messages = []
152
+ for num, line in enumerate(file_, 1):
153
+ m = py_ver_re.match(line)
154
+ if m:
155
+ major, _, minor = m.group(1).partition(".")
156
+ if sys.version_info < (int(major), int(minor)):
157
+ config.skip_test(
158
+ "Requires python >= %s" % (m.group(1))
159
+ )
160
+ continue
161
+
162
+ m = expected_re.match(line)
163
+ if m:
164
+ is_mypy = bool(m.group(1))
165
+ is_re = bool(m.group(2))
166
+ is_row = bool(m.group(3))
167
+ is_type = bool(m.group(4))
168
+
169
+ expected_msg = re.sub(r"# noqa[:]? ?.*", "", m.group(5))
170
+ if is_row:
171
+ expected_msg = re.sub(
172
+ r"Row\[([^\]]+)\]",
173
+ lambda m: f"tuple[{m.group(1)}, fallback=s"
174
+ f"qlalchemy.engine.row.{m.group(0)}]",
175
+ expected_msg,
176
+ )
177
+ # For some reason it does not use or syntax (|)
178
+ expected_msg = re.sub(
179
+ r"Optional\[(.*)\]",
180
+ lambda m: f"Union[{m.group(1)}, None]",
181
+ expected_msg,
182
+ )
183
+
184
+ if is_type:
185
+ if not is_re:
186
+ # the goal here is that we can cut-and-paste
187
+ # from vscode -> pylance into the
188
+ # EXPECTED_TYPE: line, then the test suite will
189
+ # validate that line against what mypy produces
190
+ expected_msg = re.sub(
191
+ r"([\[\]])",
192
+ lambda m: rf"\{m.group(0)}",
193
+ expected_msg,
194
+ )
195
+
196
+ # note making sure preceding text matches
197
+ # with a dot, so that an expect for "Select"
198
+ # does not match "TypedSelect"
199
+ expected_msg = re.sub(
200
+ r"([\w_]+)",
201
+ lambda m: rf"(?:.*\.)?{m.group(1)}\*?",
202
+ expected_msg,
203
+ )
204
+
205
+ expected_msg = re.sub(
206
+ "List", "builtins.list", expected_msg
207
+ )
208
+
209
+ expected_msg = re.sub(
210
+ r"\b(int|str|float|bool)\b",
211
+ lambda m: rf"builtins.{m.group(0)}\*?",
212
+ expected_msg,
213
+ )
214
+ # expected_msg = re.sub(
215
+ # r"(Sequence|Tuple|List|Union)",
216
+ # lambda m: fr"typing.{m.group(0)}\*?",
217
+ # expected_msg,
218
+ # )
219
+
220
+ is_mypy = is_re = True
221
+ expected_msg = f'Revealed type is "{expected_msg}"'
222
+
223
+ if mypy_14 and util.py39:
224
+ # use_lowercase_names, py39 and above
225
+ # https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L363 # noqa: E501
226
+
227
+ # skip first character which could be capitalized
228
+ # "List item x not found" type of message
229
+ expected_msg = expected_msg[0] + re.sub(
230
+ (
231
+ r"\b(List|Tuple|Dict|Set)\b"
232
+ if is_type
233
+ else r"\b(List|Tuple|Dict|Set|Type)\b"
234
+ ),
235
+ lambda m: m.group(1).lower(),
236
+ expected_msg[1:],
237
+ )
238
+
239
+ if mypy_14 and util.py310:
240
+ # use_or_syntax, py310 and above
241
+ # https://github.com/python/mypy/blob/304997bfb85200fb521ac727ee0ce3e6085e5278/mypy/options.py#L368 # noqa: E501
242
+ expected_msg = re.sub(
243
+ r"Optional\[(.*?)\]",
244
+ lambda m: f"{m.group(1)} | None",
245
+ expected_msg,
246
+ )
247
+ current_assert_messages.append(
248
+ (is_mypy, is_re, expected_msg.strip())
249
+ )
250
+ elif current_assert_messages:
251
+ expected_messages.extend(
252
+ (num, is_mypy, is_re, expected_msg)
253
+ for (
254
+ is_mypy,
255
+ is_re,
256
+ expected_msg,
257
+ ) in current_assert_messages
258
+ )
259
+ current_assert_messages[:] = []
260
+
261
+ return expected_messages
262
+
263
+ def _check_output(
264
+ self, path, expected_messages, stdout: str, stderr, exitcode
265
+ ):
266
+ not_located = []
267
+ filename = os.path.basename(path)
268
+ if expected_messages:
269
+ # mypy 0.990 changed how return codes work, so don't assume a
270
+ # 1 or a 0 return code here, could be either depending on if
271
+ # errors were generated or not
272
+
273
+ output = []
274
+
275
+ raw_lines = stdout.split("\n")
276
+ while raw_lines:
277
+ e = raw_lines.pop(0)
278
+ if re.match(r".+\.py:\d+: error: .*", e):
279
+ output.append(("error", e))
280
+ elif re.match(
281
+ r".+\.py:\d+: note: +(?:Possible overload|def ).*", e
282
+ ):
283
+ while raw_lines:
284
+ ol = raw_lines.pop(0)
285
+ if not re.match(r".+\.py:\d+: note: +def .*", ol):
286
+ raw_lines.insert(0, ol)
287
+ break
288
+ elif re.match(
289
+ r".+\.py:\d+: note: .*(?:perhaps|suggestion)", e, re.I
290
+ ):
291
+ pass
292
+ elif re.match(r".+\.py:\d+: note: .*", e):
293
+ output.append(("note", e))
294
+
295
+ for num, is_mypy, is_re, msg in expected_messages:
296
+ msg = msg.replace("'", '"')
297
+ prefix = "[SQLAlchemy Mypy plugin] " if not is_mypy else ""
298
+ for idx, (typ, errmsg) in enumerate(output):
299
+ if is_re:
300
+ if re.match(
301
+ rf".*{filename}\:{num}\: {typ}\: {prefix}{msg}",
302
+ errmsg,
303
+ ):
304
+ break
305
+ elif (
306
+ f"{filename}:{num}: {typ}: {prefix}{msg}"
307
+ in errmsg.replace("'", '"')
308
+ ):
309
+ break
310
+ else:
311
+ not_located.append(msg)
312
+ continue
313
+ del output[idx]
314
+
315
+ if not_located:
316
+ missing = "\n".join(not_located)
317
+ print("Couldn't locate expected messages:", missing, sep="\n")
318
+ if output:
319
+ extra = "\n".join(msg for _, msg in output)
320
+ print("Remaining messages:", extra, sep="\n")
321
+ assert False, "expected messages not found, see stdout"
322
+
323
+ if output:
324
+ print(f"{len(output)} messages from mypy were not consumed:")
325
+ print("\n".join(msg for _, msg in output))
326
+ assert False, "errors and/or notes remain, see stdout"
327
+
328
+ else:
329
+ if exitcode != 0:
330
+ print(stdout, stderr, sep="\n")
331
+
332
+ eq_(exitcode, 0, msg=stdout)
@@ -0,0 +1,227 @@
1
+ # testing/fixtures/orm.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
+ 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()