Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py : 39%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mysql/mysqlconnector.py
2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php
8r"""
9.. dialect:: mysql+mysqlconnector
10 :name: MySQL Connector/Python
11 :dbapi: myconnpy
12 :connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
13 :url: https://pypi.org/project/mysql-connector-python/
15.. note::
17 The MySQL Connector/Python DBAPI has had many issues since its release,
18 some of which may remain unresolved, and the mysqlconnector dialect is
19 **not tested as part of SQLAlchemy's continuous integration**.
20 The recommended MySQL dialects are mysqlclient and PyMySQL.
22""" # noqa
24import re
26from .base import BIT
27from .base import MySQLCompiler
28from .base import MySQLDialect
29from .base import MySQLExecutionContext
30from .base import MySQLIdentifierPreparer
31from ... import processors
32from ... import util
35class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
36 def get_lastrowid(self):
37 return self.cursor.lastrowid
40class MySQLCompiler_mysqlconnector(MySQLCompiler):
41 def visit_mod_binary(self, binary, operator, **kw):
42 if self.dialect._mysqlconnector_double_percents:
43 return (
44 self.process(binary.left, **kw)
45 + " %% "
46 + self.process(binary.right, **kw)
47 )
48 else:
49 return (
50 self.process(binary.left, **kw)
51 + " % "
52 + self.process(binary.right, **kw)
53 )
55 def post_process_text(self, text):
56 if self.dialect._mysqlconnector_double_percents:
57 return text.replace("%", "%%")
58 else:
59 return text
61 def escape_literal_column(self, text):
62 if self.dialect._mysqlconnector_double_percents:
63 return text.replace("%", "%%")
64 else:
65 return text
68class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
69 @property
70 def _double_percents(self):
71 return self.dialect._mysqlconnector_double_percents
73 @_double_percents.setter
74 def _double_percents(self, value):
75 pass
77 def _escape_identifier(self, value):
78 value = value.replace(self.escape_quote, self.escape_to_quote)
79 if self.dialect._mysqlconnector_double_percents:
80 return value.replace("%", "%%")
81 else:
82 return value
85class _myconnpyBIT(BIT):
86 def result_processor(self, dialect, coltype):
87 """MySQL-connector already converts mysql bits, so."""
89 return None
92class MySQLDialect_mysqlconnector(MySQLDialect):
93 driver = "mysqlconnector"
95 supports_unicode_binds = True
97 supports_sane_rowcount = True
98 supports_sane_multi_rowcount = True
100 supports_native_decimal = True
102 default_paramstyle = "format"
103 execution_ctx_cls = MySQLExecutionContext_mysqlconnector
104 statement_compiler = MySQLCompiler_mysqlconnector
106 preparer = MySQLIdentifierPreparer_mysqlconnector
108 colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT})
110 def __init__(self, *arg, **kw):
111 super(MySQLDialect_mysqlconnector, self).__init__(*arg, **kw)
113 # hack description encoding since mysqlconnector randomly
114 # returns bytes or not
115 self._description_decoder = (
116 processors.to_conditional_unicode_processor_factory
117 )(self.description_encoding)
119 def _check_unicode_description(self, connection):
120 # hack description encoding since mysqlconnector randomly
121 # returns bytes or not
122 return False
124 @property
125 def description_encoding(self):
126 # total guess
127 return "latin-1"
129 @util.memoized_property
130 def supports_unicode_statements(self):
131 return util.py3k or self._mysqlconnector_version_info > (2, 0)
133 @classmethod
134 def dbapi(cls):
135 from mysql import connector
137 return connector
139 def do_ping(self, dbapi_connection):
140 try:
141 dbapi_connection.ping(False)
142 except self.dbapi.Error as err:
143 if self.is_disconnect(err, dbapi_connection, None):
144 return False
145 else:
146 raise
147 else:
148 return True
150 def create_connect_args(self, url):
151 opts = url.translate_connect_args(username="user")
153 opts.update(url.query)
155 util.coerce_kw_type(opts, "allow_local_infile", bool)
156 util.coerce_kw_type(opts, "autocommit", bool)
157 util.coerce_kw_type(opts, "buffered", bool)
158 util.coerce_kw_type(opts, "compress", bool)
159 util.coerce_kw_type(opts, "connection_timeout", int)
160 util.coerce_kw_type(opts, "connect_timeout", int)
161 util.coerce_kw_type(opts, "consume_results", bool)
162 util.coerce_kw_type(opts, "force_ipv6", bool)
163 util.coerce_kw_type(opts, "get_warnings", bool)
164 util.coerce_kw_type(opts, "pool_reset_session", bool)
165 util.coerce_kw_type(opts, "pool_size", int)
166 util.coerce_kw_type(opts, "raise_on_warnings", bool)
167 util.coerce_kw_type(opts, "raw", bool)
168 util.coerce_kw_type(opts, "ssl_verify_cert", bool)
169 util.coerce_kw_type(opts, "use_pure", bool)
170 util.coerce_kw_type(opts, "use_unicode", bool)
172 # unfortunately, MySQL/connector python refuses to release a
173 # cursor without reading fully, so non-buffered isn't an option
174 opts.setdefault("buffered", True)
176 # FOUND_ROWS must be set in ClientFlag to enable
177 # supports_sane_rowcount.
178 if self.dbapi is not None:
179 try:
180 from mysql.connector.constants import ClientFlag
182 client_flags = opts.get(
183 "client_flags", ClientFlag.get_default()
184 )
185 client_flags |= ClientFlag.FOUND_ROWS
186 opts["client_flags"] = client_flags
187 except Exception:
188 pass
189 return [[], opts]
191 @util.memoized_property
192 def _mysqlconnector_version_info(self):
193 if self.dbapi and hasattr(self.dbapi, "__version__"):
194 m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
195 if m:
196 return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
198 @util.memoized_property
199 def _mysqlconnector_double_percents(self):
200 return not util.py3k and self._mysqlconnector_version_info < (2, 0)
202 def _detect_charset(self, connection):
203 return connection.connection.charset
205 def _extract_error_code(self, exception):
206 return exception.errno
208 def is_disconnect(self, e, connection, cursor):
209 errnos = (2006, 2013, 2014, 2045, 2055, 2048)
210 exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError)
211 if isinstance(e, exceptions):
212 return (
213 e.errno in errnos
214 or "MySQL Connection not available." in str(e)
215 or "Connection to MySQL is not available" in str(e)
216 )
217 else:
218 return False
220 def _compat_fetchall(self, rp, charset=None):
221 return rp.fetchall()
223 def _compat_fetchone(self, rp, charset=None):
224 return rp.fetchone()
226 _isolation_lookup = set(
227 [
228 "SERIALIZABLE",
229 "READ UNCOMMITTED",
230 "READ COMMITTED",
231 "REPEATABLE READ",
232 "AUTOCOMMIT",
233 ]
234 )
236 def _set_isolation_level(self, connection, level):
237 if level == "AUTOCOMMIT":
238 connection.autocommit = True
239 else:
240 connection.autocommit = False
241 super(MySQLDialect_mysqlconnector, self)._set_isolation_level(
242 connection, level
243 )
246dialect = MySQLDialect_mysqlconnector