SQLAlchemy 2.0.36__cp313-cp313-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 (273) hide show
  1. SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
  2. SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
  3. SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
  4. SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
  5. SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
  6. sqlalchemy/__init__.py +294 -0
  7. sqlalchemy/connectors/__init__.py +18 -0
  8. sqlalchemy/connectors/aioodbc.py +174 -0
  9. sqlalchemy/connectors/asyncio.py +213 -0
  10. sqlalchemy/connectors/pyodbc.py +249 -0
  11. sqlalchemy/cyextension/__init__.py +6 -0
  12. sqlalchemy/cyextension/collections.cp313-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/collections.pyx +409 -0
  14. sqlalchemy/cyextension/immutabledict.cp313-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  16. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  17. sqlalchemy/cyextension/processors.cp313-win_amd64.pyd +0 -0
  18. sqlalchemy/cyextension/processors.pyx +68 -0
  19. sqlalchemy/cyextension/resultproxy.cp313-win_amd64.pyd +0 -0
  20. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  21. sqlalchemy/cyextension/util.cp313-win_amd64.pyd +0 -0
  22. sqlalchemy/cyextension/util.pyx +91 -0
  23. sqlalchemy/dialects/__init__.py +61 -0
  24. sqlalchemy/dialects/_typing.py +25 -0
  25. sqlalchemy/dialects/mssql/__init__.py +88 -0
  26. sqlalchemy/dialects/mssql/aioodbc.py +64 -0
  27. sqlalchemy/dialects/mssql/base.py +4010 -0
  28. sqlalchemy/dialects/mssql/information_schema.py +254 -0
  29. sqlalchemy/dialects/mssql/json.py +133 -0
  30. sqlalchemy/dialects/mssql/provision.py +162 -0
  31. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  32. sqlalchemy/dialects/mssql/pyodbc.py +745 -0
  33. sqlalchemy/dialects/mysql/__init__.py +101 -0
  34. sqlalchemy/dialects/mysql/aiomysql.py +333 -0
  35. sqlalchemy/dialects/mysql/asyncmy.py +337 -0
  36. sqlalchemy/dialects/mysql/base.py +3494 -0
  37. sqlalchemy/dialects/mysql/cymysql.py +84 -0
  38. sqlalchemy/dialects/mysql/dml.py +219 -0
  39. sqlalchemy/dialects/mysql/enumerated.py +244 -0
  40. sqlalchemy/dialects/mysql/expression.py +141 -0
  41. sqlalchemy/dialects/mysql/json.py +81 -0
  42. sqlalchemy/dialects/mysql/mariadb.py +32 -0
  43. sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
  44. sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
  45. sqlalchemy/dialects/mysql/mysqldb.py +303 -0
  46. sqlalchemy/dialects/mysql/provision.py +110 -0
  47. sqlalchemy/dialects/mysql/pymysql.py +137 -0
  48. sqlalchemy/dialects/mysql/pyodbc.py +138 -0
  49. sqlalchemy/dialects/mysql/reflection.py +677 -0
  50. sqlalchemy/dialects/mysql/reserved_words.py +571 -0
  51. sqlalchemy/dialects/mysql/types.py +774 -0
  52. sqlalchemy/dialects/oracle/__init__.py +67 -0
  53. sqlalchemy/dialects/oracle/base.py +3271 -0
  54. sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
  55. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  56. sqlalchemy/dialects/oracle/oracledb.py +431 -0
  57. sqlalchemy/dialects/oracle/provision.py +220 -0
  58. sqlalchemy/dialects/oracle/types.py +287 -0
  59. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  60. sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
  61. sqlalchemy/dialects/postgresql/array.py +425 -0
  62. sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
  63. sqlalchemy/dialects/postgresql/base.py +5008 -0
  64. sqlalchemy/dialects/postgresql/dml.py +310 -0
  65. sqlalchemy/dialects/postgresql/ext.py +496 -0
  66. sqlalchemy/dialects/postgresql/hstore.py +397 -0
  67. sqlalchemy/dialects/postgresql/json.py +333 -0
  68. sqlalchemy/dialects/postgresql/named_types.py +509 -0
  69. sqlalchemy/dialects/postgresql/operators.py +129 -0
  70. sqlalchemy/dialects/postgresql/pg8000.py +662 -0
  71. sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
  72. sqlalchemy/dialects/postgresql/provision.py +175 -0
  73. sqlalchemy/dialects/postgresql/psycopg.py +772 -0
  74. sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
  75. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  76. sqlalchemy/dialects/postgresql/ranges.py +1029 -0
  77. sqlalchemy/dialects/postgresql/types.py +303 -0
  78. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  79. sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
  80. sqlalchemy/dialects/sqlite/base.py +2805 -0
  81. sqlalchemy/dialects/sqlite/dml.py +240 -0
  82. sqlalchemy/dialects/sqlite/json.py +92 -0
  83. sqlalchemy/dialects/sqlite/provision.py +198 -0
  84. sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
  85. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  86. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  87. sqlalchemy/engine/__init__.py +62 -0
  88. sqlalchemy/engine/_py_processors.py +136 -0
  89. sqlalchemy/engine/_py_row.py +128 -0
  90. sqlalchemy/engine/_py_util.py +74 -0
  91. sqlalchemy/engine/base.py +3375 -0
  92. sqlalchemy/engine/characteristics.py +155 -0
  93. sqlalchemy/engine/create.py +875 -0
  94. sqlalchemy/engine/cursor.py +2181 -0
  95. sqlalchemy/engine/default.py +2365 -0
  96. sqlalchemy/engine/events.py +951 -0
  97. sqlalchemy/engine/interfaces.py +3403 -0
  98. sqlalchemy/engine/mock.py +131 -0
  99. sqlalchemy/engine/processors.py +61 -0
  100. sqlalchemy/engine/reflection.py +2098 -0
  101. sqlalchemy/engine/result.py +2382 -0
  102. sqlalchemy/engine/row.py +401 -0
  103. sqlalchemy/engine/strategies.py +19 -0
  104. sqlalchemy/engine/url.py +910 -0
  105. sqlalchemy/engine/util.py +167 -0
  106. sqlalchemy/event/__init__.py +25 -0
  107. sqlalchemy/event/api.py +225 -0
  108. sqlalchemy/event/attr.py +655 -0
  109. sqlalchemy/event/base.py +470 -0
  110. sqlalchemy/event/legacy.py +246 -0
  111. sqlalchemy/event/registry.py +386 -0
  112. sqlalchemy/events.py +17 -0
  113. sqlalchemy/exc.py +830 -0
  114. sqlalchemy/ext/__init__.py +11 -0
  115. sqlalchemy/ext/associationproxy.py +2013 -0
  116. sqlalchemy/ext/asyncio/__init__.py +25 -0
  117. sqlalchemy/ext/asyncio/base.py +279 -0
  118. sqlalchemy/ext/asyncio/engine.py +1466 -0
  119. sqlalchemy/ext/asyncio/exc.py +21 -0
  120. sqlalchemy/ext/asyncio/result.py +961 -0
  121. sqlalchemy/ext/asyncio/scoping.py +1614 -0
  122. sqlalchemy/ext/asyncio/session.py +1936 -0
  123. sqlalchemy/ext/automap.py +1691 -0
  124. sqlalchemy/ext/baked.py +574 -0
  125. sqlalchemy/ext/compiler.py +570 -0
  126. sqlalchemy/ext/declarative/__init__.py +65 -0
  127. sqlalchemy/ext/declarative/extensions.py +548 -0
  128. sqlalchemy/ext/horizontal_shard.py +481 -0
  129. sqlalchemy/ext/hybrid.py +1514 -0
  130. sqlalchemy/ext/indexable.py +341 -0
  131. sqlalchemy/ext/instrumentation.py +450 -0
  132. sqlalchemy/ext/mutable.py +1073 -0
  133. sqlalchemy/ext/mypy/__init__.py +6 -0
  134. sqlalchemy/ext/mypy/apply.py +320 -0
  135. sqlalchemy/ext/mypy/decl_class.py +515 -0
  136. sqlalchemy/ext/mypy/infer.py +590 -0
  137. sqlalchemy/ext/mypy/names.py +335 -0
  138. sqlalchemy/ext/mypy/plugin.py +303 -0
  139. sqlalchemy/ext/mypy/util.py +357 -0
  140. sqlalchemy/ext/orderinglist.py +416 -0
  141. sqlalchemy/ext/serializer.py +181 -0
  142. sqlalchemy/future/__init__.py +16 -0
  143. sqlalchemy/future/engine.py +15 -0
  144. sqlalchemy/inspection.py +174 -0
  145. sqlalchemy/log.py +288 -0
  146. sqlalchemy/orm/__init__.py +170 -0
  147. sqlalchemy/orm/_orm_constructors.py +2571 -0
  148. sqlalchemy/orm/_typing.py +179 -0
  149. sqlalchemy/orm/attributes.py +2835 -0
  150. sqlalchemy/orm/base.py +973 -0
  151. sqlalchemy/orm/bulk_persistence.py +2123 -0
  152. sqlalchemy/orm/clsregistry.py +571 -0
  153. sqlalchemy/orm/collections.py +1620 -0
  154. sqlalchemy/orm/context.py +3268 -0
  155. sqlalchemy/orm/decl_api.py +1883 -0
  156. sqlalchemy/orm/decl_base.py +2190 -0
  157. sqlalchemy/orm/dependency.py +1304 -0
  158. sqlalchemy/orm/descriptor_props.py +1076 -0
  159. sqlalchemy/orm/dynamic.py +300 -0
  160. sqlalchemy/orm/evaluator.py +379 -0
  161. sqlalchemy/orm/events.py +3261 -0
  162. sqlalchemy/orm/exc.py +228 -0
  163. sqlalchemy/orm/identity.py +302 -0
  164. sqlalchemy/orm/instrumentation.py +754 -0
  165. sqlalchemy/orm/interfaces.py +1474 -0
  166. sqlalchemy/orm/loading.py +1682 -0
  167. sqlalchemy/orm/mapped_collection.py +557 -0
  168. sqlalchemy/orm/mapper.py +4432 -0
  169. sqlalchemy/orm/path_registry.py +811 -0
  170. sqlalchemy/orm/persistence.py +1782 -0
  171. sqlalchemy/orm/properties.py +886 -0
  172. sqlalchemy/orm/query.py +3396 -0
  173. sqlalchemy/orm/relationships.py +3500 -0
  174. sqlalchemy/orm/scoping.py +2165 -0
  175. sqlalchemy/orm/session.py +5301 -0
  176. sqlalchemy/orm/state.py +1143 -0
  177. sqlalchemy/orm/state_changes.py +198 -0
  178. sqlalchemy/orm/strategies.py +3473 -0
  179. sqlalchemy/orm/strategy_options.py +2569 -0
  180. sqlalchemy/orm/sync.py +164 -0
  181. sqlalchemy/orm/unitofwork.py +796 -0
  182. sqlalchemy/orm/util.py +2424 -0
  183. sqlalchemy/orm/writeonly.py +678 -0
  184. sqlalchemy/pool/__init__.py +44 -0
  185. sqlalchemy/pool/base.py +1515 -0
  186. sqlalchemy/pool/events.py +370 -0
  187. sqlalchemy/pool/impl.py +581 -0
  188. sqlalchemy/py.typed +0 -0
  189. sqlalchemy/schema.py +70 -0
  190. sqlalchemy/sql/__init__.py +145 -0
  191. sqlalchemy/sql/_dml_constructors.py +140 -0
  192. sqlalchemy/sql/_elements_constructors.py +1850 -0
  193. sqlalchemy/sql/_orm_types.py +20 -0
  194. sqlalchemy/sql/_py_util.py +75 -0
  195. sqlalchemy/sql/_selectable_constructors.py +635 -0
  196. sqlalchemy/sql/_typing.py +460 -0
  197. sqlalchemy/sql/annotation.py +585 -0
  198. sqlalchemy/sql/base.py +2185 -0
  199. sqlalchemy/sql/cache_key.py +1057 -0
  200. sqlalchemy/sql/coercions.py +1405 -0
  201. sqlalchemy/sql/compiler.py +7818 -0
  202. sqlalchemy/sql/crud.py +1669 -0
  203. sqlalchemy/sql/ddl.py +1378 -0
  204. sqlalchemy/sql/default_comparator.py +552 -0
  205. sqlalchemy/sql/dml.py +1817 -0
  206. sqlalchemy/sql/elements.py +5499 -0
  207. sqlalchemy/sql/events.py +455 -0
  208. sqlalchemy/sql/expression.py +162 -0
  209. sqlalchemy/sql/functions.py +2055 -0
  210. sqlalchemy/sql/lambdas.py +1449 -0
  211. sqlalchemy/sql/naming.py +212 -0
  212. sqlalchemy/sql/operators.py +2579 -0
  213. sqlalchemy/sql/roles.py +323 -0
  214. sqlalchemy/sql/schema.py +6158 -0
  215. sqlalchemy/sql/selectable.py +7004 -0
  216. sqlalchemy/sql/sqltypes.py +3827 -0
  217. sqlalchemy/sql/traversals.py +1024 -0
  218. sqlalchemy/sql/type_api.py +2339 -0
  219. sqlalchemy/sql/util.py +1486 -0
  220. sqlalchemy/sql/visitors.py +1165 -0
  221. sqlalchemy/testing/__init__.py +96 -0
  222. sqlalchemy/testing/assertions.py +989 -0
  223. sqlalchemy/testing/assertsql.py +516 -0
  224. sqlalchemy/testing/asyncio.py +135 -0
  225. sqlalchemy/testing/config.py +427 -0
  226. sqlalchemy/testing/engines.py +472 -0
  227. sqlalchemy/testing/entities.py +117 -0
  228. sqlalchemy/testing/exclusions.py +435 -0
  229. sqlalchemy/testing/fixtures/__init__.py +28 -0
  230. sqlalchemy/testing/fixtures/base.py +366 -0
  231. sqlalchemy/testing/fixtures/mypy.py +312 -0
  232. sqlalchemy/testing/fixtures/orm.py +227 -0
  233. sqlalchemy/testing/fixtures/sql.py +503 -0
  234. sqlalchemy/testing/pickleable.py +155 -0
  235. sqlalchemy/testing/plugin/__init__.py +6 -0
  236. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  237. sqlalchemy/testing/plugin/plugin_base.py +779 -0
  238. sqlalchemy/testing/plugin/pytestplugin.py +868 -0
  239. sqlalchemy/testing/profiling.py +324 -0
  240. sqlalchemy/testing/provision.py +496 -0
  241. sqlalchemy/testing/requirements.py +1818 -0
  242. sqlalchemy/testing/schema.py +224 -0
  243. sqlalchemy/testing/suite/__init__.py +19 -0
  244. sqlalchemy/testing/suite/test_cte.py +211 -0
  245. sqlalchemy/testing/suite/test_ddl.py +389 -0
  246. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  247. sqlalchemy/testing/suite/test_dialect.py +740 -0
  248. sqlalchemy/testing/suite/test_insert.py +630 -0
  249. sqlalchemy/testing/suite/test_reflection.py +3225 -0
  250. sqlalchemy/testing/suite/test_results.py +502 -0
  251. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  252. sqlalchemy/testing/suite/test_select.py +1999 -0
  253. sqlalchemy/testing/suite/test_sequence.py +317 -0
  254. sqlalchemy/testing/suite/test_types.py +2141 -0
  255. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  256. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  257. sqlalchemy/testing/util.py +537 -0
  258. sqlalchemy/testing/warnings.py +52 -0
  259. sqlalchemy/types.py +76 -0
  260. sqlalchemy/util/__init__.py +160 -0
  261. sqlalchemy/util/_collections.py +715 -0
  262. sqlalchemy/util/_concurrency_py3k.py +288 -0
  263. sqlalchemy/util/_has_cy.py +40 -0
  264. sqlalchemy/util/_py_collections.py +541 -0
  265. sqlalchemy/util/compat.py +301 -0
  266. sqlalchemy/util/concurrency.py +108 -0
  267. sqlalchemy/util/deprecations.py +401 -0
  268. sqlalchemy/util/langhelpers.py +2218 -0
  269. sqlalchemy/util/preloaded.py +150 -0
  270. sqlalchemy/util/queue.py +322 -0
  271. sqlalchemy/util/tool_support.py +201 -0
  272. sqlalchemy/util/topological.py +120 -0
  273. sqlalchemy/util/typing.py +629 -0
