SQLAlchemy 2.1.0rc1__cp315-cp315-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 (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win_amd64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win_amd64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win_amd64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win_amd64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win_amd64.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win_amd64.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win_amd64.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win_amd64.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,153 @@
1
+ # dialects/mysql/provision.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
+ import contextlib
9
+
10
+ from ... import event
11
+ from ... import exc
12
+ from ...testing.provision import allow_stale_update_impl
13
+ from ...testing.provision import configure_follower
14
+ from ...testing.provision import create_db
15
+ from ...testing.provision import delete_from_all_tables
16
+ from ...testing.provision import drop_db
17
+ from ...testing.provision import generate_driver_url
18
+ from ...testing.provision import temp_table_keyword_args
19
+ from ...testing.provision import upsert
20
+
21
+
22
+ @generate_driver_url.for_db("mysql", "mariadb")
23
+ def generate_driver_url(url, driver, query_str):
24
+ backend = url.get_backend_name()
25
+
26
+ # NOTE: at the moment, tests are running mariadbconnector
27
+ # against both mariadb and mysql backends. if we want this to be
28
+ # limited, do the decision making here to reject a "mysql+mariadbconnector"
29
+ # URL. Optionally also re-enable the module level
30
+ # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
31
+ # a unit and/or functional test.
32
+
33
+ # all the Jenkins tests have been running mysqlclient Python library
34
+ # built against mariadb client drivers for years against all MySQL /
35
+ # MariaDB versions going back to MySQL 5.6, currently they can talk
36
+ # to MySQL databases without problems.
37
+
38
+ if backend == "mysql":
39
+ dialect_cls = url.get_dialect()
40
+ if dialect_cls._is_mariadb_from_url(url):
41
+ backend = "mariadb"
42
+
43
+ new_url = url.set(
44
+ drivername="%s+%s" % (backend, driver)
45
+ ).update_query_string(query_str)
46
+
47
+ if driver == "mariadbconnector":
48
+ new_url = new_url.difference_update_query(["charset"])
49
+ elif driver == "mysqlconnector":
50
+ new_url = new_url.update_query_pairs(
51
+ [("collation", "utf8mb4_general_ci")]
52
+ )
53
+
54
+ try:
55
+ new_url.get_dialect()
56
+ except exc.NoSuchModuleError:
57
+ return None
58
+ else:
59
+ return new_url
60
+
61
+
62
+ @create_db.for_db("mysql", "mariadb")
63
+ def _mysql_create_db(cfg, eng, ident):
64
+ with eng.begin() as conn:
65
+ try:
66
+ _mysql_drop_db(cfg, conn, ident)
67
+ except Exception:
68
+ pass
69
+
70
+ with eng.begin() as conn:
71
+ conn.exec_driver_sql(
72
+ "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
73
+ )
74
+ conn.exec_driver_sql(
75
+ "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
76
+ )
77
+ conn.exec_driver_sql(
78
+ "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
79
+ )
80
+
81
+
82
+ @configure_follower.for_db("mysql", "mariadb")
83
+ def _mysql_configure_follower(config, ident):
84
+ config.test_schema = "%s_test_schema" % ident
85
+ config.test_schema_2 = "%s_test_schema_2" % ident
86
+
87
+
88
+ @drop_db.for_db("mysql", "mariadb")
89
+ def _mysql_drop_db(cfg, eng, ident):
90
+ with eng.begin() as conn:
91
+ conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
92
+ conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
93
+ conn.exec_driver_sql("DROP DATABASE %s" % ident)
94
+
95
+
96
+ @temp_table_keyword_args.for_db("mysql", "mariadb")
97
+ def _mysql_temp_table_keyword_args(cfg, eng):
98
+ return {"prefixes": ["TEMPORARY"]}
99
+
100
+
101
+ @upsert.for_db("mariadb")
102
+ def _upsert(
103
+ cfg,
104
+ table,
105
+ returning,
106
+ *,
107
+ set_lambda=None,
108
+ sort_by_parameter_order=False,
109
+ index_elements=None,
110
+ ):
111
+ from sqlalchemy.dialects.mysql import insert
112
+
113
+ stmt = insert(table)
114
+
115
+ if set_lambda:
116
+ stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
117
+ else:
118
+ pk1 = table.primary_key.c[0]
119
+ stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
120
+
121
+ stmt = stmt.returning(
122
+ *returning, sort_by_parameter_order=sort_by_parameter_order
123
+ )
124
+ return stmt
125
+
126
+
127
+ @delete_from_all_tables.for_db("mysql", "mariadb")
128
+ def _delete_from_all_tables(connection, cfg, metadata):
129
+ connection.exec_driver_sql("SET foreign_key_checks = 0")
130
+ try:
131
+ delete_from_all_tables.call_original(connection, cfg, metadata)
132
+ finally:
133
+ connection.exec_driver_sql("SET foreign_key_checks = 1")
134
+
135
+
136
+ @allow_stale_update_impl.for_db("mariadb")
137
+ def _allow_stale_update_impl(cfg):
138
+ @contextlib.contextmanager
139
+ def go():
140
+ @event.listens_for(cfg.db, "engine_connect")
141
+ def turn_off_snapshot_isolation(conn):
142
+ conn.exec_driver_sql("SET innodb_snapshot_isolation = 'OFF'")
143
+ conn.rollback()
144
+
145
+ try:
146
+ yield
147
+ finally:
148
+ event.remove(cfg.db, "engine_connect", turn_off_snapshot_isolation)
149
+
150
+ # dispose the pool; quick way to just have those reset
151
+ cfg.db.dispose()
152
+
153
+ return go()
@@ -0,0 +1,188 @@
1
+ # dialects/mysql/pymysql.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+
8
+ r"""
9
+
10
+ .. dialect:: mysql+pymysql
11
+ :name: PyMySQL
12
+ :dbapi: pymysql
13
+ :connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
14
+ :url: https://pymysql.readthedocs.io/
15
+
16
+ Unicode
17
+ -------
18
+
19
+ Please see :ref:`mysql_unicode` for current recommendations on unicode
20
+ handling.
21
+
22
+ .. _pymysql_ssl:
23
+
24
+ SSL Connections
25
+ ------------------
26
+
27
+ The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
28
+ described at :ref:`mysqldb_ssl`. See that section for additional examples.
29
+
30
+ If the server uses an automatically-generated certificate that is self-signed
31
+ or does not match the host name (as seen from the client), it may also be
32
+ necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
33
+
34
+ connection_uri = (
35
+ "mysql+pymysql://scott:tiger@192.168.0.134/test"
36
+ "?ssl_ca=/home/gord/client-ssl/ca.pem"
37
+ "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
38
+ "&ssl_key=/home/gord/client-ssl/client-key.pem"
39
+ "&ssl_check_hostname=false"
40
+ )
41
+
42
+ MySQL-Python Compatibility
43
+ --------------------------
44
+
45
+ The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
46
+ and targets 100% compatibility. Most behavioral notes for MySQL-python apply
47
+ to the pymysql driver as well.
48
+
49
+ """ # noqa
50
+
51
+ from __future__ import annotations
52
+
53
+ from typing import Any
54
+ from typing import Literal
55
+ from typing import Optional
56
+ from typing import Type
57
+ from typing import TYPE_CHECKING
58
+ from typing import Union
59
+
60
+ from .mysqldb import MySQLDialect_mysqldb
61
+ from ... import util
62
+ from ...util import langhelpers
63
+
64
+ if TYPE_CHECKING:
65
+
66
+ from ...engine.interfaces import ConnectArgsType
67
+ from ...engine.interfaces import DBAPIConnection
68
+ from ...engine.interfaces import DBAPICursor
69
+ from ...engine.interfaces import DBAPIModule
70
+ from ...engine.interfaces import PoolProxiedConnection
71
+ from ...engine.url import URL
72
+
73
+
74
+ def _connection_ping_reconnects_true(connection_cls: Type[Any]) -> bool:
75
+ """Given a Connection class like pymysql.Connection, aiomysql.Connection,
76
+ asyncmy.Connection, inspect the ping() method and determine if it
77
+ has a "reconnect" parameter that either defaults to True, or is positional.
78
+
79
+ a return value of True here means that when we call ``connection.ping()``,
80
+ we **must** pass `reconnect=False`. a return value of False means that
81
+ we should call ``connection.ping()`` with **no arguments**.
82
+
83
+ This routine originates from issue #10492 for pymysql, however arg
84
+ signature mismatches in aiomysql/asyncmy tracked by issue #13306
85
+ necessitated a more open ended function.
86
+
87
+ """
88
+ insp = langhelpers.get_callable_argspec(connection_cls.ping)
89
+ try:
90
+ reconnect_arg = insp.args[1]
91
+ except IndexError:
92
+ return False
93
+ else:
94
+ return reconnect_arg == "reconnect" and (
95
+ not insp.defaults or insp.defaults[0] is not False
96
+ )
97
+
98
+
99
+ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
100
+ driver = "pymysql"
101
+ supports_statement_cache = True
102
+
103
+ description_encoding = None
104
+
105
+ def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
106
+ # pymysql publishes its own version as ``VERSION_STRING``; the
107
+ # ``__version__`` and ``version_info`` attributes it also publishes
108
+ # are mysqlclient compatibility values, e.g. ``"2.2.8"`` for a
109
+ # pymysql that is itself version 1.2.0
110
+ return util.parse_version_string(
111
+ getattr(dbapi, "VERSION_STRING", None)
112
+ )
113
+
114
+ @langhelpers.memoized_property
115
+ def supports_server_side_cursors(self) -> bool:
116
+ try:
117
+ cursors = __import__("pymysql.cursors").cursors
118
+ self._sscursor = cursors.SSCursor
119
+ return True
120
+ except (ImportError, AttributeError):
121
+ return False
122
+
123
+ @classmethod
124
+ def import_dbapi(cls) -> DBAPIModule:
125
+ return __import__("pymysql")
126
+
127
+ @langhelpers.memoized_property
128
+ def _send_false_to_ping(self) -> bool:
129
+ """determine if pymysql has deprecated, changed the default of,
130
+ or removed the 'reconnect' argument of connection.ping().
131
+
132
+ See #10492 and
133
+ https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
134
+ for background.
135
+
136
+ Revised as part of #13306
137
+
138
+ """ # noqa: E501
139
+
140
+ try:
141
+ Connection = __import__(
142
+ "pymysql.connections"
143
+ ).connections.Connection
144
+ except (ImportError, AttributeError):
145
+ return True
146
+ else:
147
+ return _connection_ping_reconnects_true(Connection)
148
+
149
+ def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
150
+ if self._send_false_to_ping:
151
+ dbapi_connection.ping(False)
152
+ else:
153
+ dbapi_connection.ping()
154
+
155
+ return True
156
+
157
+ def create_connect_args(
158
+ self, url: URL, _translate_args: Optional[dict[str, Any]] = None
159
+ ) -> ConnectArgsType:
160
+ if _translate_args is None:
161
+ _translate_args = dict(username="user")
162
+ return super().create_connect_args(
163
+ url, _translate_args=_translate_args
164
+ )
165
+
166
+ def is_disconnect(
167
+ self,
168
+ e: DBAPIModule.Error,
169
+ connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
170
+ cursor: Optional[DBAPICursor],
171
+ ) -> bool:
172
+ if super().is_disconnect(e, connection, cursor):
173
+ return True
174
+ elif isinstance(e, self.loaded_dbapi.Error):
175
+ str_e = str(e).lower()
176
+ return (
177
+ "already closed" in str_e or "connection was killed" in str_e
178
+ )
179
+ else:
180
+ return False
181
+
182
+ def _extract_error_code(self, exception: BaseException) -> Any:
183
+ if isinstance(exception.args[0], Exception):
184
+ exception = exception.args[0]
185
+ return exception.args[0]
186
+
187
+
188
+ dialect = MySQLDialect_pymysql
@@ -0,0 +1,157 @@
1
+ # dialects/mysql/pyodbc.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+
8
+
9
+ r"""
10
+
11
+ .. dialect:: mysql+pyodbc
12
+ :name: PyODBC
13
+ :dbapi: pyodbc
14
+ :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
15
+ :url: https://pypi.org/project/pyodbc/
16
+
17
+ .. note::
18
+
19
+ The PyODBC for MySQL dialect is **not tested as part of
20
+ SQLAlchemy's continuous integration**.
21
+ The recommended MySQL dialects are mysqlclient and PyMySQL.
22
+ However, if you want to use the mysql+pyodbc dialect and require
23
+ full support for ``utf8mb4`` characters (including supplementary
24
+ characters like emoji) be sure to use a current release of
25
+ MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
26
+ version of the driver in your DSN or connection string.
27
+
28
+ Pass through exact pyodbc connection string::
29
+
30
+ import urllib
31
+
32
+ connection_string = (
33
+ "DRIVER=MySQL ODBC 8.0 ANSI Driver;"
34
+ "SERVER=localhost;"
35
+ "PORT=3307;"
36
+ "DATABASE=mydb;"
37
+ "UID=root;"
38
+ "PWD=(whatever);"
39
+ "charset=utf8mb4;"
40
+ )
41
+ params = urllib.parse.quote_plus(connection_string)
42
+ connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
43
+
44
+ """ # noqa
45
+
46
+ from __future__ import annotations
47
+
48
+ import datetime
49
+ import re
50
+ from typing import Any
51
+ from typing import Callable
52
+ from typing import Optional
53
+ from typing import TYPE_CHECKING
54
+ from typing import Union
55
+
56
+ from .base import MySQLDialect
57
+ from .base import MySQLExecutionContext
58
+ from .types import TIME
59
+ from ... import exc
60
+ from ... import util
61
+ from ...connectors.pyodbc import PyODBCConnector
62
+ from ...sql.sqltypes import Time
63
+
64
+ if TYPE_CHECKING:
65
+ from ...engine import Connection
66
+ from ...engine.interfaces import DBAPIConnection
67
+ from ...engine.interfaces import Dialect
68
+ from ...sql.type_api import _ResultProcessorType
69
+
70
+
71
+ class _pyodbcTIME(TIME):
72
+ def result_processor(
73
+ self, dialect: Dialect, coltype: object
74
+ ) -> _ResultProcessorType[datetime.time]:
75
+ def process(value: Any) -> Union[datetime.time, None]:
76
+ # pyodbc returns a datetime.time object; no need to convert
77
+ return value # type: ignore[no-any-return]
78
+
79
+ return process
80
+
81
+
82
+ class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
83
+ def get_lastrowid(self) -> int:
84
+ cursor = self.create_cursor()
85
+ cursor.execute("SELECT LAST_INSERT_ID()")
86
+ lastrowid = cursor.fetchone()[0] # type: ignore[index]
87
+ cursor.close()
88
+ return lastrowid # type: ignore[no-any-return]
89
+
90
+
91
+ class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
92
+ supports_statement_cache = True
93
+ colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
94
+ supports_unicode_statements = True
95
+ execution_ctx_cls = MySQLExecutionContext_pyodbc
96
+
97
+ pyodbc_driver_name = "MySQL"
98
+
99
+ def _detect_charset(self, connection: Connection) -> str:
100
+ """Sniff out the character set in use for connection results."""
101
+
102
+ # Prefer 'character_set_results' for the current connection over the
103
+ # value in the driver. SET NAMES or individual variable SETs will
104
+ # change the charset without updating the driver's view of the world.
105
+ #
106
+ # If it's decided that issuing that sort of SQL leaves you SOL, then
107
+ # this can prefer the driver value.
108
+
109
+ # set this to None as _fetch_setting attempts to use it (None is OK)
110
+ self._connection_charset = None
111
+ try:
112
+ value = self._fetch_setting(connection, "character_set_client")
113
+ if value:
114
+ return value
115
+ except exc.DBAPIError:
116
+ pass
117
+
118
+ util.warn(
119
+ "Could not detect the connection character set. "
120
+ "Assuming latin1."
121
+ )
122
+ return "latin1"
123
+
124
+ def _get_server_version_info(
125
+ self, connection: Connection
126
+ ) -> tuple[int, ...]:
127
+ return MySQLDialect._get_server_version_info(self, connection)
128
+
129
+ def _extract_error_code(self, exception: BaseException) -> Optional[int]:
130
+ m = re.compile(r"\((\d+)\)").search(str(exception.args))
131
+ if m is None:
132
+ return None
133
+ c: Optional[str] = m.group(1)
134
+ if c:
135
+ return int(c)
136
+ else:
137
+ return None
138
+
139
+ def on_connect(self) -> Callable[[DBAPIConnection], None]:
140
+ super_ = super().on_connect()
141
+
142
+ def on_connect(conn: DBAPIConnection) -> None:
143
+ if super_ is not None:
144
+ super_(conn)
145
+
146
+ # declare Unicode encoding for pyodbc as per
147
+ # https://github.com/mkleehammer/pyodbc/wiki/Unicode
148
+ pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
149
+ pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
150
+ conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
151
+ conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
152
+ conn.setencoding(encoding="utf-8")
153
+
154
+ return on_connect
155
+
156
+
157
+ dialect = MySQLDialect_pyodbc