SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,776 @@
1
+ # testing/suite/test_dialect.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
+ import importlib
11
+
12
+ from . import testing
13
+ from .. import assert_raises
14
+ from .. import config
15
+ from .. import engines
16
+ from .. import eq_
17
+ from .. import fixtures
18
+ from .. import is_not_none
19
+ from .. import is_true
20
+ from .. import mock
21
+ from .. import ne_
22
+ from .. import provide_metadata
23
+ from ..assertions import expect_raises
24
+ from ..assertions import expect_raises_message
25
+ from ..config import requirements
26
+ from ..provision import set_default_schema_on_connection
27
+ from ..schema import Column
28
+ from ..schema import Table
29
+ from ... import bindparam
30
+ from ... import dialects
31
+ from ... import event
32
+ from ... import exc
33
+ from ... import Integer
34
+ from ... import literal_column
35
+ from ... import select
36
+ from ... import String
37
+ from ...sql.compiler import Compiled
38
+ from ...util import inspect_getfullargspec
39
+
40
+
41
+ class PingTest(fixtures.TestBase):
42
+ __backend__ = True
43
+
44
+ def test_do_ping(self):
45
+ with testing.db.connect() as conn:
46
+ is_true(
47
+ testing.db.dialect.do_ping(conn.connection.dbapi_connection)
48
+ )
49
+
50
+
51
+ class ArgSignatureTest(fixtures.TestBase):
52
+ """test that all visit_XYZ() in :class:`_sql.Compiler` subclasses have
53
+ ``**kw``, for #8988.
54
+
55
+ This test uses runtime code inspection. Does not need to be a
56
+ ``__backend__`` test as it only needs to run once provided all target
57
+ dialects have been imported.
58
+
59
+ For third party dialects, the suite would be run with that third
60
+ party as a "--dburi", which means its compiler classes will have been
61
+ imported by the time this test runs.
62
+
63
+ """
64
+
65
+ def _all_subclasses(): # type: ignore # noqa
66
+ for d in dialects.__all__:
67
+ if not d.startswith("_"):
68
+ importlib.import_module("sqlalchemy.dialects.%s" % d)
69
+
70
+ stack = [Compiled]
71
+
72
+ while stack:
73
+ cls = stack.pop(0)
74
+ stack.extend(cls.__subclasses__())
75
+ yield cls
76
+
77
+ @testing.fixture(params=list(_all_subclasses()))
78
+ def all_subclasses(self, request):
79
+ yield request.param
80
+
81
+ def test_all_visit_methods_accept_kw(self, all_subclasses):
82
+ cls = all_subclasses
83
+
84
+ for k in cls.__dict__:
85
+ if k.startswith("visit_"):
86
+ meth = getattr(cls, k)
87
+
88
+ insp = inspect_getfullargspec(meth)
89
+ is_not_none(
90
+ insp.varkw,
91
+ f"Compiler visit method {cls.__name__}.{k}() does "
92
+ "not accommodate for **kw in its argument signature",
93
+ )
94
+
95
+
96
+ class ExceptionTest(fixtures.TablesTest):
97
+ """Test basic exception wrapping.
98
+
99
+ DBAPIs vary a lot in exception behavior so to actually anticipate
100
+ specific exceptions from real round trips, we need to be conservative.
101
+
102
+ """
103
+
104
+ run_deletes = "each"
105
+
106
+ __backend__ = True
107
+
108
+ @classmethod
109
+ def define_tables(cls, metadata):
110
+ Table(
111
+ "manual_pk",
112
+ metadata,
113
+ Column("id", Integer, primary_key=True, autoincrement=False),
114
+ Column("data", String(50)),
115
+ )
116
+
117
+ @requirements.duplicate_key_raises_integrity_error
118
+ def test_integrity_error(self):
119
+ with config.db.connect() as conn:
120
+ trans = conn.begin()
121
+ conn.execute(
122
+ self.tables.manual_pk.insert(), {"id": 1, "data": "d1"}
123
+ )
124
+
125
+ assert_raises(
126
+ exc.IntegrityError,
127
+ conn.execute,
128
+ self.tables.manual_pk.insert(),
129
+ {"id": 1, "data": "d1"},
130
+ )
131
+
132
+ trans.rollback()
133
+
134
+ def test_exception_with_non_ascii(self):
135
+ with config.db.connect() as conn:
136
+ try:
137
+ # try to create an error message that likely has non-ascii
138
+ # characters in the DBAPI's message string. unfortunately
139
+ # there's no way to make this happen with some drivers like
140
+ # mysqlclient, pymysql. this at least does produce a non-
141
+ # ascii error message for cx_oracle, psycopg2
142
+ conn.execute(select(literal_column("méil")))
143
+ assert False
144
+ except exc.DBAPIError as err:
145
+ err_str = str(err)
146
+
147
+ assert str(err.orig) in str(err)
148
+
149
+ assert isinstance(err_str, str)
150
+
151
+
152
+ class IsolationLevelTest(fixtures.TestBase):
153
+ __backend__ = True
154
+
155
+ __requires__ = ("isolation_level",)
156
+
157
+ def _get_non_default_isolation_level(self):
158
+ levels = requirements.get_isolation_levels(config)
159
+
160
+ default = levels["default"]
161
+ supported = levels["supported"]
162
+
163
+ s = set(supported).difference(["AUTOCOMMIT", default])
164
+ if s:
165
+ return s.pop()
166
+ else:
167
+ config.skip_test("no non-default isolation level available")
168
+
169
+ def test_default_isolation_level(self):
170
+ eq_(
171
+ config.db.dialect.default_isolation_level,
172
+ requirements.get_isolation_levels(config)["default"],
173
+ )
174
+
175
+ def test_non_default_isolation_level(self):
176
+ non_default = self._get_non_default_isolation_level()
177
+
178
+ with config.db.connect() as conn:
179
+ existing = conn.get_isolation_level()
180
+
181
+ ne_(existing, non_default)
182
+
183
+ conn.execution_options(isolation_level=non_default)
184
+
185
+ eq_(conn.get_isolation_level(), non_default)
186
+
187
+ conn.dialect.reset_isolation_level(
188
+ conn.connection.dbapi_connection
189
+ )
190
+
191
+ eq_(conn.get_isolation_level(), existing)
192
+
193
+ def test_all_levels(self):
194
+ levels = requirements.get_isolation_levels(config)
195
+
196
+ all_levels = levels["supported"]
197
+
198
+ for level in set(all_levels).difference(["AUTOCOMMIT"]):
199
+ with config.db.connect() as conn:
200
+ conn.execution_options(isolation_level=level)
201
+
202
+ eq_(conn.get_isolation_level(), level)
203
+
204
+ trans = conn.begin()
205
+ trans.rollback()
206
+
207
+ eq_(conn.get_isolation_level(), level)
208
+
209
+ with config.db.connect() as conn:
210
+ eq_(
211
+ conn.get_isolation_level(),
212
+ levels["default"],
213
+ )
214
+
215
+ @testing.requires.get_isolation_level_values
216
+ def test_invalid_level_execution_option(self, connection_no_trans):
217
+ """test for the new get_isolation_level_values() method"""
218
+
219
+ connection = connection_no_trans
220
+ with expect_raises_message(
221
+ exc.ArgumentError,
222
+ "Invalid value '%s' for isolation_level. "
223
+ "Valid isolation levels for '%s' are %s"
224
+ % (
225
+ "FOO",
226
+ connection.dialect.name,
227
+ ", ".join(
228
+ requirements.get_isolation_levels(config)["supported"]
229
+ ),
230
+ ),
231
+ ):
232
+ connection.execution_options(isolation_level="FOO")
233
+
234
+ @testing.requires.get_isolation_level_values
235
+ @testing.requires.dialect_level_isolation_level_param
236
+ def test_invalid_level_engine_param(self, testing_engine):
237
+ """test for the new get_isolation_level_values() method
238
+ and support for the dialect-level 'isolation_level' parameter.
239
+
240
+ """
241
+
242
+ eng = testing_engine(options=dict(isolation_level="FOO"))
243
+ with expect_raises_message(
244
+ exc.ArgumentError,
245
+ "Invalid value '%s' for isolation_level. "
246
+ "Valid isolation levels for '%s' are %s"
247
+ % (
248
+ "FOO",
249
+ eng.dialect.name,
250
+ ", ".join(
251
+ requirements.get_isolation_levels(config)["supported"]
252
+ ),
253
+ ),
254
+ ):
255
+ eng.connect()
256
+
257
+ @testing.requires.independent_readonly_connections
258
+ def test_dialect_user_setting_is_restored(self, testing_engine):
259
+ levels = requirements.get_isolation_levels(config)
260
+ default = levels["default"]
261
+ supported = (
262
+ sorted(
263
+ set(levels["supported"]).difference([default, "AUTOCOMMIT"])
264
+ )
265
+ )[0]
266
+
267
+ e = testing_engine(options={"isolation_level": supported})
268
+
269
+ with e.connect() as conn:
270
+ eq_(conn.get_isolation_level(), supported)
271
+
272
+ with e.connect() as conn:
273
+ conn.execution_options(isolation_level=default)
274
+ eq_(conn.get_isolation_level(), default)
275
+
276
+ with e.connect() as conn:
277
+ eq_(conn.get_isolation_level(), supported)
278
+
279
+
280
+ class AutocommitIsolationTest(fixtures.TablesTest):
281
+ run_deletes = "each"
282
+
283
+ __requires__ = ("autocommit",)
284
+
285
+ __backend__ = True
286
+
287
+ @classmethod
288
+ def define_tables(cls, metadata):
289
+ Table(
290
+ "some_table",
291
+ metadata,
292
+ Column("id", Integer, primary_key=True, autoincrement=False),
293
+ Column("data", String(50)),
294
+ test_needs_acid=True,
295
+ )
296
+
297
+ def _test_conn_autocommits(self, conn, autocommit, ensure_table=False):
298
+ if ensure_table:
299
+ self.tables.some_table.create(conn, checkfirst=True)
300
+ conn.commit()
301
+
302
+ trans = conn.begin()
303
+ conn.execute(
304
+ self.tables.some_table.insert(), {"id": 1, "data": "some data"}
305
+ )
306
+ trans.rollback()
307
+
308
+ eq_(
309
+ conn.scalar(select(self.tables.some_table.c.id)),
310
+ 1 if autocommit else None,
311
+ )
312
+ conn.rollback()
313
+
314
+ with conn.begin():
315
+ conn.execute(self.tables.some_table.delete())
316
+
317
+ def test_autocommit_on(self, connection_no_trans):
318
+ conn = connection_no_trans
319
+ c2 = conn.execution_options(isolation_level="AUTOCOMMIT")
320
+ self._test_conn_autocommits(c2, True)
321
+
322
+ c2.dialect.reset_isolation_level(c2.connection.dbapi_connection)
323
+
324
+ self._test_conn_autocommits(conn, False)
325
+
326
+ def test_autocommit_off(self, connection_no_trans):
327
+ conn = connection_no_trans
328
+ self._test_conn_autocommits(conn, False)
329
+
330
+ def test_turn_autocommit_off_via_default_iso_level(
331
+ self, connection_no_trans
332
+ ):
333
+ conn = connection_no_trans
334
+ conn = conn.execution_options(isolation_level="AUTOCOMMIT")
335
+ self._test_conn_autocommits(conn, True)
336
+
337
+ conn.execution_options(
338
+ isolation_level=requirements.get_isolation_levels(config)[
339
+ "default"
340
+ ]
341
+ )
342
+ self._test_conn_autocommits(conn, False)
343
+
344
+ @testing.requires.skip_autocommit_rollback
345
+ @testing.variation("autocommit_setting", ["false", "engine", "option"])
346
+ @testing.variation("block_rollback", [True, False])
347
+ def test_autocommit_block(
348
+ self, testing_engine, autocommit_setting, block_rollback
349
+ ):
350
+ kw = {}
351
+ if bool(block_rollback):
352
+ kw["skip_autocommit_rollback"] = True
353
+ if autocommit_setting.engine:
354
+ kw["isolation_level"] = "AUTOCOMMIT"
355
+
356
+ engine = testing_engine(options=kw)
357
+
358
+ conn = engine.connect()
359
+ if autocommit_setting.option:
360
+ conn.execution_options(isolation_level="AUTOCOMMIT")
361
+ self._test_conn_autocommits(
362
+ conn,
363
+ autocommit_setting.engine or autocommit_setting.option,
364
+ ensure_table=True,
365
+ )
366
+ with mock.patch.object(
367
+ conn.connection, "rollback", wraps=conn.connection.rollback
368
+ ) as check_rollback:
369
+ conn.close()
370
+ if autocommit_setting.false or not block_rollback:
371
+ eq_(check_rollback.mock_calls, [mock.call()])
372
+ else:
373
+ eq_(check_rollback.mock_calls, [])
374
+
375
+ @testing.requires.independent_readonly_connections
376
+ @testing.variation("use_dialect_setting", [True, False])
377
+ def test_dialect_autocommit_is_restored(
378
+ self, testing_engine, use_dialect_setting
379
+ ):
380
+ """test #10147"""
381
+
382
+ if use_dialect_setting:
383
+ e = testing_engine(options={"isolation_level": "AUTOCOMMIT"})
384
+ else:
385
+ e = testing_engine().execution_options(
386
+ isolation_level="AUTOCOMMIT"
387
+ )
388
+
389
+ levels = requirements.get_isolation_levels(config)
390
+
391
+ default = levels["default"]
392
+
393
+ with e.connect() as conn:
394
+ self._test_conn_autocommits(conn, True)
395
+
396
+ with e.connect() as conn:
397
+ conn.execution_options(isolation_level=default)
398
+ self._test_conn_autocommits(conn, False)
399
+
400
+ with e.connect() as conn:
401
+ self._test_conn_autocommits(conn, True)
402
+
403
+
404
+ class EscapingTest(fixtures.TestBase):
405
+ @provide_metadata
406
+ def test_percent_sign_round_trip(self):
407
+ """test that the DBAPI accommodates for escaped / nonescaped
408
+ percent signs in a way that matches the compiler
409
+
410
+ """
411
+ m = self.metadata
412
+ t = Table("t", m, Column("data", String(50)))
413
+ t.create(config.db)
414
+ with config.db.begin() as conn:
415
+ conn.execute(t.insert(), dict(data="some % value"))
416
+ conn.execute(t.insert(), dict(data="some %% other value"))
417
+
418
+ eq_(
419
+ conn.scalar(
420
+ select(t.c.data).where(
421
+ t.c.data == literal_column("'some % value'")
422
+ )
423
+ ),
424
+ "some % value",
425
+ )
426
+
427
+ eq_(
428
+ conn.scalar(
429
+ select(t.c.data).where(
430
+ t.c.data == literal_column("'some %% other value'")
431
+ )
432
+ ),
433
+ "some %% other value",
434
+ )
435
+
436
+
437
+ class WeCanSetDefaultSchemaWEventsTest(fixtures.TestBase):
438
+ __backend__ = True
439
+
440
+ __requires__ = ("default_schema_name_switch",)
441
+
442
+ def test_control_case(self):
443
+ default_schema_name = config.db.dialect.default_schema_name
444
+
445
+ eng = engines.testing_engine()
446
+ with eng.connect():
447
+ pass
448
+
449
+ eq_(eng.dialect.default_schema_name, default_schema_name)
450
+
451
+ def test_wont_work_wo_insert(self):
452
+ default_schema_name = config.db.dialect.default_schema_name
453
+
454
+ eng = engines.testing_engine()
455
+
456
+ @event.listens_for(eng, "connect")
457
+ def on_connect(dbapi_connection, connection_record):
458
+ set_default_schema_on_connection(
459
+ config, dbapi_connection, config.test_schema
460
+ )
461
+
462
+ with eng.connect() as conn:
463
+ what_it_should_be = eng.dialect._get_default_schema_name(conn)
464
+ eq_(what_it_should_be, config.test_schema)
465
+
466
+ eq_(eng.dialect.default_schema_name, default_schema_name)
467
+
468
+ def test_schema_change_on_connect(self):
469
+ eng = engines.testing_engine()
470
+
471
+ @event.listens_for(eng, "connect", insert=True)
472
+ def on_connect(dbapi_connection, connection_record):
473
+ set_default_schema_on_connection(
474
+ config, dbapi_connection, config.test_schema
475
+ )
476
+
477
+ with eng.connect() as conn:
478
+ what_it_should_be = eng.dialect._get_default_schema_name(conn)
479
+ eq_(what_it_should_be, config.test_schema)
480
+
481
+ eq_(eng.dialect.default_schema_name, config.test_schema)
482
+
483
+ def test_schema_change_works_w_transactions(self):
484
+ eng = engines.testing_engine()
485
+
486
+ @event.listens_for(eng, "connect", insert=True)
487
+ def on_connect(dbapi_connection, *arg):
488
+ set_default_schema_on_connection(
489
+ config, dbapi_connection, config.test_schema
490
+ )
491
+
492
+ with eng.connect() as conn:
493
+ trans = conn.begin()
494
+ what_it_should_be = eng.dialect._get_default_schema_name(conn)
495
+ eq_(what_it_should_be, config.test_schema)
496
+ trans.rollback()
497
+
498
+ what_it_should_be = eng.dialect._get_default_schema_name(conn)
499
+ eq_(what_it_should_be, config.test_schema)
500
+
501
+ eq_(eng.dialect.default_schema_name, config.test_schema)
502
+
503
+
504
+ class FutureWeCanSetDefaultSchemaWEventsTest(
505
+ fixtures.FutureEngineMixin, WeCanSetDefaultSchemaWEventsTest
506
+ ):
507
+ pass
508
+
509
+
510
+ class DifficultParametersTest(fixtures.TestBase):
511
+ __backend__ = True
512
+
513
+ tough_parameters = testing.combinations(
514
+ ("boring",),
515
+ ("per cent",),
516
+ ("per % cent",),
517
+ ("%percent",),
518
+ ("par(ens)",),
519
+ ("percent%(ens)yah",),
520
+ ("col:ons",),
521
+ ("_starts_with_underscore",),
522
+ ("dot.s",),
523
+ ("more :: %colons%",),
524
+ ("_name",),
525
+ ("___name",),
526
+ ("[BracketsAndCase]",),
527
+ ("42numbers",),
528
+ ("percent%signs",),
529
+ ("has spaces",),
530
+ ("/slashes/",),
531
+ ("more/slashes",),
532
+ ("q?marks",),
533
+ ("1param",),
534
+ ("1col:on",),
535
+ argnames="paramname",
536
+ )
537
+
538
+ @tough_parameters
539
+ @config.requirements.unusual_column_name_characters
540
+ def test_round_trip_same_named_column(
541
+ self, paramname, connection, metadata
542
+ ):
543
+ name = paramname
544
+
545
+ t = Table(
546
+ "t",
547
+ metadata,
548
+ Column("id", Integer, primary_key=True),
549
+ Column(name, String(50), nullable=False),
550
+ )
551
+
552
+ # table is created
553
+ t.create(connection)
554
+
555
+ # automatic param generated by insert
556
+ connection.execute(t.insert().values({"id": 1, name: "some name"}))
557
+
558
+ # automatic param generated by criteria, plus selecting the column
559
+ stmt = select(t.c[name]).where(t.c[name] == "some name")
560
+
561
+ eq_(connection.scalar(stmt), "some name")
562
+
563
+ # use the name in a param explicitly
564
+ stmt = select(t.c[name]).where(t.c[name] == bindparam(name))
565
+
566
+ row = connection.execute(stmt, {name: "some name"}).first()
567
+
568
+ # name works as the key from cursor.description
569
+ eq_(row._mapping[name], "some name")
570
+
571
+ # use expanding IN
572
+ stmt = select(t.c[name]).where(
573
+ t.c[name].in_(["some name", "some other_name"])
574
+ )
575
+
576
+ connection.execute(stmt).first()
577
+
578
+ @testing.fixture
579
+ def multirow_fixture(self, metadata, connection):
580
+ mytable = Table(
581
+ "mytable",
582
+ metadata,
583
+ Column("myid", Integer),
584
+ Column("name", String(50)),
585
+ Column("desc", String(50)),
586
+ )
587
+
588
+ mytable.create(connection)
589
+
590
+ connection.execute(
591
+ mytable.insert(),
592
+ [
593
+ {"myid": 1, "name": "a", "desc": "a_desc"},
594
+ {"myid": 2, "name": "b", "desc": "b_desc"},
595
+ {"myid": 3, "name": "c", "desc": "c_desc"},
596
+ {"myid": 4, "name": "d", "desc": "d_desc"},
597
+ ],
598
+ )
599
+ yield mytable
600
+
601
+ @tough_parameters
602
+ def test_standalone_bindparam_escape(
603
+ self, paramname, connection, multirow_fixture
604
+ ):
605
+ tbl1 = multirow_fixture
606
+ stmt = select(tbl1.c.myid).where(
607
+ tbl1.c.name == bindparam(paramname, value="x")
608
+ )
609
+ res = connection.scalar(stmt, {paramname: "c"})
610
+ eq_(res, 3)
611
+
612
+ @tough_parameters
613
+ def test_standalone_bindparam_escape_expanding(
614
+ self, paramname, connection, multirow_fixture
615
+ ):
616
+ tbl1 = multirow_fixture
617
+ stmt = (
618
+ select(tbl1.c.myid)
619
+ .where(tbl1.c.name.in_(bindparam(paramname, value=["a", "b"])))
620
+ .order_by(tbl1.c.myid)
621
+ )
622
+
623
+ res = connection.scalars(stmt, {paramname: ["d", "a"]}).all()
624
+ eq_(res, [1, 4])
625
+
626
+
627
+ class ReturningGuardsTest(fixtures.TablesTest):
628
+ """test that the various 'returning' flags are set appropriately"""
629
+
630
+ __backend__ = True
631
+
632
+ @classmethod
633
+ def define_tables(cls, metadata):
634
+ Table(
635
+ "t",
636
+ metadata,
637
+ Column("id", Integer, primary_key=True, autoincrement=False),
638
+ Column("data", String(50)),
639
+ )
640
+
641
+ @testing.fixture
642
+ def run_stmt(self, connection):
643
+ t = self.tables.t
644
+
645
+ def go(stmt, executemany, id_param_name, expect_success):
646
+ stmt = stmt.returning(t.c.id)
647
+
648
+ if executemany:
649
+ if not expect_success:
650
+ # for RETURNING executemany(), we raise our own
651
+ # error as this is independent of general RETURNING
652
+ # support
653
+ with expect_raises_message(
654
+ exc.StatementError,
655
+ rf"Dialect {connection.dialect.name}\+"
656
+ f"{connection.dialect.driver} with "
657
+ f"current server capabilities does not support "
658
+ f".*RETURNING when executemany is used",
659
+ ):
660
+ connection.execute(
661
+ stmt,
662
+ [
663
+ {id_param_name: 1, "data": "d1"},
664
+ {id_param_name: 2, "data": "d2"},
665
+ {id_param_name: 3, "data": "d3"},
666
+ ],
667
+ )
668
+ else:
669
+ result = connection.execute(
670
+ stmt,
671
+ [
672
+ {id_param_name: 1, "data": "d1"},
673
+ {id_param_name: 2, "data": "d2"},
674
+ {id_param_name: 3, "data": "d3"},
675
+ ],
676
+ )
677
+ eq_(result.all(), [(1,), (2,), (3,)])
678
+ else:
679
+ if not expect_success:
680
+ # for RETURNING execute(), we pass all the way to the DB
681
+ # and let it fail
682
+ with expect_raises(exc.DBAPIError):
683
+ connection.execute(
684
+ stmt, {id_param_name: 1, "data": "d1"}
685
+ )
686
+ else:
687
+ result = connection.execute(
688
+ stmt, {id_param_name: 1, "data": "d1"}
689
+ )
690
+ eq_(result.all(), [(1,)])
691
+
692
+ return go
693
+
694
+ def test_insert_single(self, connection, run_stmt):
695
+ t = self.tables.t
696
+
697
+ stmt = t.insert()
698
+
699
+ run_stmt(stmt, False, "id", connection.dialect.insert_returning)
700
+
701
+ def test_insert_many(self, connection, run_stmt):
702
+ t = self.tables.t
703
+
704
+ stmt = t.insert()
705
+
706
+ run_stmt(
707
+ stmt, True, "id", connection.dialect.insert_executemany_returning
708
+ )
709
+
710
+ def test_update_single(self, connection, run_stmt):
711
+ t = self.tables.t
712
+
713
+ connection.execute(
714
+ t.insert(),
715
+ [
716
+ {"id": 1, "data": "d1"},
717
+ {"id": 2, "data": "d2"},
718
+ {"id": 3, "data": "d3"},
719
+ ],
720
+ )
721
+
722
+ stmt = t.update().where(t.c.id == bindparam("b_id"))
723
+
724
+ run_stmt(stmt, False, "b_id", connection.dialect.update_returning)
725
+
726
+ def test_update_many(self, connection, run_stmt):
727
+ t = self.tables.t
728
+
729
+ connection.execute(
730
+ t.insert(),
731
+ [
732
+ {"id": 1, "data": "d1"},
733
+ {"id": 2, "data": "d2"},
734
+ {"id": 3, "data": "d3"},
735
+ ],
736
+ )
737
+
738
+ stmt = t.update().where(t.c.id == bindparam("b_id"))
739
+
740
+ run_stmt(
741
+ stmt, True, "b_id", connection.dialect.update_executemany_returning
742
+ )
743
+
744
+ def test_delete_single(self, connection, run_stmt):
745
+ t = self.tables.t
746
+
747
+ connection.execute(
748
+ t.insert(),
749
+ [
750
+ {"id": 1, "data": "d1"},
751
+ {"id": 2, "data": "d2"},
752
+ {"id": 3, "data": "d3"},
753
+ ],
754
+ )
755
+
756
+ stmt = t.delete().where(t.c.id == bindparam("b_id"))
757
+
758
+ run_stmt(stmt, False, "b_id", connection.dialect.delete_returning)
759
+
760
+ def test_delete_many(self, connection, run_stmt):
761
+ t = self.tables.t
762
+
763
+ connection.execute(
764
+ t.insert(),
765
+ [
766
+ {"id": 1, "data": "d1"},
767
+ {"id": 2, "data": "d2"},
768
+ {"id": 3, "data": "d3"},
769
+ ],
770
+ )
771
+
772
+ stmt = t.delete().where(t.c.id == bindparam("b_id"))
773
+
774
+ run_stmt(
775
+ stmt, True, "b_id", connection.dialect.delete_executemany_returning
776
+ )