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,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,158 @@
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
+ from __future__ import annotations
51
+
52
+ from typing import Any
53
+ from typing import Dict
54
+ from typing import Optional
55
+ from typing import TYPE_CHECKING
56
+ from typing import Union
57
+
58
+ from .mysqldb import MySQLDialect_mysqldb
59
+ from ...util import langhelpers
60
+ from ...util.typing import Literal
61
+
62
+ if TYPE_CHECKING:
63
+
64
+ from ...engine.interfaces import ConnectArgsType
65
+ from ...engine.interfaces import DBAPIConnection
66
+ from ...engine.interfaces import DBAPICursor
67
+ from ...engine.interfaces import DBAPIModule
68
+ from ...engine.interfaces import PoolProxiedConnection
69
+ from ...engine.url import URL
70
+
71
+
72
+ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
73
+ driver = "pymysql"
74
+ supports_statement_cache = True
75
+
76
+ description_encoding = None
77
+
78
+ @langhelpers.memoized_property
79
+ def supports_server_side_cursors(self) -> bool:
80
+ try:
81
+ cursors = __import__("pymysql.cursors").cursors
82
+ self._sscursor = cursors.SSCursor
83
+ return True
84
+ except (ImportError, AttributeError):
85
+ return False
86
+
87
+ @classmethod
88
+ def import_dbapi(cls) -> DBAPIModule:
89
+ return __import__("pymysql")
90
+
91
+ @langhelpers.memoized_property
92
+ def _send_false_to_ping(self) -> bool:
93
+ """determine if pymysql has deprecated, changed the default of,
94
+ or removed the 'reconnect' argument of connection.ping().
95
+
96
+ See #10492 and
97
+ https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
98
+ for background.
99
+
100
+ """ # noqa: E501
101
+
102
+ try:
103
+ Connection = __import__(
104
+ "pymysql.connections"
105
+ ).connections.Connection
106
+ except (ImportError, AttributeError):
107
+ return True
108
+ else:
109
+ insp = langhelpers.get_callable_argspec(Connection.ping)
110
+ try:
111
+ reconnect_arg = insp.args[1]
112
+ except IndexError:
113
+ return False
114
+ else:
115
+ return reconnect_arg == "reconnect" and (
116
+ not insp.defaults or insp.defaults[0] is not False
117
+ )
118
+
119
+ def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
120
+ if self._send_false_to_ping:
121
+ dbapi_connection.ping(False)
122
+ else:
123
+ dbapi_connection.ping()
124
+
125
+ return True
126
+
127
+ def create_connect_args(
128
+ self, url: URL, _translate_args: Optional[Dict[str, Any]] = None
129
+ ) -> ConnectArgsType:
130
+ if _translate_args is None:
131
+ _translate_args = dict(username="user")
132
+ return super().create_connect_args(
133
+ url, _translate_args=_translate_args
134
+ )
135
+
136
+ def is_disconnect(
137
+ self,
138
+ e: DBAPIModule.Error,
139
+ connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
140
+ cursor: Optional[DBAPICursor],
141
+ ) -> bool:
142
+ if super().is_disconnect(e, connection, cursor):
143
+ return True
144
+ elif isinstance(e, self.loaded_dbapi.Error):
145
+ str_e = str(e).lower()
146
+ return (
147
+ "already closed" in str_e or "connection was killed" in str_e
148
+ )
149
+ else:
150
+ return False
151
+
152
+ def _extract_error_code(self, exception: BaseException) -> Any:
153
+ if isinstance(exception.args[0], Exception):
154
+ exception = exception.args[0]
155
+ return exception.args[0]
156
+
157
+
158
+ 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
+ from __future__ import annotations
46
+
47
+ import datetime
48
+ import re
49
+ from typing import Any
50
+ from typing import Callable
51
+ from typing import Optional
52
+ from typing import Tuple
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