Hello community, here is the log from the commit of package python-PyMySQL for openSUSE:Factory checked in at 2017-08-18 15:04:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-PyMySQL (Old) and /work/SRC/openSUSE:Factory/.python-PyMySQL.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-PyMySQL" Fri Aug 18 15:04:20 2017 rev:9 rq:517107 version:0.7.11 Changes: -------- --- /work/SRC/openSUSE:Factory/python-PyMySQL/python-PyMySQL.changes 2016-12-08 00:29:16.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-PyMySQL.new/python-PyMySQL.changes 2017-08-18 15:04:30.513270999 +0200 @@ -1,0 +2,25 @@ +Wed Aug 16 01:36:37 UTC 2017 - toddrme2...@gmail.com + +- Implement single-spec version +- update to 0.7.11 + * Fixed Connection.close() failed when failed to send COM_CLOSE packet. + * Cursor.executemany() accepts query ends with semicolon. + * ssl parameters can be read from my.cnf. +- update to 0.7.10 + * **SECURITY FIX**: Raise RuntimeError when received LOAD_LOCAL packet while + ``loacal_infile=False``. (Thanks to Bryan Helmig) + * Raise SERVER_LOST error for MariaDB's shutdown packet (#540) + * Change default connect_timeout to 10. + * Add bind_address option (#529) +- update to 0.7.9 + * Fix PyMySQL stop reading rows when first column is empty string (#513) + Reverts DEPRECATE_EOF introduced in 0.7.7. +- update to 0.7.8 + * Revert error message change in 0.7.7. + (SQLAlchemy parses error message, #507) +- update to 0.7.7 + * Add new unicode collation (#498) + * Fix conv option is not used for encoding objects. + * Experimental support for DEPRECATE_EOF protocol. + +------------------------------------------------------------------- Old: ---- PyMySQL-0.7.6.tar.gz New: ---- PyMySQL-0.7.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-PyMySQL.spec ++++++ --- /var/tmp/diff_new_pack.o9xpIu/_old 2017-08-18 15:04:33.028916689 +0200 +++ /var/tmp/diff_new_pack.o9xpIu/_new 2017-08-18 15:04:33.040914999 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-PyMySQL # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,28 +16,28 @@ # +%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-PyMySQL -Version: 0.7.6 +Version: 0.7.11 Release: 0 Summary: Pure Python MySQL Driver License: MIT Group: Development/Languages/Python -Url: http://code.google.com/p/pymysql -Source: https://pypi.io/packages/source/P/PyMySQL/PyMySQL-%{version}.tar.gz -BuildRequires: python-devel -BuildRequires: python-setuptools -BuildRoot: %{_tmppath}/%{name}-%{version}-build -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} -%else +Url: https://github.com/PyMySQL/PyMySQL/ +Source: https://files.pythonhosted.org/packages/source/P/PyMySQL/PyMySQL-%{version}.tar.gz +BuildRequires: %{python_module devel} +BuildRequires: %{python_module setuptools} +BuildRequires: fdupes +BuildRequires: python-rpm-macros BuildArch: noarch -%endif + +%python_subpackages %description This package contains a pure-Python MySQL client library. Documentation on the MySQL client/server protocol can be found here: -http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol If you would -like to run the test suite, edit the config parameters in pymysql/tests/base.py. +http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol + The goal of pymysql is to be a drop-in replacement for MySQLdb and work on CPython 2.3+, Jython, IronPython, PyPy and Python 3. We test for compatibility by simply changing the import statements in the Django MySQL backend and running @@ -49,15 +49,16 @@ sed -i '1 { /^#!/ d }' pymysql/tests/thirdparty/test_MySQLdb/*.py %build -python setup.py build +%python_build %install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} +%python_install +%python_expand %fdupes %{buildroot}%{$python_sitelib} #%%check #NOTE(saschpe): Needs mysql server -%files +%files %{python_files} %defattr(-,root,root,-) %doc CHANGELOG LICENSE README.rst %{python_sitelib}/* ++++++ PyMySQL-0.7.6.tar.gz -> PyMySQL-0.7.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/CHANGELOG new/PyMySQL-0.7.11/CHANGELOG --- old/PyMySQL-0.7.6/CHANGELOG 2016-07-29 05:21:41.000000000 +0200 +++ new/PyMySQL-0.7.11/CHANGELOG 2017-04-05 20:05:20.000000000 +0200 @@ -1,5 +1,50 @@ # Changes +## 0.7.11 + +Release date: 2017-04-06 + +* Fixed Connection.close() failed when failed to send COM_CLOSE packet. +* Cursor.executemany() accepts query ends with semicolon. +* ssl parameters can be read from my.cnf. + + +## 0.7.10 + +Release date: 2017-02-14 + +* **SECURITY FIX**: Raise RuntimeError when received LOAD_LOCAL packet while + ``loacal_infile=False``. (Thanks to Bryan Helmig) + +* Raise SERVER_LOST error for MariaDB's shutdown packet (#540) + +* Change default connect_timeout to 10. + +* Add bind_address option (#529) + + +## 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/PKG-INFO new/PyMySQL-0.7.11/PKG-INFO --- old/PyMySQL-0.7.6/PKG-INFO 2016-07-29 05:27:02.000000000 +0200 +++ new/PyMySQL-0.7.11/PKG-INFO 2017-04-05 20:11:44.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.6 +Version: 0.7.11 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki @@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Intended Audience :: Developers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/PyMySQL.egg-info/PKG-INFO new/PyMySQL-0.7.11/PyMySQL.egg-info/PKG-INFO --- old/PyMySQL-0.7.6/PyMySQL.egg-info/PKG-INFO 2016-07-29 05:27:02.000000000 +0200 +++ new/PyMySQL-0.7.11/PyMySQL.egg-info/PKG-INFO 2017-04-05 20:11:44.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.6 +Version: 0.7.11 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki @@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Intended Audience :: Developers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/PyMySQL.egg-info/SOURCES.txt new/PyMySQL-0.7.11/PyMySQL.egg-info/SOURCES.txt --- old/PyMySQL-0.7.6/PyMySQL.egg-info/SOURCES.txt 2016-07-29 05:27:02.000000000 +0200 +++ new/PyMySQL-0.7.11/PyMySQL.egg-info/SOURCES.txt 2017-04-05 20:11:44.000000000 +0200 @@ -38,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.6/README.rst new/PyMySQL-0.7.11/README.rst --- old/PyMySQL-0.7.6/README.rst 2016-07-28 17:42:45.000000000 +0200 +++ new/PyMySQL-0.7.11/README.rst 2017-02-14 13:21:45.000000000 +0100 @@ -22,10 +22,11 @@ is to be a drop-in replacement for MySQLdb and work on CPython, PyPy and IronPython. NOTE: PyMySQL doesn't support low level APIs `_mysql` provides like `data_seek`, -`store_result`, and `use_result`. You should use high level APIs defined in PEP 294. -But some APIs like `autocommit` and `ping` are supported because PEP 294 doesn't cover +`store_result`, and `use_result`. You should use high level APIs defined in `PEP 249`_. +But some APIs like `autocommit` and `ping` are supported because `PEP 249`_ doesn't cover their usecase. +.. _`PEP 249`: https://www.python.org/dev/peps/pep-0249/ Requirements ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/pymysql/__init__.py new/PyMySQL-0.7.11/pymysql/__init__.py --- old/PyMySQL-0.7.6/pymysql/__init__.py 2016-07-29 05:21:53.000000000 +0200 +++ new/PyMySQL-0.7.11/pymysql/__init__.py 2017-04-05 20:05:40.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, 6, None) +VERSION = (0, 7, 11, None) threadsafety = 1 apilevel = "2.0" paramstyle = "pyformat" @@ -87,7 +89,7 @@ from .connections import Connection return Connection(*args, **kwargs) -from pymysql import connections as _orig_conn +from . import connections as _orig_conn if _orig_conn.Connection.__init__.__doc__ is not None: Connect.__doc__ = _orig_conn.Connection.__init__.__doc__ del _orig_conn diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/pymysql/charset.py new/PyMySQL-0.7.11/pymysql/charset.py --- old/PyMySQL-0.7.6/pymysql/charset.py 2016-07-28 17:42:45.000000000 +0200 +++ new/PyMySQL-0.7.11/pymysql/charset.py 2016-08-27 19:59:17.000000000 +0200 @@ -253,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.6/pymysql/connections.py new/PyMySQL-0.7.11/pymysql/connections.py --- old/PyMySQL-0.7.6/pymysql/connections.py 2016-07-28 17:42:45.000000000 +0200 +++ new/PyMySQL-0.7.11/pymysql/connections.py 2017-04-05 20:02:01.000000000 +0200 @@ -17,7 +17,7 @@ import warnings from .charset import MBLENGTH, charset_by_name, charset_by_id -from .constants import CLIENT, COMMAND, FIELD_TYPE, SERVER_STATUS +from .constants import CLIENT, COMMAND, CR, FIELD_TYPE, SERVER_STATUS from .converters import escape_item, escape_string, through, conversions as _conv from .cursors import Cursor from .optionfile import Parser @@ -117,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() @@ -363,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]) @@ -525,17 +524,19 @@ _sock = None _auth_plugin_name = '' + _closed = False def __init__(self, host=None, user=None, password="", database=None, port=0, unix_socket=None, charset='', sql_mode=None, read_default_file=None, conv=None, use_unicode=None, client_flag=0, cursorclass=Cursor, init_command=None, - connect_timeout=None, ssl=None, read_default_group=None, + connect_timeout=10, ssl=None, read_default_group=None, compress=None, named_pipe=None, no_delay=None, autocommit=False, db=None, passwd=None, local_infile=False, max_allowed_packet=16*1024*1024, defer_connect=False, - auth_plugin_map={}, read_timeout=None, write_timeout=None): + auth_plugin_map={}, read_timeout=None, write_timeout=None, + bind_address=None): """ Establish a connection to the MySQL database. Accepts several arguments: @@ -545,6 +546,9 @@ password: Password to use. database: Database to use, None to not use a particular one. port: MySQL port to use, default is usually OK. (default: 3306) + bind_address: When the client has multiple network interfaces, specify + the interface from which to connect to the host. Argument can be + a hostname or an IP address. unix_socket: Optionally, you can use a unix socket rather than TCP/IP. charset: Charset you want to use. sql_mode: Default SQL_MODE to use. @@ -561,6 +565,7 @@ cursorclass: Custom cursor class to use. init_command: Initial SQL statement to run when connection is established. connect_timeout: Timeout before throwing an exception when connecting. + (default: 10, min: 1, max: 31536000) ssl: A dict of arguments similar to mysql_ssl_set()'s parameters. For now the capath and cipher arguments are not supported. @@ -595,17 +600,10 @@ if compress or named_pipe: raise NotImplementedError("compress and named_pipe arguments are not supported") - if local_infile: + self._local_infile = bool(local_infile) + if self._local_infile: client_flag |= CLIENT.LOCAL_FILES - self.ssl = False - if ssl: - if not SSL_ENABLED: - raise NotImplementedError("ssl module not found") - self.ssl = True - client_flag |= CLIENT.SSL - self.ctx = self._create_ssl_ctx(ssl) - if read_default_group and not read_default_file: if sys.platform.startswith("win"): read_default_file = "c:\\my.ini" @@ -633,7 +631,23 @@ database = _config("database", database) unix_socket = _config("socket", unix_socket) port = int(_config("port", port)) + bind_address = _config("bind-address", bind_address) charset = _config("default-character-set", charset) + if not ssl: + ssl = {} + if isinstance(ssl, dict): + for key in ["ca", "capath", "cert", "key", "cipher"]: + value = _config("ssl-" + key, ssl.get(key)) + if value: + ssl[key] = value + + self.ssl = False + if ssl: + if not SSL_ENABLED: + raise NotImplementedError("ssl module not found") + self.ssl = True + client_flag |= CLIENT.SSL + self.ctx = self._create_ssl_ctx(ssl) self.host = host or "localhost" self.port = port or 3306 @@ -641,6 +655,10 @@ self.password = password or "" self.db = database self.unix_socket = unix_socket + self.bind_address = bind_address + if not (0 < connect_timeout <= 31536000): + raise ValueError("connect_timeout should be >0 and <=31536000") + self.connect_timeout = connect_timeout or None if read_timeout is not None and read_timeout <= 0: raise ValueError("read_timeout should be >= 0") self._read_timeout = read_timeout @@ -659,13 +677,12 @@ 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 self.cursorclass = cursorclass - self.connect_timeout = connect_timeout self._result = None self._affected_rows = 0 @@ -707,24 +724,25 @@ def close(self): """Send the quit message and close the socket""" - if self._sock is None: + if self._closed: raise err.Error("Already closed") + self._closed = True + if self._sock is None: + return send_data = struct.pack('<iB', 1, COMMAND.COM_QUIT) try: self._write_bytes(send_data) except Exception: pass finally: - sock = self._sock - self._sock = None - self._rfile = None - sock.close() + self._force_close() @property def open(self): return self._sock is not None - def __del__(self): + def _force_close(self): + """Close connection without QUIT message""" if self._sock: try: self._sock.close() @@ -733,6 +751,8 @@ self._sock = None self._rfile = None + __del__ = _force_close + def autocommit(self, value): self.autocommit_mode = bool(value) current = self.get_autocommit() @@ -876,6 +896,7 @@ self.encoding = encoding def connect(self, sock=None): + self._closed = False try: if sock is None: if self.unix_socket and self.host in ('localhost', '127.0.0.1'): @@ -885,10 +906,14 @@ self.host_info = "Localhost via UNIX socket" if DEBUG: print('connected using unix_socket') else: + kwargs = {} + if self.bind_address is not None: + kwargs['source_address'] = (self.bind_address, 0) while True: try: sock = socket.create_connection( - (self.host, self.port), self.connect_timeout) + (self.host, self.port), self.connect_timeout, + **kwargs) break except (OSError, IOError) as e: if e.errno == errno.EINTR: @@ -965,8 +990,15 @@ btrl, btrh, packet_number = struct.unpack('<HBB', packet_header) bytes_to_read = btrl + (btrh << 16) if packet_number != self._next_seq_id: - raise err.InternalError("Packet sequence number wrong - got %d expected %d" % - (packet_number, self._next_seq_id)) + self._force_close() + if packet_number == 0: + # MariaDB sends error packet with seqno==0 when shutdown + raise err.OperationalError( + CR.CR_SERVER_LOST, + "Lost connection to MySQL server during query") + raise err.InternalError( + "Packet sequence number wrong - got %d expected %d" + % (packet_number, self._next_seq_id)) self._next_seq_id = (self._next_seq_id + 1) % 256 recv_data = self._read_bytes(bytes_to_read) @@ -991,12 +1023,14 @@ except (IOError, OSError) as e: if e.errno == errno.EINTR: continue + self._force_close() raise err.OperationalError( - 2013, + CR.CR_SERVER_LOST, "Lost connection to MySQL server during query (%s)" % (e,)) if len(data) < num_bytes: + self._force_close() raise err.OperationalError( - 2013, "Lost connection to MySQL server during query") + CR.CR_SERVER_LOST, "Lost connection to MySQL server during query") return data def _write_bytes(self, data): @@ -1004,7 +1038,10 @@ try: self._sock.sendall(data) except IOError as e: - raise err.OperationalError(2006, "MySQL server has gone away (%r)" % (e,)) + self._force_close() + raise err.OperationalError( + CR.CR_SERVER_GONE_ERROR, + "MySQL server has gone away (%r)" % (e,)) def _read_query_result(self, unbuffered=False): if unbuffered: @@ -1342,6 +1379,9 @@ self.has_next = ok_packet.has_next def _read_load_local_packet(self, first_packet): + if not self.connection._local_infile: + raise RuntimeError( + "**WARN**: Received LOAD_LOCAL packet but local_infile option is false.") load_packet = LoadLocalPacketWrapper(first_packet) sender = LoadLocalFile(load_packet.filename, self.connection) try: @@ -1356,12 +1396,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() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/pymysql/constants/CLIENT.py new/PyMySQL-0.7.11/pymysql/constants/CLIENT.py --- old/PyMySQL-0.7.6/pymysql/constants/CLIENT.py 2016-03-06 15:57:08.000000000 +0100 +++ new/PyMySQL-0.7.11/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.6/pymysql/constants/CR.py new/PyMySQL-0.7.11/pymysql/constants/CR.py --- old/PyMySQL-0.7.6/pymysql/constants/CR.py 2015-09-29 17:10:42.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/pymysql/cursors.py new/PyMySQL-0.7.11/pymysql/cursors.py --- old/PyMySQL-0.7.6/pymysql/cursors.py 2016-07-28 17:42:45.000000000 +0200 +++ new/PyMySQL-0.7.11/pymysql/cursors.py 2017-04-05 20:02:01.000000000 +0200 @@ -5,7 +5,6 @@ import warnings from ._compat import range_type, text_type, PY2 - from . import err @@ -15,16 +14,16 @@ RE_INSERT_VALUES = re.compile( r"\s*((?:INSERT|REPLACE)\s.+\sVALUES?\s+)" + r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" + - r"(\s*(?:ON DUPLICATE.*)?)\Z", + r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z", re.IGNORECASE | re.DOTALL) class Cursor(object): - ''' + """ This is the object you use to interact with the database. - ''' + """ - #: Max stetement size which :meth:`executemany` generates. + #: Max statement size which :meth:`executemany` generates. #: #: Max size of allowed statement is max_allowed_packet - packet_header_size. #: Default value of max_allowed_packet is 1048576. @@ -33,10 +32,10 @@ _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 @@ -48,9 +47,9 @@ self._warnings_handled = False def close(self): - ''' + """ Closing a cursor just exhausts all remaining data. - ''' + """ conn = self.connection if conn is None: return @@ -116,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 @@ -274,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 @@ -283,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 () @@ -293,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 () @@ -404,8 +403,8 @@ or for connections to remote servers over a slow network. Instead of copying every row of data into a buffer, this will fetch - rows as needed. The upside of this, is the client uses much less memory, - and rows are returned much faster when traveling over a slow network, + rows as needed. The upside of this is the client uses much less memory, + and rows are returned much faster when traveling over a slow network or if the result set is very big. There are limitations, though. The MySQL protocol doesn't support @@ -444,11 +443,11 @@ 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: @@ -477,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 @@ -517,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.6/pymysql/err.py new/PyMySQL-0.7.11/pymysql/err.py --- old/PyMySQL-0.7.6/pymysql/err.py 2015-09-29 17:10:42.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/pymysql/tests/__init__.py new/PyMySQL-0.7.11/pymysql/tests/__init__.py --- old/PyMySQL-0.7.6/pymysql/tests/__init__.py 2016-05-26 06:05:45.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/pymysql/tests/test_basic.py new/PyMySQL-0.7.11/pymysql/tests/test_basic.py --- old/PyMySQL-0.7.6/pymysql/tests/test_basic.py 2016-07-28 17:42:45.000000000 +0200 +++ new/PyMySQL-0.7.11/pymysql/tests/test_basic.py 2016-08-29 18:14:43.000000000 +0200 @@ -375,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.6/pymysql/tests/test_err.py new/PyMySQL-0.7.11/pymysql/tests/test_err.py --- old/PyMySQL-0.7.6/pymysql/tests/test_err.py 1970-01-01 01:00:00.000000000 +0100 +++ new/PyMySQL-0.7.11/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.6/pymysql/tests/test_load_local.py new/PyMySQL-0.7.11/pymysql/tests/test_load_local.py --- old/PyMySQL-0.7.6/pymysql/tests/test_load_local.py 2016-07-15 18:43:37.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/pymysql/times.py new/PyMySQL-0.7.11/pymysql/times.py --- old/PyMySQL-0.7.6/pymysql/times.py 2015-09-29 17:10:42.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/pymysql/util.py new/PyMySQL-0.7.11/pymysql/util.py --- old/PyMySQL-0.7.6/pymysql/util.py 2016-07-26 05:09:26.000000000 +0200 +++ new/PyMySQL-0.7.11/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.6/setup.cfg new/PyMySQL-0.7.11/setup.cfg --- old/PyMySQL-0.7.6/setup.cfg 2016-07-29 05:27:02.000000000 +0200 +++ new/PyMySQL-0.7.11/setup.cfg 2017-04-05 20:11:44.000000000 +0200 @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = False - [flake8] ignore = E226,E301,E701 exclude = tests,build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.6/setup.py new/PyMySQL-0.7.11/setup.py --- old/PyMySQL-0.7.6/setup.py 2016-05-26 06:13:35.000000000 +0200 +++ new/PyMySQL-0.7.11/setup.py 2017-04-05 20:10:50.000000000 +0200 @@ -26,6 +26,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Intended Audience :: Developers',