Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/connectors/mxodbc.py: 32%
76 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# connectors/mxodbc.py
2# Copyright (C) 2005-2022 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
8"""
9Provide a SQLALchemy connector for the eGenix mxODBC commercial
10Python adapter for ODBC. This is not a free product, but eGenix
11provides SQLAlchemy with a license for use in continuous integration
12testing.
14This has been tested for use with mxODBC 3.1.2 on SQL Server 2005
15and 2008, using the SQL Server Native driver. However, it is
16possible for this to be used on other database platforms.
18For more info on mxODBC, see https://www.egenix.com/
20.. deprecated:: 1.4 The mxODBC DBAPI is deprecated and will be removed
21 in a future version. Please use one of the supported DBAPIs to
22 connect to mssql.
24"""
26import re
27import sys
28import warnings
30from . import Connector
31from ..util import warn_deprecated
34class MxODBCConnector(Connector):
35 driver = "mxodbc"
37 supports_sane_multi_rowcount = False
38 supports_unicode_statements = True
39 supports_unicode_binds = True
41 supports_native_decimal = True
43 @classmethod
44 def dbapi(cls):
45 # this classmethod will normally be replaced by an instance
46 # attribute of the same name, so this is normally only called once.
47 cls._load_mx_exceptions()
48 platform = sys.platform
49 if platform == "win32":
50 from mx.ODBC import Windows as Module
51 # this can be the string "linux2", and possibly others
52 elif "linux" in platform:
53 from mx.ODBC import unixODBC as Module
54 elif platform == "darwin":
55 from mx.ODBC import iODBC as Module
56 else:
57 raise ImportError("Unrecognized platform for mxODBC import")
59 warn_deprecated(
60 "The mxODBC DBAPI is deprecated and will be removed"
61 "in a future version. Please use one of the supported DBAPIs to"
62 "connect to mssql.",
63 version="1.4",
64 )
65 return Module
67 @classmethod
68 def _load_mx_exceptions(cls):
69 """Import mxODBC exception classes into the module namespace,
70 as if they had been imported normally. This is done here
71 to avoid requiring all SQLAlchemy users to install mxODBC.
72 """
73 global InterfaceError, ProgrammingError
74 from mx.ODBC import InterfaceError
75 from mx.ODBC import ProgrammingError
77 def on_connect(self):
78 def connect(conn):
79 conn.stringformat = self.dbapi.MIXED_STRINGFORMAT
80 conn.datetimeformat = self.dbapi.PYDATETIME_DATETIMEFORMAT
81 conn.decimalformat = self.dbapi.DECIMAL_DECIMALFORMAT
82 conn.errorhandler = self._error_handler()
84 return connect
86 def _error_handler(self):
87 """Return a handler that adjusts mxODBC's raised Warnings to
88 emit Python standard warnings.
89 """
90 from mx.ODBC.Error import Warning as MxOdbcWarning
92 def error_handler(connection, cursor, errorclass, errorvalue):
93 if issubclass(errorclass, MxOdbcWarning):
94 errorclass.__bases__ = (Warning,)
95 warnings.warn(
96 message=str(errorvalue), category=errorclass, stacklevel=2
97 )
98 else:
99 raise errorclass(errorvalue)
101 return error_handler
103 def create_connect_args(self, url):
104 r"""Return a tuple of \*args, \**kwargs for creating a connection.
106 The mxODBC 3.x connection constructor looks like this:
108 connect(dsn, user='', password='',
109 clear_auto_commit=1, errorhandler=None)
111 This method translates the values in the provided URI
112 into args and kwargs needed to instantiate an mxODBC Connection.
114 The arg 'errorhandler' is not used by SQLAlchemy and will
115 not be populated.
117 """
118 opts = url.translate_connect_args(username="user")
119 opts.update(url.query)
120 args = opts.pop("host")
121 opts.pop("port", None)
122 opts.pop("database", None)
123 return (args,), opts
125 def is_disconnect(self, e, connection, cursor):
126 # TODO: eGenix recommends checking connection.closed here
127 # Does that detect dropped connections ?
128 if isinstance(e, self.dbapi.ProgrammingError):
129 return "connection already closed" in str(e)
130 elif isinstance(e, self.dbapi.Error):
131 return "[08S01]" in str(e)
132 else:
133 return False
135 def _get_server_version_info(self, connection):
136 # eGenix suggests using conn.dbms_version instead
137 # of what we're doing here
138 dbapi_con = connection.connection
139 version = []
140 r = re.compile(r"[.\-]")
141 # 18 == pyodbc.SQL_DBMS_VER
142 for n in r.split(dbapi_con.getinfo(18)[1]):
143 try:
144 version.append(int(n))
145 except ValueError:
146 version.append(n)
147 return tuple(version)
149 def _get_direct(self, context):
150 if context:
151 native_odbc_execute = context.execution_options.get(
152 "native_odbc_execute", "auto"
153 )
154 # default to direct=True in all cases, is more generally
155 # compatible especially with SQL Server
156 return False if native_odbc_execute is True else True
157 else:
158 return True
160 def do_executemany(self, cursor, statement, parameters, context=None):
161 cursor.executemany(
162 statement, parameters, direct=self._get_direct(context)
163 )
165 def do_execute(self, cursor, statement, parameters, context=None):
166 cursor.execute(statement, parameters, direct=self._get_direct(context))