From fc903b21e15695233a6ede89d7e7a1a217214533 Mon Sep 17 00:00:00 2001 From: Vilnis Termanis Date: Mon, 25 Jul 2016 16:11:00 +0100 Subject: [PATCH] Warning behaviour improvements - Warnings now propagated using warnings module when get_warnings is set (raise_on_warnings behaves the same as before) - Do not disable get_warnings if raise_on_warnings is explicitly set to False - errors.Warning subclasses Warning - errors.get_mysql_exception can now return warnings where appropriate - cursor_[cext].handle_warnings now also emits warnings, not just exceptions - Ignore "unread result" exception in cursor[_cext].close() - connection.close() does not leak socket if cmd_quit fails --- lib/mysql/connector/abstracts.py | 4 +++- lib/mysql/connector/connection.py | 11 ++++++----- lib/mysql/connector/cursor.py | 29 +++++++++++++++++++++-------- lib/mysql/connector/cursor_cext.py | 27 +++++++++++++++++++++------ lib/mysql/connector/errors.py | 10 +++++++--- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/lib/mysql/connector/abstracts.py b/lib/mysql/connector/abstracts.py index 6bbd2c1..745c16e 100644 --- a/lib/mysql/connector/abstracts.py +++ b/lib/mysql/connector/abstracts.py @@ -559,7 +559,9 @@ def raise_on_warnings(self, value): if not isinstance(value, bool): raise ValueError("Expected a boolean type") self._raise_on_warnings = value - self._get_warnings = value + # don't disable warning retrieval if raising explicitly disabled + if value: + self._get_warnings = value @property diff --git a/lib/mysql/connector/connection.py b/lib/mysql/connector/connection.py index 453c73e..7c06766 100644 --- a/lib/mysql/connector/connection.py +++ b/lib/mysql/connector/connection.py @@ -229,11 +229,12 @@ def close(self): if not self._socket: return - try: - self.cmd_quit() - self._socket.close_connection() - except (AttributeError, errors.Error): - pass # Getting an exception would mean we are disconnected. + for action in (self.cmd_quit, self._socket.close_connection): + try: + action() + except (AttributeError, errors.Error): + pass # Getting an exception would mean we are disconnected. + disconnect = close def _send_cmd(self, command, argument=None, packet_number=0, packet=None, diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py index 7cc7362..352ed2a 100644 --- a/lib/mysql/connector/cursor.py +++ b/lib/mysql/connector/cursor.py @@ -26,6 +26,7 @@ from collections import namedtuple import re +import warnings import weakref from . import errors @@ -341,7 +342,12 @@ def close(self): if self._connection is None: return False - self._connection.handle_unread_result() + try: + self._connection.handle_unread_result() + except errors.InternalError as ex: + # Don't hide other exceptions which might have occured on closing + if ex.msg != "Unread result found": + raise self._reset_result() self._connection = None @@ -399,9 +405,6 @@ def _handle_noresultset(self, res): "Failed handling non-resultset; {0}".format(err)) self._handle_warnings() - if self._connection.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception( - self._warnings[0][1], self._warnings[0][2]) def _handle_resultset(self): """Handles result set @@ -773,9 +776,22 @@ def _fetch_warnings(self): return None def _handle_warnings(self): - """Handle possible warnings after all results are consumed""" + """Handle possible warnings after all results are consumed + + Also raises exceptions if raise_on_warnings is set + """ if self._connection.get_warnings is True and self._warning_count: self._warnings = self._fetch_warnings() + # should not happen given self._warning_count is set? + if not self._warnings: + return + + warning = errors.get_mysql_exception( + self._warnings[0][1], self._warnings[0][2], warning=True) + if self._connection.raise_on_warnings: + raise warning + else: + warnings.warn(warning, stacklevel=4) def _handle_eof(self, eof): """Handle EOF packet""" @@ -783,9 +799,6 @@ def _handle_eof(self, eof): self._nextrow = (None, None) self._warning_count = eof['warning_count'] self._handle_warnings() - if self._connection.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception( - self._warnings[0][1], self._warnings[0][2]) def _fetch_row(self): """Returns the next row in the result set diff --git a/lib/mysql/connector/cursor_cext.py b/lib/mysql/connector/cursor_cext.py index 4dd6e9a..c572dc7 100644 --- a/lib/mysql/connector/cursor_cext.py +++ b/lib/mysql/connector/cursor_cext.py @@ -26,6 +26,7 @@ from collections import namedtuple import re +import warnings import weakref from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract @@ -144,9 +145,22 @@ def _fetch_warnings(self): return None def _handle_warnings(self): - """Handle possible warnings after all results are consumed""" + """Handle possible warnings after all results are consumed + + Also raises exceptions if raise_on_warnings is set + """ if self._cnx.get_warnings is True and self._warning_count: self._warnings = self._fetch_warnings() + # should not happen given self._warning_count is set? + if not self._warnings: + return + + warning = errors.get_mysql_exception( + *self._warnings[0][1:3], warning=True) + if self._cnx.raise_on_warnings: + raise warning + else: + warnings.warn(warning, stacklevel=4) def _handle_result(self, result): """Handles the result after statement execution""" @@ -160,8 +174,6 @@ def _handle_result(self, result): self._affected_rows = result['affected_rows'] self._rowcount = -1 self._handle_warnings() - if self._cnx.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception(*self._warnings[0][1:3]) def _handle_resultset(self): """Handle a result set""" @@ -174,8 +186,6 @@ def _handle_eof(self): """ self._warning_count = self._cnx.warning_count self._handle_warnings() - if self._cnx.raise_on_warnings is True and self._warnings: - raise errors.get_mysql_exception(*self._warnings[0][1:3]) if not self._cnx.more_results: self._cnx.free_result() @@ -389,7 +399,12 @@ def close(self): if not self._cnx: return False - self._cnx.handle_unread_result() + try: + self._cnx.handle_unread_result() + except errors.InternalError as ex: + # Don't hide other exceptions which might have occured on closing + if ex.msg != "Unread result found": + raise self._warnings = None self._cnx = None return True diff --git a/lib/mysql/connector/errors.py b/lib/mysql/connector/errors.py index ea354f2..0ecb890 100644 --- a/lib/mysql/connector/errors.py +++ b/lib/mysql/connector/errors.py @@ -98,7 +98,7 @@ def custom_error_exception(error=None, exception=None): return _CUSTOM_ERROR_EXCEPTIONS -def get_mysql_exception(errno, msg=None, sqlstate=None): +def get_mysql_exception(errno, msg=None, sqlstate=None, warning=False): """Get the exception matching the MySQL error This function will return an exception based on the SQLState. The given @@ -124,7 +124,11 @@ def get_mysql_exception(errno, msg=None, sqlstate=None): pass if not sqlstate: - return DatabaseError(msg=msg, errno=errno) + if warning: + # match order of Warning arguments with Error + return Warning(errno, msg) + else: + return DatabaseError(msg=msg, errno=errno) try: return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]]( @@ -206,7 +210,7 @@ def __str__(self): return self._full_msg -class Warning(Exception): # pylint: disable=W0622 +class Warning(Warning, Exception): # pylint: disable=W0622 """Exception for important warnings""" pass