Hello community, here is the log from the commit of package python3-PyMySQL for openSUSE:Factory checked in at 2016-09-14 23:35:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-PyMySQL (Old) and /work/SRC/openSUSE:Factory/.python3-PyMySQL.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-PyMySQL" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-PyMySQL/python3-PyMySQL.changes 2016-07-28 23:46:30.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python3-PyMySQL.new/python3-PyMySQL.changes 2016-09-14 23:35:10.000000000 +0200 @@ -1,0 +2,22 @@ +Mon Sep 12 19:38:20 UTC 2016 - [email protected] + +- update to version 0.7.9: + * Fix PyMySQL stop reading rows when first column is empty string + (#513) Reverts DEPRECATE_EOF introduced in 0.7.7. + +- changes from version 0.7.8: + * Revert error message change in 0.7.7. (SQLAlchemy parses error + message, #507) + +- changes from version 0.7.7: + * Add new unicode collation (#498) + * Fix conv option is not used for encoding objects. + * Experimental support for DEPRECATE_EOF protocol. + +- changes from version 0.7.6: + * Fix SELECT JSON type cause UnicodeError + * Avoid float convertion while parsing microseconds + * Warning has number + * SSCursor supports warnings + +------------------------------------------------------------------- Old: ---- PyMySQL-0.7.5.tar.gz New: ---- PyMySQL-0.7.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-PyMySQL.spec ++++++ --- /var/tmp/diff_new_pack.I45z1z/_old 2016-09-14 23:35:11.000000000 +0200 +++ /var/tmp/diff_new_pack.I45z1z/_new 2016-09-14 23:35:11.000000000 +0200 @@ -17,7 +17,7 @@ Name: python3-PyMySQL -Version: 0.7.5 +Version: 0.7.9 Release: 0 Summary: Pure Python MySQL Driver License: MIT ++++++ PyMySQL-0.7.5.tar.gz -> PyMySQL-0.7.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/CHANGELOG new/PyMySQL-0.7.9/CHANGELOG --- old/PyMySQL-0.7.5/CHANGELOG 2016-06-28 14:18:40.000000000 +0200 +++ new/PyMySQL-0.7.9/CHANGELOG 2016-09-03 21:13:57.000000000 +0200 @@ -1,5 +1,36 @@ # Changes +## 0.7.9 + +Release date: 2016-09-03 + +* Fix PyMySQL stop reading rows when first column is empty string (#513) + Reverts DEPRECATE_EOF introduced in 0.7.7. + +## 0.7.8 + +Release date: 2016-09-01 + +* Revert error message change in 0.7.7. + (SQLAlchemy parses error message, #507) + +## 0.7.7 + +Release date: 2016-08-30 + +* Add new unicode collation (#498) +* Fix conv option is not used for encoding objects. +* Experimental support for DEPRECATE_EOF protocol. + +## 0.7.6 + +Release date: 2016-07-29 + +* Fix SELECT JSON type cause UnicodeError +* Avoid float convertion while parsing microseconds +* Warning has number +* SSCursor supports warnings + ## 0.7.5 Release date: 2016-06-28 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/PKG-INFO new/PyMySQL-0.7.9/PKG-INFO --- old/PyMySQL-0.7.5/PKG-INFO 2016-06-28 14:18:51.000000000 +0200 +++ new/PyMySQL-0.7.9/PKG-INFO 2016-09-03 21:14:48.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.5 +Version: 0.7.9 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/PyMySQL.egg-info/PKG-INFO new/PyMySQL-0.7.9/PyMySQL.egg-info/PKG-INFO --- old/PyMySQL-0.7.5/PyMySQL.egg-info/PKG-INFO 2016-06-28 14:18:50.000000000 +0200 +++ new/PyMySQL-0.7.9/PyMySQL.egg-info/PKG-INFO 2016-09-03 21:14:48.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.5 +Version: 0.7.9 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/PyMySQL.egg-info/SOURCES.txt new/PyMySQL-0.7.9/PyMySQL.egg-info/SOURCES.txt --- old/PyMySQL-0.7.5/PyMySQL.egg-info/SOURCES.txt 2016-06-28 14:18:51.000000000 +0200 +++ new/PyMySQL-0.7.9/PyMySQL.egg-info/SOURCES.txt 2016-09-03 21:14:48.000000000 +0200 @@ -10,7 +10,6 @@ PyMySQL.egg-info/PKG-INFO PyMySQL.egg-info/SOURCES.txt PyMySQL.egg-info/dependency_links.txt -PyMySQL.egg-info/pbr.json PyMySQL.egg-info/top_level.txt pymysql/__init__.py pymysql/_compat.py @@ -39,6 +38,7 @@ pymysql/tests/test_connection.py pymysql/tests/test_converters.py pymysql/tests/test_cursor.py +pymysql/tests/test_err.py pymysql/tests/test_issues.py pymysql/tests/test_load_local.py pymysql/tests/test_nextset.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/PyMySQL.egg-info/pbr.json new/PyMySQL-0.7.9/PyMySQL.egg-info/pbr.json --- old/PyMySQL-0.7.5/PyMySQL.egg-info/pbr.json 2015-02-25 10:09:23.000000000 +0100 +++ new/PyMySQL-0.7.9/PyMySQL.egg-info/pbr.json 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -{"is_release": false, "git_version": "08bac52"} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/README.rst new/PyMySQL-0.7.9/README.rst --- old/PyMySQL-0.7.5/README.rst 2016-05-24 10:05:28.000000000 +0200 +++ new/PyMySQL-0.7.9/README.rst 2016-08-27 19:59:17.000000000 +0200 @@ -15,7 +15,8 @@ PyMySQL ======= -.. contents:: +.. contents:: Table of Contents + :local: This package contains a pure-Python MySQL client library. The goal of PyMySQL is to be a drop-in replacement for MySQLdb and work on CPython, PyPy and IronPython. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/__init__.py new/PyMySQL-0.7.9/pymysql/__init__.py --- old/PyMySQL-0.7.5/pymysql/__init__.py 2016-06-28 14:18:40.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/__init__.py 2016-09-03 21:12:20.000000000 +0200 @@ -26,14 +26,16 @@ from ._compat import PY2 from .constants import FIELD_TYPE from .converters import escape_dict, escape_sequence, escape_string -from .err import Warning, Error, InterfaceError, DataError, \ - DatabaseError, OperationalError, IntegrityError, InternalError, \ - NotSupportedError, ProgrammingError, MySQLError -from .times import Date, Time, Timestamp, \ - DateFromTicks, TimeFromTicks, TimestampFromTicks +from .err import ( + Warning, Error, InterfaceError, DataError, + DatabaseError, OperationalError, IntegrityError, InternalError, + NotSupportedError, ProgrammingError, MySQLError) +from .times import ( + Date, Time, Timestamp, + DateFromTicks, TimeFromTicks, TimestampFromTicks) -VERSION = (0, 7, 5, None) +VERSION = (0, 7, 9, None) threadsafety = 1 apilevel = "2.0" paramstyle = "pyformat" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/charset.py new/PyMySQL-0.7.9/pymysql/charset.py --- old/PyMySQL-0.7.5/pymysql/charset.py 2014-08-29 13:32:54.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/charset.py 2016-08-27 19:59:17.000000000 +0200 @@ -11,6 +11,10 @@ self.id, self.name, self.collation = id, name, collation self.is_default = is_default == 'Yes' + def __repr__(self): + return "Charset(id=%s, name=%r, collation=%r)" % ( + self.id, self.name, self.collation) + @property def encoding(self): name = self.name @@ -249,6 +253,10 @@ _charsets.add(Charset(241, 'utf8mb4', 'utf8mb4_esperanto_ci', '')) _charsets.add(Charset(242, 'utf8mb4', 'utf8mb4_hungarian_ci', '')) _charsets.add(Charset(243, 'utf8mb4', 'utf8mb4_sinhala_ci', '')) +_charsets.add(Charset(244, 'utf8mb4', 'utf8mb4_german2_ci', '')) +_charsets.add(Charset(245, 'utf8mb4', 'utf8mb4_croatian_ci', '')) +_charsets.add(Charset(246, 'utf8mb4', 'utf8mb4_unicode_520_ci', '')) +_charsets.add(Charset(247, 'utf8mb4', 'utf8mb4_vietnamese_ci', '')) charset_by_name = _charsets.by_name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/connections.py new/PyMySQL-0.7.9/pymysql/connections.py --- old/PyMySQL-0.7.5/pymysql/connections.py 2016-06-28 13:27:57.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/connections.py 2016-09-03 21:04:12.000000000 +0200 @@ -88,7 +88,6 @@ FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.MEDIUM_BLOB, - FIELD_TYPE.JSON, FIELD_TYPE.STRING, FIELD_TYPE.TINY_BLOB, FIELD_TYPE.VAR_STRING, @@ -118,20 +117,18 @@ try: print("packet length:", len(data)) - print("method call[1]:", sys._getframe(1).f_code.co_name) - print("method call[2]:", sys._getframe(2).f_code.co_name) - print("method call[3]:", sys._getframe(3).f_code.co_name) - print("method call[4]:", sys._getframe(4).f_code.co_name) - print("method call[5]:", sys._getframe(5).f_code.co_name) - print("-" * 88) + for i in range(1, 6): + f = sys._getframe(i) + print("call[%d]: %s (line %d)" % (i, f.f_code.co_name, f.f_lineno)) + print("-" * 66) except ValueError: pass dump_data = [data[i:i+16] for i in range_type(0, min(len(data), 256), 16)] for d in dump_data: print(' '.join(map(lambda x: "{:02X}".format(byte2int(x)), d)) + ' ' * (16 - len(d)) + ' ' * 2 + - ' '.join(map(lambda x: "{}".format(is_ascii(x)), d))) - print("-" * 88) + ''.join(map(lambda x: "{}".format(is_ascii(x)), d))) + print("-" * 66) print() @@ -364,17 +361,18 @@ return result def is_ok_packet(self): - return self._data[0:1] == b'\0' - - def is_auth_switch_request(self): - # http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest - return self._data[0:1] == b'\xfe' + # https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html + return self._data[0:1] == b'\0' and len(self._data) >= 7 def is_eof_packet(self): # http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-EOF_Packet # Caution: \xFE may be LengthEncodedInteger. # If \xFE is LengthEncodedInteger header, 8bytes followed. - return len(self._data) < 9 and self._data[0:1] == b'\xfe' + return self._data[0:1] == b'\xfe' and len(self._data) < 9 + + def is_auth_switch_request(self): + # http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest + return self._data[0:1] == b'\xfe' def is_resultset_packet(self): field_count = ord(self._data[0:1]) @@ -407,9 +405,9 @@ def __init__(self, data, encoding): MysqlPacket.__init__(self, data, encoding) - self.__parse_field_descriptor(encoding) + self._parse_field_descriptor(encoding) - def __parse_field_descriptor(self, encoding): + def _parse_field_descriptor(self, encoding): """Parse the 'Field Descriptor' (Metadata) packet. This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0). @@ -660,7 +658,7 @@ self.encoding = charset_by_name(self.charset).encoding - client_flag |= CLIENT.CAPABILITIES | CLIENT.MULTI_STATEMENTS + client_flag |= CLIENT.CAPABILITIES if self.db: client_flag |= CLIENT.CONNECT_WITH_DB self.client_flag = client_flag @@ -1357,12 +1355,16 @@ self._read_ok_packet(ok_packet) def _check_packet_is_eof(self, packet): - if packet.is_eof_packet(): - eof_packet = EOFPacketWrapper(packet) - self.warning_count = eof_packet.warning_count - self.has_next = eof_packet.has_next - return True - return False + if not packet.is_eof_packet(): + return False + #TODO: Support CLIENT.DEPRECATE_EOF + # 1) Add DEPRECATE_EOF to CAPABILITIES + # 2) Mask CAPABILITIES with server_capabilities + # 3) if server_capabilities & CLIENT.DEPRECATE_EOF: use OKPacketWrapper instead of EOFPacketWrapper + wp = EOFPacketWrapper(packet) + self.warning_count = wp.warning_count + self.has_next = wp.has_next + return True def _read_result_packet(self, first_packet): self.field_count = first_packet.read_length_encoded_integer() @@ -1433,21 +1435,30 @@ self.fields = [] self.converters = [] use_unicode = self.connection.use_unicode + conn_encoding = self.connection.encoding description = [] + for i in range_type(self.field_count): field = self.connection._read_packet(FieldDescriptorPacket) self.fields.append(field) description.append(field.description()) field_type = field.type_code if use_unicode: - if field_type in TEXT_TYPES: - charset = charset_by_id(field.charsetnr) - if charset.is_binary: + if field_type == FIELD_TYPE.JSON: + # When SELECT from JSON column: charset = binary + # When SELECT CAST(... AS JSON): charset = connection encoding + # This behavior is different from TEXT / BLOB. + # We should decode result by connection encoding regardless charsetnr. + # See https://github.com/PyMySQL/PyMySQL/issues/488 + encoding = conn_encoding # SELECT CAST(... AS JSON) + elif field_type in TEXT_TYPES: + if field.charsetnr == 63: # binary # TEXTs with charset=binary means BINARY types. encoding = None else: - encoding = charset.encoding + encoding = conn_encoding else: + # Integers, Dates and Times, and other basic data is encoded in ascii encoding = 'ascii' else: encoding = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/constants/CLIENT.py new/PyMySQL-0.7.9/pymysql/constants/CLIENT.py --- old/PyMySQL-0.7.5/pymysql/constants/CLIENT.py 2016-05-18 11:03:00.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/constants/CLIENT.py 2016-09-03 21:04:12.000000000 +0200 @@ -19,11 +19,13 @@ PS_MULTI_RESULTS = 1 << 18 PLUGIN_AUTH = 1 << 19 PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21 -CAPABILITIES = (LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | - PROTOCOL_41 | SECURE_CONNECTION | PLUGIN_AUTH | - PLUGIN_AUTH_LENENC_CLIENT_DATA) +CAPABILITIES = ( + LONG_PASSWORD | LONG_FLAG | PROTOCOL_41 | TRANSACTIONS + | SECURE_CONNECTION | MULTI_STATEMENTS | MULTI_RESULTS + | PLUGIN_AUTH | PLUGIN_AUTH_LENENC_CLIENT_DATA) + # Not done yet CONNECT_ATTRS = 1 << 20 HANDLE_EXPIRED_PASSWORDS = 1 << 22 SESSION_TRACK = 1 << 23 -CLIENT_DEPRECATE_EOF = 1 << 24 +DEPRECATE_EOF = 1 << 24 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/constants/CR.py new/PyMySQL-0.7.9/pymysql/constants/CR.py --- old/PyMySQL-0.7.5/pymysql/constants/CR.py 2016-05-18 11:03:00.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/constants/CR.py 2016-08-29 18:14:43.000000000 +0200 @@ -1,3 +1,4 @@ +# flake8: noqa # errmsg.h CR_ERROR_FIRST = 2000 CR_UNKNOWN_ERROR = 2000 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/converters.py new/PyMySQL-0.7.9/pymysql/converters.py --- old/PyMySQL-0.7.5/pymysql/converters.py 2016-05-24 09:22:27.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/converters.py 2016-08-27 19:59:17.000000000 +0200 @@ -2,6 +2,7 @@ import datetime from decimal import Decimal +import re import time from .constants import FIELD_TYPE, FLAG @@ -145,6 +146,16 @@ def escape_struct_time(obj, mapping=None): return escape_datetime(datetime.datetime(*obj[:6])) +def _convert_second_fraction(s): + if not s: + return 0 + # Pad zeros to ensure the fraction length in microseconds + s = s.ljust(6, '0') + return int(s[:6]) + +DATETIME_RE = re.compile(r"(\d{1,4})-(\d{1,2})-(\d{1,2})[T ](\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?") + + def convert_datetime(obj): """Returns a DATETIME or TIMESTAMP column value as a datetime object: @@ -163,23 +174,20 @@ """ if not PY2 and isinstance(obj, (bytes, bytearray)): obj = obj.decode('ascii') - if ' ' in obj: - sep = ' ' - elif 'T' in obj: - sep = 'T' - else: + + m = DATETIME_RE.match(obj) + if not m: return convert_date(obj) try: - ymd, hms = obj.split(sep, 1) - usecs = '0' - if '.' in hms: - hms, usecs = hms.split('.') - usecs = float('0.' + usecs) * 1e6 - return datetime.datetime(*[ int(x) for x in ymd.split('-')+hms.split(':')+[usecs] ]) + groups = list(m.groups()) + groups[-1] = _convert_second_fraction(groups[-1]) + return datetime.datetime(*[ int(x) for x in groups ]) except ValueError: return convert_date(obj) +TIMEDELTA_RE = re.compile(r"(-)?(\d{1,3}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?") + def convert_timedelta(obj): """Returns a TIME column as a timedelta object: @@ -200,16 +208,17 @@ """ if not PY2 and isinstance(obj, (bytes, bytearray)): obj = obj.decode('ascii') + + m = TIMEDELTA_RE.match(obj) + if not m: + return None + try: - microseconds = 0 - if "." in obj: - (obj, tail) = obj.split('.') - microseconds = float('0.' + tail) * 1e6 - hours, minutes, seconds = obj.split(':') - negate = 1 - if hours.startswith("-"): - hours = hours[1:] - negate = -1 + groups = list(m.groups()) + groups[-1] = _convert_second_fraction(groups[-1]) + negate = -1 if groups[0] else 1 + hours, minutes, seconds, microseconds = groups[1:] + tdelta = datetime.timedelta( hours = int(hours), minutes = int(minutes), @@ -220,6 +229,9 @@ except ValueError: return None +TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?") + + def convert_time(obj): """Returns a TIME column as a time object: @@ -244,17 +256,21 @@ """ if not PY2 and isinstance(obj, (bytes, bytearray)): obj = obj.decode('ascii') + + m = TIME_RE.match(obj) + if not m: + return None + try: - microseconds = 0 - if "." in obj: - (obj, tail) = obj.split('.') - microseconds = float('0.' + tail) * 1e6 - hours, minutes, seconds = obj.split(':') + groups = list(m.groups()) + groups[-1] = _convert_second_fraction(groups[-1]) + hours, minutes, seconds, microseconds = groups return datetime.time(hour=int(hours), minute=int(minutes), second=int(seconds), microsecond=int(microseconds)) except ValueError: return None + def convert_date(obj): """Returns a DATE column as a date object: @@ -324,7 +340,7 @@ #def convert_bit(b): # b = "\x00" * (8 - len(b)) + b # pad w/ zeroes # return struct.unpack(">Q", b)[0] -# +# # the snippet above is right, but MySQLdb doesn't process bits, # so we shouldn't either convert_bit = through diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/cursors.py new/PyMySQL-0.7.9/pymysql/cursors.py --- old/PyMySQL-0.7.5/pymysql/cursors.py 2016-06-28 13:27:57.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/cursors.py 2016-08-29 18:14:36.000000000 +0200 @@ -5,7 +5,6 @@ import warnings from ._compat import range_type, text_type, PY2 - from . import err @@ -20,9 +19,9 @@ class Cursor(object): - ''' + """ This is the object you use to interact with the database. - ''' + """ #: Max stetement size which :meth:`executemany` generates. #: @@ -30,11 +29,13 @@ #: Default value of max_allowed_packet is 1048576. max_stmt_length = 1024000 + _defer_warnings = False + def __init__(self, connection): - ''' + """ Do not create an instance of a Cursor yourself. Call connections.Connection.cursor(). - ''' + """ self.connection = connection self.description = None self.rownumber = 0 @@ -43,11 +44,12 @@ self._executed = None self._result = None self._rows = None + self._warnings_handled = False def close(self): - ''' + """ Closing a cursor just exhausts all remaining data. - ''' + """ conn = self.connection if conn is None: return @@ -86,6 +88,9 @@ """Get the next query set""" conn = self._get_db() current_result = self._result + # for unbuffered queries warnings are only available once whole result has been read + if unbuffered: + self._show_warnings() if current_result is None or current_result is not conn._result: return None if not current_result.has_next: @@ -110,12 +115,12 @@ if isinstance(args, (tuple, list)): if PY2: args = tuple(map(ensure_bytes, args)) - return tuple(conn.escape(arg) for arg in args) + return tuple(conn.literal(arg) for arg in args) elif isinstance(args, dict): if PY2: args = dict((ensure_bytes(key), ensure_bytes(val)) for (key, val) in args.items()) - return dict((key, conn.escape(val)) for (key, val) in args.items()) + return dict((key, conn.literal(val)) for (key, val) in args.items()) else: # If it's not a dictionary let's try escaping it anyways. # Worst case it will throw a Value error @@ -268,7 +273,7 @@ return args def fetchone(self): - ''' Fetch the next row ''' + """Fetch the next row""" self._check_executed() if self._rows is None or self.rownumber >= len(self._rows): return None @@ -277,7 +282,7 @@ return result def fetchmany(self, size=None): - ''' Fetch several rows ''' + """Fetch several rows""" self._check_executed() if self._rows is None: return () @@ -287,7 +292,7 @@ return result def fetchall(self): - ''' Fetch all the rows ''' + """Fetch all the rows""" self._check_executed() if self._rows is None: return () @@ -328,14 +333,18 @@ self.description = result.description self.lastrowid = result.insert_id self._rows = result.rows + self._warnings_handled = False - if result.warning_count > 0: - self._show_warnings(conn) + if not self._defer_warnings: + self._show_warnings() - def _show_warnings(self, conn): - if self._result and self._result.has_next: + def _show_warnings(self): + if self._warnings_handled: return - ws = conn.show_warnings() + self._warnings_handled = True + if self._result and (self._result.has_next or not self._result.warning_count): + return + ws = self._get_db().show_warnings() if ws is None: return for w in ws: @@ -343,7 +352,7 @@ if PY2: if isinstance(msg, unicode): msg = msg.encode('utf-8', 'replace') - warnings.warn(str(msg), err.Warning, 4) + warnings.warn(err.Warning(*w[1:3]), stacklevel=4) def __iter__(self): return iter(self.fetchone, None) @@ -404,6 +413,8 @@ possible to scroll backwards, as only the current row is held in memory. """ + _defer_warnings = True + def _conv_row(self, row): return row @@ -432,14 +443,15 @@ return self._nextset(unbuffered=True) def read_next(self): - """ Read next row """ + """Read next row""" return self._conv_row(self._result._read_rowdata_packet_unbuffered()) def fetchone(self): - """ Fetch next row """ + """Fetch next row""" self._check_executed() row = self.read_next() if row is None: + self._show_warnings() return None self.rownumber += 1 return row @@ -464,7 +476,7 @@ return self.fetchall_unbuffered() def fetchmany(self, size=None): - """ Fetch many """ + """Fetch many""" self._check_executed() if size is None: size = self.arraysize @@ -473,6 +485,7 @@ for i in range_type(size): row = self.read_next() if row is None: + self._show_warnings() break rows.append(row) self.rownumber += 1 @@ -503,4 +516,4 @@ class SSDictCursor(DictCursorMixin, SSCursor): - """ An unbuffered cursor, which returns results as a dictionary """ + """An unbuffered cursor, which returns results as a dictionary""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/err.py new/PyMySQL-0.7.9/pymysql/err.py --- old/PyMySQL-0.7.5/pymysql/err.py 2015-02-05 09:45:43.000000000 +0100 +++ new/PyMySQL-0.7.9/pymysql/err.py 2016-09-03 21:04:12.000000000 +0200 @@ -68,10 +68,12 @@ error_map = {} + def _map_error(exc, *errors): for error in errors: error_map[error] = exc + _map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR, ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME, ER.WRONG_TABLE_NAME, ER.FIELD_SPECIFIED_TWICE, @@ -89,32 +91,17 @@ ER.CON_COUNT_ERROR, ER.TABLEACCESS_DENIED_ERROR, ER.COLUMNACCESS_DENIED_ERROR) + del _map_error, ER -def _get_error_info(data): +def raise_mysql_exception(data): errno = struct.unpack('<h', data[1:3])[0] is_41 = data[3:4] == b"#" if is_41: - # version 4.1 - sqlstate = data[4:9].decode("utf8", 'replace') - errorvalue = data[9:].decode("utf8", 'replace') - return (errno, sqlstate, errorvalue) + # client protocol 4.1 + errval = data[9:].decode('utf-8', 'replace') else: - # version 4.0 - return (errno, None, data[3:].decode("utf8", 'replace')) - - -def _check_mysql_exception(errinfo): - errno, sqlstate, errorvalue = errinfo - errorclass = error_map.get(errno, None) - if errorclass: - raise errorclass(errno, errorvalue) - - # couldn't find the right error number - raise InternalError(errno, errorvalue) - - -def raise_mysql_exception(data): - errinfo = _get_error_info(data) - _check_mysql_exception(errinfo) + errval = data[3:].decode('utf-8', 'replace') + errorclass = error_map.get(errno, InternalError) + raise errorclass(errno, errval) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/__init__.py new/PyMySQL-0.7.9/pymysql/tests/__init__.py --- old/PyMySQL-0.7.5/pymysql/tests/__init__.py 2016-06-14 10:59:53.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/tests/__init__.py 2016-09-03 21:04:12.000000000 +0200 @@ -5,6 +5,7 @@ from pymysql.tests.test_connection import * from pymysql.tests.test_converters import * from pymysql.tests.test_cursor import * +from pymysql.tests.test_err import * from pymysql.tests.test_issues import * from pymysql.tests.test_load_local import * from pymysql.tests.test_nextset import * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_DictCursor.py new/PyMySQL-0.7.9/pymysql/tests/test_DictCursor.py --- old/PyMySQL-0.7.5/pymysql/tests/test_DictCursor.py 2015-02-04 18:10:29.000000000 +0100 +++ new/PyMySQL-0.7.9/pymysql/tests/test_DictCursor.py 2016-08-27 19:59:17.000000000 +0200 @@ -21,7 +21,9 @@ with warnings.catch_warnings(): warnings.filterwarnings("ignore") c.execute("drop table if exists dictcursor") - c.execute("""CREATE TABLE dictcursor (name char(20), age int , DOB datetime)""") + # include in filterwarnings since for unbuffered dict cursor warning for lack of table + # will only be propagated at start of next execute() call + c.execute("""CREATE TABLE dictcursor (name char(20), age int , DOB datetime)""") data = [("bob", 21, "1990-02-06 23:04:56"), ("jim", 56, "1955-05-09 13:12:45"), ("fred", 100, "1911-09-12 01:01:01")] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_basic.py new/PyMySQL-0.7.9/pymysql/tests/test_basic.py --- old/PyMySQL-0.7.5/pymysql/tests/test_basic.py 2016-05-24 09:22:27.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/tests/test_basic.py 2016-08-29 18:14:43.000000000 +0200 @@ -1,15 +1,16 @@ -import pymysql.cursors - -from pymysql.tests import base -from pymysql import util -from pymysql.err import ProgrammingError - -import time +# coding: utf-8 import datetime +import json +import time import warnings from unittest2 import SkipTest +from pymysql import util +import pymysql.cursors +from pymysql.tests import base +from pymysql.err import ProgrammingError + __all__ = ["TestConversion", "TestCursor", "TestBulkInserts"] @@ -238,6 +239,31 @@ self.assertEqual([(1,)], list(c.fetchall())) c.close() + def test_json(self): + args = self.databases[0].copy() + args["charset"] = "utf8mb4" + conn = pymysql.connect(**args) + if not self.mysql_server_is(conn, (5, 7, 0)): + raise SkipTest("JSON type is not supported on MySQL <= 5.6") + + self.safe_create_table(conn, "test_json", """\ +create table test_json ( + id int not null, + json JSON not null, + primary key (id) +);""") + cur = conn.cursor() + + json_str = u'{"hello": "こんにちは"}' + cur.execute("INSERT INTO test_json (id, `json`) values (42, %s)", (json_str,)) + cur.execute("SELECT `json` from `test_json` WHERE `id`=42") + res = cur.fetchone()[0] + self.assertEqual(json.loads(res), json.loads(json_str)) + + cur.execute("SELECT CAST(%s AS JSON) AS x", (json_str,)) + res = cur.fetchone()[0] + self.assertEqual(json.loads(res), json.loads(json_str)) + class TestBulkInserts(base.PyMySQLTestCase): @@ -349,4 +375,5 @@ cur.execute("drop table if exists no_exists_table") self.assertEqual(len(ws), 1) self.assertEqual(ws[0].category, pymysql.Warning) - self.assertTrue(u"no_exists_table" in str(ws[0].message)) + if u"no_exists_table" not in str(ws[0].message): + self.fail("'no_exists_table' not in %s" % (str(ws[0].message),)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_converters.py new/PyMySQL-0.7.9/pymysql/tests/test_converters.py --- old/PyMySQL-0.7.5/pymysql/tests/test_converters.py 2016-05-18 11:03:00.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/tests/test_converters.py 2016-08-27 19:59:17.000000000 +0200 @@ -1,3 +1,4 @@ +import datetime from unittest import TestCase from pymysql._compat import PY2 @@ -21,3 +22,46 @@ converters.escape_string(b"foo\nbar"), b"foo\\nbar" ) + + def test_convert_datetime(self): + expected = datetime.datetime(2007, 2, 24, 23, 6, 20) + dt = converters.convert_datetime('2007-02-24 23:06:20') + self.assertEqual(dt, expected) + + def test_convert_datetime_with_fsp(self): + expected = datetime.datetime(2007, 2, 24, 23, 6, 20, 511581) + dt = converters.convert_datetime('2007-02-24 23:06:20.511581') + self.assertEqual(dt, expected) + + def _test_convert_timedelta(self, with_negate=False, with_fsp=False): + d = {'hours': 789, 'minutes': 12, 'seconds': 34} + s = '%(hours)s:%(minutes)s:%(seconds)s' % d + if with_fsp: + d['microseconds'] = 511581 + s += '.%(microseconds)s' % d + + expected = datetime.timedelta(**d) + if with_negate: + expected = -expected + s = '-' + s + + tdelta = converters.convert_timedelta(s) + self.assertEqual(tdelta, expected) + + def test_convert_timedelta(self): + self._test_convert_timedelta(with_negate=False, with_fsp=False) + self._test_convert_timedelta(with_negate=True, with_fsp=False) + + def test_convert_timedelta_with_fsp(self): + self._test_convert_timedelta(with_negate=False, with_fsp=True) + self._test_convert_timedelta(with_negate=False, with_fsp=True) + + def test_convert_time(self): + expected = datetime.time(23, 6, 20) + time_obj = converters.convert_time('23:06:20') + self.assertEqual(time_obj, expected) + + def test_convert_time_with_fsp(self): + expected = datetime.time(23, 6, 20, 511581) + time_obj = converters.convert_time('23:06:20.511581') + self.assertEqual(time_obj, expected) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_err.py new/PyMySQL-0.7.9/pymysql/tests/test_err.py --- old/PyMySQL-0.7.5/pymysql/tests/test_err.py 1970-01-01 01:00:00.000000000 +0100 +++ new/PyMySQL-0.7.9/pymysql/tests/test_err.py 2016-09-03 21:04:12.000000000 +0200 @@ -0,0 +1,21 @@ +import unittest2 + +from pymysql import err + + +__all__ = ["TestRaiseException"] + + +class TestRaiseException(unittest2.TestCase): + + def test_raise_mysql_exception(self): + data = b"\xff\x15\x04Access denied" + with self.assertRaises(err.OperationalError) as cm: + err.raise_mysql_exception(data) + self.assertEqual(cm.exception.args, (1045, 'Access denied')) + + def test_raise_mysql_exception_client_protocol_41(self): + data = b"\xff\x15\x04#28000Access denied" + with self.assertRaises(err.OperationalError) as cm: + err.raise_mysql_exception(data) + self.assertEqual(cm.exception.args, (1045, 'Access denied')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_issues.py new/PyMySQL-0.7.9/pymysql/tests/test_issues.py --- old/PyMySQL-0.7.5/pymysql/tests/test_issues.py 2016-06-28 13:58:08.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/tests/test_issues.py 2016-08-27 19:59:17.000000000 +0200 @@ -4,6 +4,8 @@ import sys import pymysql +from pymysql import cursors +from pymysql._compat import text_type from pymysql.tests import base import unittest2 @@ -467,7 +469,7 @@ # select WKB query = "SELECT AsBinary(geom) FROM issue363" - if sys.version_info[0:2] >= (3,2) and self.mysql_server_is(conn, (5, 7, 0)): + if self.mysql_server_is(conn, (5, 7, 0)): with self.assertWarns(pymysql.err.Warning) as cm: cur.execute(query) else: @@ -486,3 +488,28 @@ # don't assert the exact internal binary value, as it could # vary across implementations self.assertTrue(isinstance(row[0], bytes)) + + def test_issue_491(self): + """ Test warning propagation """ + conn = pymysql.connect(charset="utf8", **self.databases[0]) + + with warnings.catch_warnings(): + # Ignore all warnings other than pymysql generated ones + warnings.simplefilter("ignore") + warnings.simplefilter("error", category=pymysql.Warning) + + # verify for both buffered and unbuffered cursor types + for cursor_class in (cursors.Cursor, cursors.SSCursor): + c = conn.cursor(cursor_class) + try: + c.execute("SELECT CAST('124b' AS SIGNED)") + c.fetchall() + except pymysql.Warning as e: + # Warnings should have errorcode and string message, just like exceptions + self.assertEqual(len(e.args), 2) + self.assertEqual(e.args[0], 1292) + self.assertTrue(isinstance(e.args[1], text_type)) + else: + self.fail("Should raise Warning") + finally: + c.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/tests/test_load_local.py new/PyMySQL-0.7.9/pymysql/tests/test_load_local.py --- old/PyMySQL-0.7.5/pymysql/tests/test_load_local.py 2016-06-14 10:59:53.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/tests/test_load_local.py 2016-08-29 18:14:43.000000000 +0200 @@ -80,7 +80,9 @@ "test_load_local FIELDS TERMINATED BY ','").format(filename) ) self.assertEqual(w[0].category, Warning) - self.assertTrue("Incorrect integer value" in str(w[-1].message)) + expected_message = "Incorrect integer value" + if expected_message not in str(w[-1].message): + self.fail("%r not in %r" % (expected_message, w[-1].message)) finally: c.execute("DROP TABLE test_load_local") c.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/times.py new/PyMySQL-0.7.9/pymysql/times.py --- old/PyMySQL-0.7.5/pymysql/times.py 2014-08-29 13:32:54.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/times.py 2016-08-29 18:14:43.000000000 +0200 @@ -1,16 +1,20 @@ from time import localtime from datetime import date, datetime, time, timedelta + Date = date Time = time TimeDelta = timedelta Timestamp = datetime + def DateFromTicks(ticks): return date(*localtime(ticks)[:3]) + def TimeFromTicks(ticks): return time(*localtime(ticks)[3:6]) + def TimestampFromTicks(ticks): return datetime(*localtime(ticks)[:6]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/pymysql/util.py new/PyMySQL-0.7.9/pymysql/util.py --- old/PyMySQL-0.7.5/pymysql/util.py 2016-05-18 11:03:00.000000000 +0200 +++ new/PyMySQL-0.7.9/pymysql/util.py 2016-08-29 18:14:43.000000000 +0200 @@ -1,14 +1,17 @@ import struct + def byte2int(b): if isinstance(b, int): return b else: return struct.unpack("!B", b)[0] + def int2byte(i): return struct.pack("!B", i) + def join_bytes(bs): if len(bs) == 0: return "" @@ -17,4 +20,3 @@ for b in bs[1:]: rv += b return rv - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.5/setup.cfg new/PyMySQL-0.7.9/setup.cfg --- old/PyMySQL-0.7.5/setup.cfg 2016-06-28 14:18:51.000000000 +0200 +++ new/PyMySQL-0.7.9/setup.cfg 2016-09-03 21:14:48.000000000 +0200 @@ -1,6 +1,3 @@ -[wheel] -universal = 1 - [flake8] ignore = E226,E301,E701 exclude = tests,build