@@ -0,0 +1,303 @@
1
+ # dialects/mysql/mysqldb.py
2
+ # Copyright (C) 2005-2024 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
+ """
11
+
12
+ .. dialect:: mysql+mysqldb
13
+ :name: mysqlclient (maintained fork of MySQL-Python)
14
+ :dbapi: mysqldb
15
+ :connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
16
+ :url: https://pypi.org/project/mysqlclient/
17
+
18
+ Driver Status
19
+ -------------
20
+
21
+ The mysqlclient DBAPI is a maintained fork of the
22
+ `MySQL-Python <https://sourceforge.net/projects/mysql-python>`_ DBAPI
23
+ that is no longer maintained. `mysqlclient`_ supports Python 2 and Python 3
24
+ and is very stable.
25
+
26
+ .. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python
27
+
28
+ .. _mysqldb_unicode:
29
+
30
+ Unicode
31
+ -------
32
+
33
+ Please see :ref:`mysql_unicode` for current recommendations on unicode
34
+ handling.
35
+
36
+ .. _mysqldb_ssl:
37
+
38
+ SSL Connections
39
+ ----------------
40
+
41
+ The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the
42
+ key "ssl", which may be specified using the
43
+ :paramref:`_sa.create_engine.connect_args` dictionary::
44
+
45
+ engine = create_engine(
46
+ "mysql+mysqldb://scott:tiger@192.168.0.134/test",
47
+ connect_args={
48
+ "ssl": {
49
+ "ca": "/home/gord/client-ssl/ca.pem",
50
+ "cert": "/home/gord/client-ssl/client-cert.pem",
51
+ "key": "/home/gord/client-ssl/client-key.pem"
52
+ }
53
+ }
54
+ )
55
+
56
+ For convenience, the following keys may also be specified inline within the URL
57
+ where they will be interpreted into the "ssl" dictionary automatically:
58
+ "ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher",
59
+ "ssl_check_hostname". An example is as follows::
60
+
61
+ connection_uri = (
62
+ "mysql+mysqldb://scott:tiger@192.168.0.134/test"
63
+ "?ssl_ca=/home/gord/client-ssl/ca.pem"
64
+ "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
65
+ "&ssl_key=/home/gord/client-ssl/client-key.pem"
66
+ )
67
+
68
+ .. seealso::
69
+
70
+ :ref:`pymysql_ssl` in the PyMySQL dialect
71
+
72
+
73
+ Using MySQLdb with Google Cloud SQL
74
+ -----------------------------------
75
+
76
+ Google Cloud SQL now recommends use of the MySQLdb dialect. Connect
77
+ using a URL like the following::
78
+
79
+ mysql+mysqldb://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>
80
+
81
+ Server Side Cursors
82
+ -------------------
83
+
84
+ The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`.
85
+
86
+ """
87
+
88
+ import re
89
+
90
+ from .base import MySQLCompiler
91
+ from .base import MySQLDialect
92
+ from .base import MySQLExecutionContext
93
+ from .base import MySQLIdentifierPreparer
94
+ from .base import TEXT
95
+ from ... import sql
96
+ from ... import util
97
+
98
+
99
+ class MySQLExecutionContext_mysqldb(MySQLExecutionContext):
100
+ pass
101
+
102
+
103
+ class MySQLCompiler_mysqldb(MySQLCompiler):
104
+ pass
105
+
106
+
107
+ class MySQLDialect_mysqldb(MySQLDialect):
108
+ driver = "mysqldb"
109
+ supports_statement_cache = True
110
+ supports_unicode_statements = True
111
+ supports_sane_rowcount = True
112
+ supports_sane_multi_rowcount = True
113
+
114
+ supports_native_decimal = True
115
+
116
+ default_paramstyle = "format"
117
+ execution_ctx_cls = MySQLExecutionContext_mysqldb
118
+ statement_compiler = MySQLCompiler_mysqldb
119
+ preparer = MySQLIdentifierPreparer
120
+
121
+ def __init__(self, **kwargs):
122
+ super().__init__(**kwargs)
123
+ self._mysql_dbapi_version = (
124
+ self._parse_dbapi_version(self.dbapi.__version__)
125
+ if self.dbapi is not None and hasattr(self.dbapi, "__version__")
126
+ else (0, 0, 0)
127
+ )
128
+
129
+ def _parse_dbapi_version(self, version):
130
+ m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
131
+ if m:
132
+ return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
133
+ else:
134
+ return (0, 0, 0)
135
+
136
+ @util.langhelpers.memoized_property
137
+ def supports_server_side_cursors(self):
138
+ try:
139
+ cursors = __import__("MySQLdb.cursors").cursors
140
+ self._sscursor = cursors.SSCursor
141
+ return True
142
+ except (ImportError, AttributeError):
143
+ return False
144
+
145
+ @classmethod
146
+ def import_dbapi(cls):
147
+ return __import__("MySQLdb")
148
+
149
+ def on_connect(self):
150
+ super_ = super().on_connect()
151
+
152
+ def on_connect(conn):
153
+ if super_ is not None:
154
+ super_(conn)
155
+
156
+ charset_name = conn.character_set_name()
157
+
158
+ if charset_name is not None:
159
+ cursor = conn.cursor()
160
+ cursor.execute("SET NAMES %s" % charset_name)
161
+ cursor.close()
162
+
163
+ return on_connect
164
+
165
+ def do_ping(self, dbapi_connection):
166
+ dbapi_connection.ping()
167
+ return True
168
+
169
+ def do_executemany(self, cursor, statement, parameters, context=None):
170
+ rowcount = cursor.executemany(statement, parameters)
171
+ if context is not None:
172
+ context._rowcount = rowcount
173
+
174
+ def _check_unicode_returns(self, connection):
175
+ # work around issue fixed in
176
+ # https://github.com/farcepest/MySQLdb1/commit/cd44524fef63bd3fcb71947392326e9742d520e8
177
+ # specific issue w/ the utf8mb4_bin collation and unicode returns
178
+
179
+ collation = connection.exec_driver_sql(
180
+ "show collation where %s = 'utf8mb4' and %s = 'utf8mb4_bin'"
181
+ % (
182
+ self.identifier_preparer.quote("Charset"),
183
+ self.identifier_preparer.quote("Collation"),
184
+ )
185
+ ).scalar()
186
+ has_utf8mb4_bin = self.server_version_info > (5,) and collation
187
+ if has_utf8mb4_bin:
188
+ additional_tests = [
189
+ sql.collate(
190
+ sql.cast(
191
+ sql.literal_column("'test collated returns'"),
192
+ TEXT(charset="utf8mb4"),
193
+ ),
194
+ "utf8mb4_bin",
195
+ )
196
+ ]
197
+ else:
198
+ additional_tests = []
199
+ return super()._check_unicode_returns(connection, additional_tests)
200
+
201
+ def create_connect_args(self, url, _translate_args=None):
202
+ if _translate_args is None:
203
+ _translate_args = dict(
204
+ database="db", username="user", password="passwd"
205
+ )
206
+
207
+ opts = url.translate_connect_args(**_translate_args)
208
+ opts.update(url.query)
209
+
210
+ util.coerce_kw_type(opts, "compress", bool)
211
+ util.coerce_kw_type(opts, "connect_timeout", int)
212
+ util.coerce_kw_type(opts, "read_timeout", int)
213
+ util.coerce_kw_type(opts, "write_timeout", int)
214
+ util.coerce_kw_type(opts, "client_flag", int)
215
+ util.coerce_kw_type(opts, "local_infile", bool)
216
+ # Note: using either of the below will cause all strings to be
217
+ # returned as Unicode, both in raw SQL operations and with column
218
+ # types like String and MSString.
219
+ util.coerce_kw_type(opts, "use_unicode", bool)
220
+ util.coerce_kw_type(opts, "charset", str)
221
+
222
+ # Rich values 'cursorclass' and 'conv' are not supported via
223
+ # query string.
224
+
225
+ ssl = {}
226
+ keys = [
227
+ ("ssl_ca", str),
228
+ ("ssl_key", str),
229
+ ("ssl_cert", str),
230
+ ("ssl_capath", str),
231
+ ("ssl_cipher", str),
232
+ ("ssl_check_hostname", bool),
233
+ ]
234
+ for key, kw_type in keys:
235
+ if key in opts:
236
+ ssl[key[4:]] = opts[key]
237
+ util.coerce_kw_type(ssl, key[4:], kw_type)
238
+ del opts[key]
239
+ if ssl:
240
+ opts["ssl"] = ssl
241
+
242
+ # FOUND_ROWS must be set in CLIENT_FLAGS to enable
243
+ # supports_sane_rowcount.
244
+ client_flag = opts.get("client_flag", 0)
245
+
246
+ client_flag_found_rows = self._found_rows_client_flag()
247
+ if client_flag_found_rows is not None:
248
+ client_flag |= client_flag_found_rows
249
+ opts["client_flag"] = client_flag
250
+ return [[], opts]
251
+
252
+ def _found_rows_client_flag(self):
253
+ if self.dbapi is not None:
254
+ try:
255
+ CLIENT_FLAGS = __import__(
256
+ self.dbapi.__name__ + ".constants.CLIENT"
257
+ ).constants.CLIENT
258
+ except (AttributeError, ImportError):
259
+ return None
260
+ else:
261
+ return CLIENT_FLAGS.FOUND_ROWS
262
+ else:
263
+ return None
264
+
265
+ def _extract_error_code(self, exception):
266
+ return exception.args[0]
267
+
268
+ def _detect_charset(self, connection):
269
+ """Sniff out the character set in use for connection results."""
270
+
271
+ try:
272
+ # note: the SQL here would be
273
+ # "SHOW VARIABLES LIKE 'character_set%%'"
274
+ cset_name = connection.connection.character_set_name
275
+ except AttributeError:
276
+ util.warn(
277
+ "No 'character_set_name' can be detected with "
278
+ "this MySQL-Python version; "
279
+ "please upgrade to a recent version of MySQL-Python. "
280
+ "Assuming latin1."
281
+ )
282
+ return "latin1"
283
+ else:
284
+ return cset_name()
285
+
286
+ def get_isolation_level_values(self, dbapi_connection):
287
+ return (
288
+ "SERIALIZABLE",
289
+ "READ UNCOMMITTED",
290
+ "READ COMMITTED",
291
+ "REPEATABLE READ",
292
+ "AUTOCOMMIT",
293
+ )
294
+
295
+ def set_isolation_level(self, dbapi_connection, level):
296
+ if level == "AUTOCOMMIT":
297
+ dbapi_connection.autocommit(True)
298
+ else:
299
+ dbapi_connection.autocommit(False)
300
+ super().set_isolation_level(dbapi_connection, level)
301
+
302
+
303
+ dialect = MySQLDialect_mysqldb
@@ -0,0 +1,110 @@
1
+ # dialects/mysql/provision.py
2
+ # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+ from ... import exc
10
+ from ...testing.provision import configure_follower
11
+ from ...testing.provision import create_db
12
+ from ...testing.provision import drop_db
13
+ from ...testing.provision import generate_driver_url
14
+ from ...testing.provision import temp_table_keyword_args
15
+ from ...testing.provision import upsert
16
+
17
+
18
+ @generate_driver_url.for_db("mysql", "mariadb")
19
+ def generate_driver_url(url, driver, query_str):
20
+ backend = url.get_backend_name()
21
+
22
+ # NOTE: at the moment, tests are running mariadbconnector
23
+ # against both mariadb and mysql backends. if we want this to be
24
+ # limited, do the decision making here to reject a "mysql+mariadbconnector"
25
+ # URL. Optionally also re-enable the module level
26
+ # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include
27
+ # a unit and/or functional test.
28
+
29
+ # all the Jenkins tests have been running mysqlclient Python library
30
+ # built against mariadb client drivers for years against all MySQL /
31
+ # MariaDB versions going back to MySQL 5.6, currently they can talk
32
+ # to MySQL databases without problems.
33
+
34
+ if backend == "mysql":
35
+ dialect_cls = url.get_dialect()
36
+ if dialect_cls._is_mariadb_from_url(url):
37
+ backend = "mariadb"
38
+
39
+ new_url = url.set(
40
+ drivername="%s+%s" % (backend, driver)
41
+ ).update_query_string(query_str)
42
+
43
+ if driver == "mariadbconnector":
44
+ new_url = new_url.difference_update_query(["charset"])
45
+
46
+ try:
47
+ new_url.get_dialect()
48
+ except exc.NoSuchModuleError:
49
+ return None
50
+ else:
51
+ return new_url
52
+
53
+
54
+ @create_db.for_db("mysql", "mariadb")
55
+ def _mysql_create_db(cfg, eng, ident):
56
+ with eng.begin() as conn:
57
+ try:
58
+ _mysql_drop_db(cfg, conn, ident)
59
+ except Exception:
60
+ pass
61
+
62
+ with eng.begin() as conn:
63
+ conn.exec_driver_sql(
64
+ "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident
65
+ )
66
+ conn.exec_driver_sql(
67
+ "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident
68
+ )
69
+ conn.exec_driver_sql(
70
+ "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident
71
+ )
72
+
73
+
74
+ @configure_follower.for_db("mysql", "mariadb")
75
+ def _mysql_configure_follower(config, ident):
76
+ config.test_schema = "%s_test_schema" % ident
77
+ config.test_schema_2 = "%s_test_schema_2" % ident
78
+
79
+
80
+ @drop_db.for_db("mysql", "mariadb")
81
+ def _mysql_drop_db(cfg, eng, ident):
82
+ with eng.begin() as conn:
83
+ conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident)
84
+ conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident)
85
+ conn.exec_driver_sql("DROP DATABASE %s" % ident)
86
+
87
+
88
+ @temp_table_keyword_args.for_db("mysql", "mariadb")
89
+ def _mysql_temp_table_keyword_args(cfg, eng):
90
+ return {"prefixes": ["TEMPORARY"]}
91
+
92
+
93
+ @upsert.for_db("mariadb")
94
+ def _upsert(
95
+ cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False
96
+ ):
97
+ from sqlalchemy.dialects.mysql import insert
98
+
99
+ stmt = insert(table)
100
+
101
+ if set_lambda:
102
+ stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted))
103
+ else:
104
+ pk1 = table.primary_key.c[0]
105
+ stmt = stmt.on_duplicate_key_update({pk1.key: pk1})
106
+
107
+ stmt = stmt.returning(
108
+ *returning, sort_by_parameter_order=sort_by_parameter_order
109
+ )
110
+ return stmt
@@ -0,0 +1,137 @@
1
+ # dialects/mysql/pymysql.py
2
+ # Copyright (C) 2005-2024 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
+ r"""
11
+
12
+ .. dialect:: mysql+pymysql
13
+ :name: PyMySQL
14
+ :dbapi: pymysql
15
+ :connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
16
+ :url: https://pymysql.readthedocs.io/
17
+
18
+ Unicode
19
+ -------
20
+
21
+ Please see :ref:`mysql_unicode` for current recommendations on unicode
22
+ handling.
23
+
24
+ .. _pymysql_ssl:
25
+
26
+ SSL Connections
27
+ ------------------
28
+
29
+ The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb,
30
+ described at :ref:`mysqldb_ssl`. See that section for additional examples.
31
+
32
+ If the server uses an automatically-generated certificate that is self-signed
33
+ or does not match the host name (as seen from the client), it may also be
34
+ necessary to indicate ``ssl_check_hostname=false`` in PyMySQL::
35
+
36
+ connection_uri = (
37
+ "mysql+pymysql://scott:tiger@192.168.0.134/test"
38
+ "?ssl_ca=/home/gord/client-ssl/ca.pem"
39
+ "&ssl_cert=/home/gord/client-ssl/client-cert.pem"
40
+ "&ssl_key=/home/gord/client-ssl/client-key.pem"
41
+ "&ssl_check_hostname=false"
42
+ )
43
+
44
+
45
+ MySQL-Python Compatibility
46
+ --------------------------
47
+
48
+ The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver,
49
+ and targets 100% compatibility. Most behavioral notes for MySQL-python apply
50
+ to the pymysql driver as well.
51
+
52
+ """ # noqa
53
+
54
+ from .mysqldb import MySQLDialect_mysqldb
55
+ from ...util import langhelpers
56
+
57
+
58
+ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
59
+ driver = "pymysql"
60
+ supports_statement_cache = True
61
+
62
+ description_encoding = None
63
+
64
+ @langhelpers.memoized_property
65
+ def supports_server_side_cursors(self):
66
+ try:
67
+ cursors = __import__("pymysql.cursors").cursors
68
+ self._sscursor = cursors.SSCursor
69
+ return True
70
+ except (ImportError, AttributeError):
71
+ return False
72
+
73
+ @classmethod
74
+ def import_dbapi(cls):
75
+ return __import__("pymysql")
76
+
77
+ @langhelpers.memoized_property
78
+ def _send_false_to_ping(self):
79
+ """determine if pymysql has deprecated, changed the default of,
80
+ or removed the 'reconnect' argument of connection.ping().
81
+
82
+ See #10492 and
83
+ https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971
84
+ for background.
85
+
86
+ """ # noqa: E501
87
+
88
+ try:
89
+ Connection = __import__(
90
+ "pymysql.connections"
91
+ ).connections.Connection
92
+ except (ImportError, AttributeError):
93
+ return True
94
+ else:
95
+ insp = langhelpers.get_callable_argspec(Connection.ping)
96
+ try:
97
+ reconnect_arg = insp.args[1]
98
+ except IndexError:
99
+ return False
100
+ else:
101
+ return reconnect_arg == "reconnect" and (
102
+ not insp.defaults or insp.defaults[0] is not False
103
+ )
104
+
105
+ def do_ping(self, dbapi_connection):
106
+ if self._send_false_to_ping:
107
+ dbapi_connection.ping(False)
108
+ else:
109
+ dbapi_connection.ping()
110
+
111
+ return True
112
+
113
+ def create_connect_args(self, url, _translate_args=None):
114
+ if _translate_args is None:
115
+ _translate_args = dict(username="user")
116
+ return super().create_connect_args(
117
+ url, _translate_args=_translate_args
118
+ )
119
+
120
+ def is_disconnect(self, e, connection, cursor):
121
+ if super().is_disconnect(e, connection, cursor):
122
+ return True
123
+ elif isinstance(e, self.dbapi.Error):
124
+ str_e = str(e).lower()
125
+ return (
126
+ "already closed" in str_e or "connection was killed" in str_e
127
+ )
128
+ else:
129
+ return False
130
+
131
+ def _extract_error_code(self, exception):
132
+ if isinstance(exception.args[0], Exception):
133
+ exception = exception.args[0]
134
+ return exception.args[0]
135
+
136
+
137
+ dialect = MySQLDialect_pymysql
@@ -0,0 +1,138 @@
1
+ # dialects/mysql/pyodbc.py
2
+ # Copyright (C) 2005-2024 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
+ r"""
11
+
12
+
13
+ .. dialect:: mysql+pyodbc
14
+ :name: PyODBC
15
+ :dbapi: pyodbc
16
+ :connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
17
+ :url: https://pypi.org/project/pyodbc/
18
+
19
+ .. note::
20
+
21
+ The PyODBC for MySQL dialect is **not tested as part of
22
+ SQLAlchemy's continuous integration**.
23
+ The recommended MySQL dialects are mysqlclient and PyMySQL.
24
+ However, if you want to use the mysql+pyodbc dialect and require
25
+ full support for ``utf8mb4`` characters (including supplementary
26
+ characters like emoji) be sure to use a current release of
27
+ MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode")
28
+ version of the driver in your DSN or connection string.
29
+
30
+ Pass through exact pyodbc connection string::
31
+
32
+ import urllib
33
+ connection_string = (
34
+ 'DRIVER=MySQL ODBC 8.0 ANSI Driver;'
35
+ 'SERVER=localhost;'
36
+ 'PORT=3307;'
37
+ 'DATABASE=mydb;'
38
+ 'UID=root;'
39
+ 'PWD=(whatever);'
40
+ 'charset=utf8mb4;'
41
+ )
42
+ params = urllib.parse.quote_plus(connection_string)
43
+ connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params
44
+
45
+ """ # noqa
46
+
47
+ import re
48
+
49
+ from .base import MySQLDialect
50
+ from .base import MySQLExecutionContext
51
+ from .types import TIME
52
+ from ... import exc
53
+ from ... import util
54
+ from ...connectors.pyodbc import PyODBCConnector
55
+ from ...sql.sqltypes import Time
56
+
57
+
58
+ class _pyodbcTIME(TIME):
59
+ def result_processor(self, dialect, coltype):
60
+ def process(value):
61
+ # pyodbc returns a datetime.time object; no need to convert
62
+ return value
63
+
64
+ return process
65
+
66
+
67
+ class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
68
+ def get_lastrowid(self):
69
+ cursor = self.create_cursor()
70
+ cursor.execute("SELECT LAST_INSERT_ID()")
71
+ lastrowid = cursor.fetchone()[0]
72
+ cursor.close()
73
+ return lastrowid
74
+
75
+
76
+ class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
77
+ supports_statement_cache = True
78
+ colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME})
79
+ supports_unicode_statements = True
80
+ execution_ctx_cls = MySQLExecutionContext_pyodbc
81
+
82
+ pyodbc_driver_name = "MySQL"
83
+
84
+ def _detect_charset(self, connection):
85
+ """Sniff out the character set in use for connection results."""
86
+
87
+ # Prefer 'character_set_results' for the current connection over the
88
+ # value in the driver. SET NAMES or individual variable SETs will
89
+ # change the charset without updating the driver's view of the world.
90
+ #
91
+ # If it's decided that issuing that sort of SQL leaves you SOL, then
92
+ # this can prefer the driver value.
93
+
94
+ # set this to None as _fetch_setting attempts to use it (None is OK)
95
+ self._connection_charset = None
96
+ try:
97
+ value = self._fetch_setting(connection, "character_set_client")
98
+ if value:
99
+ return value
100
+ except exc.DBAPIError:
101
+ pass
102
+
103
+ util.warn(
104
+ "Could not detect the connection character set. "
105
+ "Assuming latin1."
106
+ )
107
+ return "latin1"
108
+
109
+ def _get_server_version_info(self, connection):
110
+ return MySQLDialect._get_server_version_info(self, connection)
111
+
112
+ def _extract_error_code(self, exception):
113
+ m = re.compile(r"\((\d+)\)").search(str(exception.args))
114
+ c = m.group(1)
115
+ if c:
116
+ return int(c)
117
+ else:
118
+ return None
119
+
120
+ def on_connect(self):
121
+ super_ = super().on_connect()
122
+
123
+ def on_connect(conn):
124
+ if super_ is not None:
125
+ super_(conn)
126
+
127
+ # declare Unicode encoding for pyodbc as per
128
+ # https://github.com/mkleehammer/pyodbc/wiki/Unicode
129
+ pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR
130
+ pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR
131
+ conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8")
132
+ conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8")
133
+ conn.setencoding(encoding="utf-8")
134
+
135
+ return on_connect
136
+
137
+
138
+ dialect = MySQLDialect_pyodbc