SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 (270) hide show
  1. sqlalchemy/__init__.py +298 -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 +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -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 +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -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 +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -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 +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -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 +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -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 +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.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,157 @@
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 Literal
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
+
61
+ if TYPE_CHECKING:
62
+
63
+ from ...engine.interfaces import ConnectArgsType
64
+ from ...engine.interfaces import DBAPIConnection
65
+ from ...engine.interfaces import DBAPICursor
66
+ from ...engine.interfaces import DBAPIModule
67
+ from ...engine.interfaces import PoolProxiedConnection
68
+ from ...engine.url import URL
69
+
70
+
71
+ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
72
+ driver = "pymysql"
73
+ supports_statement_cache = True
74
+
75
+ description_encoding = None
76
+
77
+ @langhelpers.memoized_property
78
+ def supports_server_side_cursors(self) -> bool:
79
+ try:
80
+ cursors = __import__("pymysql.cursors").cursors
81
+ self._sscursor = cursors.SSCursor
82
+ return True
83
+ except (ImportError, AttributeError):
84
+ return False
85
+
86
+ @classmethod
87
+ def import_dbapi(cls) -> DBAPIModule:
88
+ return __import__("pymysql")
89
+
90
+ @langhelpers.memoized_property
91
+ def _send_false_to_ping(self) -> bool:
92
+ """determine if pymysql has deprecated, changed the default of,
93
+ or removed the 'reconnect' argument of connection.ping().
94
+
95
+ See #10492 and
96
+ https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
97
+ for background.
98
+
99
+ """ # noqa: E501
100
+
101
+ try:
102
+ Connection = __import__(
103
+ "pymysql.connections"
104
+ ).connections.Connection
105
+ except (ImportError, AttributeError):
106
+ return True
107
+ else:
108
+ insp = langhelpers.get_callable_argspec(Connection.ping)
109
+ try:
110
+ reconnect_arg = insp.args[1]
111
+ except IndexError:
112
+ return False
113
+ else:
114
+ return reconnect_arg == "reconnect" and (
115
+ not insp.defaults or insp.defaults[0] is not False
116
+ )
117
+
118
+ def do_ping(self, dbapi_connection: DBAPIConnection) -> Literal[True]:
119
+ if self._send_false_to_ping:
120
+ dbapi_connection.ping(False)
121
+ else:
122
+ dbapi_connection.ping()
123
+
124
+ return True
125
+
126
+ def create_connect_args(
127
+ self, url: URL, _translate_args: Optional[dict[str, Any]] = None
128
+ ) -> ConnectArgsType:
129
+ if _translate_args is None:
130
+ _translate_args = dict(username="user")
131
+ return super().create_connect_args(
132
+ url, _translate_args=_translate_args
133
+ )
134
+
135
+ def is_disconnect(
136
+ self,
137
+ e: DBAPIModule.Error,
138
+ connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
139
+ cursor: Optional[DBAPICursor],
140
+ ) -> bool:
141
+ if super().is_disconnect(e, connection, cursor):
142
+ return True
143
+ elif isinstance(e, self.loaded_dbapi.Error):
144
+ str_e = str(e).lower()
145
+ return (
146
+ "already closed" in str_e or "connection was killed" in str_e
147
+ )
148
+ else:
149
+ return False
150
+
151
+ def _extract_error_code(self, exception: BaseException) -> Any:
152
+ if isinstance(exception.args[0], Exception):
153
+ exception = exception.args[0]
154
+ return exception.args[0]
155
+
156
+
157
+ dialect = MySQLDialect_pymysql
@@ -0,0 +1,156 @@
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 TYPE_CHECKING
53
+ from typing import Union
54
+
55
+ from .base import MySQLDialect
56
+ from .base import MySQLExecutionContext
57
+ from .types import TIME
58
+ from ... import exc
59
+ from ... import util
60
+ from ...connectors.pyodbc import PyODBCConnector
61
+ from ...sql.sqltypes import Time
62
+
63
+ if TYPE_CHECKING:
64
+ from ...engine import Connection
65
+ from ...engine.interfaces import DBAPIConnection
66
+ from ...engine.interfaces import Dialect
67
+ from ...sql.type_api import _ResultProcessorType
68
+
69
+
70
+ class _pyodbcTIME(TIME):
71
+ def result_processor(
72
+ self, dialect: Dialect, coltype: object
73
+ ) -> _ResultProcessorType[datetime.time]:
74
+ def process(value: Any) -> Union[datetime.time, None]:
75
+ # pyodbc returns a datetime.time object; no need to convert
76
+ return value # type: ignore[no-any-return]
77
+
78
+ return process
79
+
80
+
81
+ class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
82
+ def get_lastrowid(self) -> int:
83
+ cursor = self.create_cursor()
84
+ cursor.execute("SELECT LAST_INSERT_ID()")
85
+ lastrowid = cursor.fetchone()[0] # type: ignore[index]
86
+ cursor.close()
87
+ return lastrowid # type: ignore[no-any-return]
88
+
89
+
90
+ class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
91
+ supports_statement_cache = True
92
+ colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
93
+ supports_unicode_statements = True
94
+ execution_ctx_cls = MySQLExecutionContext_pyodbc
95
+
96
+ pyodbc_driver_name = "MySQL"
97
+
98
+ def _detect_charset(self, connection: Connection) -> str:
99
+ """Sniff out the character set in use for connection results."""
100
+
101
+ # Prefer 'character_set_results' for the current connection over the
102
+ # value in the driver. SET NAMES or individual variable SETs will
103
+ # change the charset without updating the driver's view of the world.
104
+ #
105
+ # If it's decided that issuing that sort of SQL leaves you SOL, then
106
+ # this can prefer the driver value.
107
+
108
+ # set this to None as _fetch_setting attempts to use it (None is OK)
109
+ self._connection_charset = None
110
+ try:
111
+ value = self._fetch_setting(connection, "character_set_client")
112
+ if value:
113
+ return value
114
+ except exc.DBAPIError:
115
+ pass
116
+
117
+ util.warn(
118
+ "Could not detect the connection character set. "
119
+ "Assuming latin1."
120
+ )
121
+ return "latin1"
122
+
123
+ def _get_server_version_info(
124
+ self, connection: Connection
125
+ ) -> tuple[int, ...]:
126
+ return MySQLDialect._get_server_version_info(self, connection)
127
+
128
+ def _extract_error_code(self, exception: BaseException) -> Optional[int]:
129
+ m = re.compile(r"\((\d+)\)").search(str(exception.args))
130
+ if m is None:
131
+ return None
132
+ c: Optional[str] = m.group(1)
133
+ if c:
134
+ return int(c)
135
+ else:
136
+ return None
137
+
138
+ def on_connect(self) -> Callable[[DBAPIConnection], None]:
139
+ super_ = super().on_connect()
140
+
141
+ def on_connect(conn: DBAPIConnection) -> None:
142
+ if super_ is not None:
143
+ super_(conn)
144
+
145
+ # declare Unicode encoding for pyodbc as per
146
+ # https://github.com/mkleehammer/pyodbc/wiki/Unicode
147
+ pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
148
+ pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
149
+ conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
150
+ conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
151
+ conn.setencoding(encoding="utf-8")
152
+
153
+ return on_connect
154
+
155
+
156
+ dialect = MySQLDialect_pyodbc