Hello community, here is the log from the commit of package python3-PyMySQL for openSUSE:Factory checked in at 2016-07-28 23:46:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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-05-25 21:23:12.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python3-PyMySQL.new/python3-PyMySQL.changes 2016-07-28 23:46:30.000000000 +0200 @@ -1,0 +2,28 @@ +Sat Jul 23 17:32:11 UTC 2016 - a...@gmx.de + +- specfile: + *updated url + +- update to version 0.7.5: + * Fix exception raised while importing when getpwuid() fails (#472) + * SSCursor supports LOAD DATA LOCAL INFILE (#473) + * Fix encoding error happen for JSON type (#477) + * Fix test fail on Python 2.7 and MySQL 5.7 (#478) + +- changes from version 0.7.4: + * Fix AttributeError may happen while Connection.__del__ (#463) + * Fix SyntaxError in test_cursor. (#464) + * frozenset support for query value. (#461) + * Start using readthedocs.io + +- changes from version 0.7.3: + * Add read_timeout and write_timeout option. + * Support serialization customization by `conv` option. + * Unknown type is converted by `str()`, for MySQLdb compatibility. + * Support '%%' in `Cursor.executemany()` + * Support REPLACE statement in `Cursor.executemany()` + * Fix handling incomplete row caused by 'SHOW SLAVE HOSTS'. + * Fix decode error when use_unicode=False on PY3 + * Fix port option in my.cnf file is ignored. + +------------------------------------------------------------------- @@ -6 +33,0 @@ - Old: ---- PyMySQL-0.7.2.tar.gz New: ---- PyMySQL-0.7.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-PyMySQL.spec ++++++ --- /var/tmp/diff_new_pack.ZQWNB7/_old 2016-07-28 23:46:32.000000000 +0200 +++ /var/tmp/diff_new_pack.ZQWNB7/_new 2016-07-28 23:46:32.000000000 +0200 @@ -17,12 +17,12 @@ Name: python3-PyMySQL -Version: 0.7.2 +Version: 0.7.5 Release: 0 Summary: Pure Python MySQL Driver License: MIT Group: Development/Languages/Python -Url: http://code.google.com/p/pymysql +Url: https://github.com/PyMySQL/PyMySQL Source: https://files.pythonhosted.org/packages/source/P/PyMySQL/PyMySQL-%{version}.tar.gz BuildRequires: python3-devel BuildRequires: python3-setuptools ++++++ PyMySQL-0.7.2.tar.gz -> PyMySQL-0.7.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/CHANGELOG new/PyMySQL-0.7.5/CHANGELOG --- old/PyMySQL-0.7.2/CHANGELOG 2016-02-24 11:38:43.000000000 +0100 +++ new/PyMySQL-0.7.5/CHANGELOG 2016-06-28 14:18:40.000000000 +0200 @@ -1,5 +1,37 @@ # Changes +## 0.7.5 + +Release date: 2016-06-28 + +* Fix exception raised while importing when getpwuid() fails (#472) +* SSCursor supports LOAD DATA LOCAL INFILE (#473) +* Fix encoding error happen for JSON type (#477) +* Fix test fail on Python 2.7 and MySQL 5.7 (#478) + +## 0.7.4 + +Release date: 2016-05-26 + +* Fix AttributeError may happen while Connection.__del__ (#463) +* Fix SyntaxError in test_cursor. (#464) +* frozenset support for query value. (#461) +* Start using readthedocs.io + +## 0.7.3 + +Release date: 2016-05-19 + +* Add read_timeout and write_timeout option. +* Support serialization customization by `conv` option. +* Unknown type is converted by `str()`, for MySQLdb compatibility. +* Support '%%' in `Cursor.executemany()` +* Support REPLACE statement in `Cursor.executemany()` +* Fix handling incomplete row caused by 'SHOW SLAVE HOSTS'. +* Fix decode error when use_unicode=False on PY3 +* Fix port option in my.cnf file is ignored. + + ## 0.7.2 Release date: 2016-02-24 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/PKG-INFO new/PyMySQL-0.7.5/PKG-INFO --- old/PyMySQL-0.7.2/PKG-INFO 2016-02-24 11:40:57.000000000 +0100 +++ new/PyMySQL-0.7.5/PKG-INFO 2016-06-28 14:18:51.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.2 +Version: 0.7.5 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki @@ -8,15 +8,14 @@ License: MIT Description: UNKNOWN Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Database diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/PyMySQL.egg-info/PKG-INFO new/PyMySQL-0.7.5/PyMySQL.egg-info/PKG-INFO --- old/PyMySQL-0.7.2/PyMySQL.egg-info/PKG-INFO 2016-02-24 11:40:56.000000000 +0100 +++ new/PyMySQL-0.7.5/PyMySQL.egg-info/PKG-INFO 2016-06-28 14:18:50.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: PyMySQL -Version: 0.7.2 +Version: 0.7.5 Summary: Pure Python MySQL Driver Home-page: https://github.com/PyMySQL/PyMySQL/ Author: INADA Naoki @@ -8,15 +8,14 @@ License: MIT Description: UNKNOWN Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Database diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/PyMySQL.egg-info/SOURCES.txt new/PyMySQL-0.7.5/PyMySQL.egg-info/SOURCES.txt --- old/PyMySQL-0.7.2/PyMySQL.egg-info/SOURCES.txt 2016-02-24 11:40:57.000000000 +0100 +++ new/PyMySQL-0.7.5/PyMySQL.egg-info/SOURCES.txt 2016-06-28 14:18:51.000000000 +0200 @@ -39,7 +39,6 @@ pymysql/tests/test_connection.py pymysql/tests/test_converters.py pymysql/tests/test_cursor.py -pymysql/tests/test_example.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.2/README.rst new/PyMySQL-0.7.5/README.rst --- old/PyMySQL-0.7.2/README.rst 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/README.rst 2016-05-24 10:05:28.000000000 +0200 @@ -1,18 +1,30 @@ -======= -PyMySQL -======= +.. image:: https://readthedocs.org/projects/pymysql/badge/?version=latest + :target: http://pymysql.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status .. image:: https://travis-ci.org/PyMySQL/PyMySQL.svg?branch=master - :target: https://travis-ci.org/PyMySQL/PyMySQL + :target: https://travis-ci.org/PyMySQL/PyMySQL .. image:: https://coveralls.io/repos/PyMySQL/PyMySQL/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=master + :target: https://coveralls.io/github/PyMySQL/PyMySQL?branch=master + +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://github.com/PyMySQL/PyMySQL/blob/master/LICENSE + + +PyMySQL +======= .. contents:: 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. +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 +their usecase. + Requirements ------------- @@ -42,38 +54,14 @@ $ pip install PyMySQL -Alternatively (e.g. if ``pip`` is not available), a tarball can be downloaded -from GitHub and installed with Setuptools:: - $ # X.X is the desired PyMySQL version (e.g. 0.5 or 0.6). - $ curl -L https://github.com/PyMySQL/PyMySQL/tarball/pymysql-X.X | tar xz - $ cd PyMySQL* - $ python setup.py install - $ # The folder PyMySQL* can be safely removed now. - -Test Suite ----------- - -If you would like to run the test suite, create database for test like this:: - - mysql -e 'create database test_pymysql DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;' - mysql -e 'create database test_pymysql2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;' - -Then, copy the file ``.travis.databases.json`` to ``pymysql/tests/databases.json`` -and edit the new file to match your MySQL configuration:: - - $ cp .travis.databases.json pymysql/tests/databases.json - $ $EDITOR pymysql/tests/databases.json - -To run all the tests, execute the script ``runtests.py``:: - - $ python runtests.py - -A ``tox.ini`` file is also provided for conveniently running tests on multiple -Python versions:: +Documentation +------------- - $ tox +Documentation is available online: http://pymysql.readthedocs.io/ +For support, please refer to the `StackOverflow +<http://stackoverflow.com/questions/tagged/pymysql>`_. Example ------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/__init__.py new/PyMySQL-0.7.5/pymysql/__init__.py --- old/PyMySQL-0.7.2/pymysql/__init__.py 2016-02-24 11:39:11.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/__init__.py 2016-06-28 14:18:40.000000000 +0200 @@ -33,7 +33,7 @@ DateFromTicks, TimeFromTicks, TimestampFromTicks -VERSION = (0, 7, 2, None) +VERSION = (0, 7, 5, None) threadsafety = 1 apilevel = "2.0" paramstyle = "pyformat" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/connections.py new/PyMySQL-0.7.5/pymysql/connections.py --- old/PyMySQL-0.7.2/pymysql/connections.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/connections.py 2016-06-28 13:27:57.000000000 +0200 @@ -18,8 +18,7 @@ from .charset import MBLENGTH, charset_by_name, charset_by_id from .constants import CLIENT, COMMAND, FIELD_TYPE, SERVER_STATUS -from .converters import ( - escape_item, encoders, decoders, escape_string, through) +from .converters import escape_item, escape_string, through, conversions as _conv from .cursors import Cursor from .optionfile import Parser from .util import byte2int, int2byte @@ -36,7 +35,8 @@ import getpass DEFAULT_USER = getpass.getuser() del getpass -except ImportError: +except (ImportError, KeyError): + # KeyError occurs when there's no entry in OS database for a current user. DEFAULT_USER = None @@ -88,6 +88,7 @@ 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, @@ -523,19 +524,19 @@ connect(). """ - socket = None + _sock = None _auth_plugin_name = '' def __init__(self, host=None, user=None, password="", - database=None, port=3306, unix_socket=None, + database=None, port=0, unix_socket=None, charset='', sql_mode=None, - read_default_file=None, conv=decoders, use_unicode=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, 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={}): + auth_plugin_map={}, read_timeout=None, write_timeout=None): """ Establish a connection to the MySQL database. Accepts several arguments: @@ -544,15 +545,16 @@ user: Username to log in as password: Password to use. database: Database to use, None to not use a particular one. - port: MySQL port to use, default is usually OK. + port: MySQL port to use, default is usually OK. (default: 3306) 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. read_default_file: Specifies my.cnf file to read these parameters from under the [client] section. conv: - Decoders dictionary to use instead of the default one. - This is used to provide custom marshalling of types. See converters. + Conversion dictionary to use instead of the default one. + This is used to provide custom marshalling and unmarshaling of types. + See converters. use_unicode: Whether or not to default to unicode strings. This option defaults to true for Py3k. @@ -635,11 +637,17 @@ charset = _config("default-character-set", charset) self.host = host or "localhost" - self.port = port + self.port = port or 3306 self.user = user or DEFAULT_USER self.password = password or "" self.db = database self.unix_socket = unix_socket + if read_timeout is not None and read_timeout <= 0: + raise ValueError("read_timeout should be >= 0") + self._read_timeout = read_timeout + if write_timeout is not None and write_timeout <= 0: + raise ValueError("write_timeout should be >= 0") + self._write_timeout = write_timeout if charset: self.charset = charset self.use_unicode = True @@ -667,14 +675,17 @@ #: specified autocommit mode. None means use server default. self.autocommit_mode = autocommit - self.encoders = encoders # Need for MySQLdb compatibility. - self.decoders = conv + if conv is None: + conv = _conv + # Need for MySQLdb compatibility. + self.encoders = dict([(k, v) for (k, v) in conv.items() if type(k) is not int]) + self.decoders = dict([(k, v) for (k, v) in conv.items() if type(k) is int]) self.sql_mode = sql_mode self.init_command = init_command self.max_allowed_packet = max_allowed_packet self._auth_plugin_map = auth_plugin_map if defer_connect: - self.socket = None + self._sock = None else: self.connect() @@ -697,7 +708,7 @@ def close(self): """Send the quit message and close the socket""" - if self.socket is None: + if self._sock is None: raise err.Error("Already closed") send_data = struct.pack('<iB', 1, COMMAND.COM_QUIT) try: @@ -705,22 +716,22 @@ except Exception: pass finally: - sock = self.socket - self.socket = None + sock = self._sock + self._sock = None self._rfile = None sock.close() @property def open(self): - return self.socket is not None + return self._sock is not None def __del__(self): - if self.socket: + if self._sock: try: - self.socket.close() + self._sock.close() except: pass - self.socket = None + self._sock = None self._rfile = None def autocommit(self, value): @@ -770,19 +781,25 @@ return result.rows def select_db(self, db): - '''Set current db''' + """Set current db""" self._execute_command(COMMAND.COM_INIT_DB, db) self._read_ok_packet() def escape(self, obj, mapping=None): - """Escape whatever value you pass to it""" + """Escape whatever value you pass to it. + + Non-standard, for internal use; do not use this in your applications. + """ if isinstance(obj, str_type): return "'" + self.escape_string(obj) + "'" return escape_item(obj, self.charset, mapping=mapping) def literal(self, obj): - '''Alias for escape()''' - return self.escape(obj) + """Alias for escape() + + Non-standard, for internal use; do not use this in your applications. + """ + return self.escape(obj, self.encoders) def escape_string(self, s): if (self.server_status & @@ -834,7 +851,7 @@ def ping(self, reconnect=True): """Check if the server is alive""" - if self.socket is None: + if self._sock is None: if reconnect: self.connect() reconnect = False @@ -883,7 +900,7 @@ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.settimeout(None) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self.socket = sock + self._sock = sock self._rfile = _makefile(sock, 'rb') self._next_seq_id = 0 @@ -967,6 +984,7 @@ return packet def _read_bytes(self, num_bytes): + self._sock.settimeout(self._read_timeout) while True: try: data = self._rfile.read(num_bytes) @@ -983,8 +1001,9 @@ return data def _write_bytes(self, data): + self._sock.settimeout(self._write_timeout) try: - self.socket.sendall(data) + self._sock.sendall(data) except IOError as e: raise err.OperationalError(2006, "MySQL server has gone away (%r)" % (e,)) @@ -1012,7 +1031,7 @@ return 0 def _execute_command(self, command, sql): - if not self.socket: + if not self._sock: raise err.InterfaceError("(0, '')") # If the last query was unbuffered, make sure it finishes before @@ -1066,8 +1085,8 @@ if self.ssl and self.server_capabilities & CLIENT.SSL: self.write_packet(data_init) - self.socket = self.ctx.wrap_socket(self.socket, server_hostname=self.host) - self._rfile = _makefile(self.socket, 'rb') + self._sock = self.ctx.wrap_socket(self._sock, server_hostname=self.host) + self._rfile = _makefile(self._sock, 'rb') data = data_init + self.user + b'\0' @@ -1301,6 +1320,10 @@ self._read_ok_packet(first_packet) self.unbuffered_active = False self.connection = None + elif first_packet.is_load_local_packet(): + self._read_load_local_packet(first_packet) + self.unbuffered_active = False + self.connection = None else: self.field_count = first_packet.read_length_encoded_integer() self._get_descriptions() @@ -1390,7 +1413,12 @@ def _read_row_from_packet(self, packet): row = [] for encoding, converter in self.converters: - data = packet.read_length_coded_string() + try: + data = packet.read_length_coded_string() + except IndexError: + # No more columns in this row + # See https://github.com/PyMySQL/PyMySQL/pull/434 + break if data is not None: if encoding is not None: data = data.decode(encoding) @@ -1441,7 +1469,7 @@ def send_data(self): """Send data packets from the local file to the server""" - if not self.connection.socket: + if not self.connection._sock: raise err.InterfaceError("(0, '')") conn = self.connection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/constants/FIELD_TYPE.py new/PyMySQL-0.7.5/pymysql/constants/FIELD_TYPE.py --- old/PyMySQL-0.7.2/pymysql/constants/FIELD_TYPE.py 2014-08-29 13:32:54.000000000 +0200 +++ new/PyMySQL-0.7.5/pymysql/constants/FIELD_TYPE.py 2016-06-28 13:27:57.000000000 +0200 @@ -17,6 +17,7 @@ NEWDATE = 14 VARCHAR = 15 BIT = 16 +JSON = 245 NEWDECIMAL = 246 ENUM = 247 SET = 248 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/converters.py new/PyMySQL-0.7.5/pymysql/converters.py --- old/PyMySQL-0.7.2/pymysql/converters.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/converters.py 2016-05-24 09:22:27.000000000 +0200 @@ -109,7 +109,7 @@ return u"'%s'" % _escape_unicode(value) def escape_str(value, mapping=None): - return "'%s'" % escape_string(value, mapping) + return "'%s'" % escape_string(str(value), mapping) def escape_None(value, mapping=None): return 'NULL' @@ -161,6 +161,8 @@ True """ + if not PY2 and isinstance(obj, (bytes, bytearray)): + obj = obj.decode('ascii') if ' ' in obj: sep = ' ' elif 'T' in obj: @@ -196,6 +198,8 @@ can accept values as (+|-)DD HH:MM:SS. The latter format will not be parsed correctly by this function. """ + if not PY2 and isinstance(obj, (bytes, bytearray)): + obj = obj.decode('ascii') try: microseconds = 0 if "." in obj: @@ -238,6 +242,8 @@ to be treated as time-of-day and not a time offset, then you can use set this function as the converter for FIELD_TYPE.TIME. """ + if not PY2 and isinstance(obj, (bytes, bytearray)): + obj = obj.decode('ascii') try: microseconds = 0 if "." in obj: @@ -263,6 +269,8 @@ True """ + if not PY2 and isinstance(obj, (bytes, bytearray)): + obj = obj.decode('ascii') try: return datetime.date(*[ int(x) for x in obj.split('-', 2) ]) except ValueError: @@ -290,6 +298,8 @@ True """ + if not PY2 and isinstance(timestamp, (bytes, bytearray)): + timestamp = timestamp.decode('ascii') if timestamp[4] == '-': return convert_datetime(timestamp) timestamp += "0"*(14-len(timestamp)) # padding @@ -302,6 +312,8 @@ return None def convert_set(s): + if isinstance(s, (bytes, bytearray)): + return set(s.split(b",")) return set(s.split(",")) @@ -343,6 +355,7 @@ tuple: escape_sequence, list: escape_sequence, set: escape_sequence, + frozenset: escape_sequence, dict: escape_dict, bytearray: escape_bytes, type(None): escape_None, @@ -385,7 +398,6 @@ # for MySQLdb compatibility -conversions = decoders - -def Thing2Literal(obj): - return escape_str(str(obj)) +conversions = encoders.copy() +conversions.update(decoders) +Thing2Literal = escape_str diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/cursors.py new/PyMySQL-0.7.5/pymysql/cursors.py --- old/PyMySQL-0.7.2/pymysql/cursors.py 2016-02-24 11:35:47.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/cursors.py 2016-06-28 13:27:57.000000000 +0200 @@ -12,8 +12,11 @@ #: Regular expression for :meth:`Cursor.executemany`. #: executemany only suports simple bulk insert. #: You can use it to load large dataset. -RE_INSERT_VALUES = re.compile(r"""(INSERT\s.+\sVALUES\s+)(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))(\s*(?:ON DUPLICATE.*)?)\Z""", - re.IGNORECASE | re.DOTALL) +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", + re.IGNORECASE | re.DOTALL) class Cursor(object): @@ -141,7 +144,7 @@ :param str query: Query to execute. - :param args: arameters used with query. (optional) + :param args: parameters used with query. (optional) :type args: tuple, list or dict :return: Number of affected rows @@ -160,17 +163,23 @@ return result def executemany(self, query, args): + # type: (str, list) -> int """Run several data against one query - PyMySQL can execute bulkinsert for query like 'INSERT ... VALUES (%s)'. - In other form of queries, just run :meth:`execute` many times. + :param query: query to execute on server + :param args: Sequence of sequences or mappings. It is used as parameter. + :return: Number of rows affected, if any. + + This method improves performance on multiple-row INSERT and + REPLACE. Otherwise it is equivalent to looping over args with + execute(). """ if not args: return m = RE_INSERT_VALUES.match(query) if m: - q_prefix = m.group(1) + q_prefix = m.group(1) % () q_values = m.group(2).rstrip() q_postfix = m.group(3) or '' assert q_values[0] == '(' and q_values[-1] == ')' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/__init__.py new/PyMySQL-0.7.5/pymysql/tests/__init__.py --- old/PyMySQL-0.7.2/pymysql/tests/__init__.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/tests/__init__.py 2016-06-14 10:59:53.000000000 +0200 @@ -1,12 +1,14 @@ -from pymysql.tests.test_issues import * -from pymysql.tests.test_basic import * -from pymysql.tests.test_nextset import * +# Sorted by alphabetical order from pymysql.tests.test_DictCursor import * -from pymysql.tests.test_connection import * from pymysql.tests.test_SSCursor import * +from pymysql.tests.test_basic import * +from pymysql.tests.test_connection import * +from pymysql.tests.test_converters import * +from pymysql.tests.test_cursor import * +from pymysql.tests.test_issues import * from pymysql.tests.test_load_local import * +from pymysql.tests.test_nextset import * from pymysql.tests.test_optionfile import * -from pymysql.tests.test_converters import * from pymysql.tests.thirdparty import * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/test_basic.py new/PyMySQL-0.7.5/pymysql/tests/test_basic.py --- old/PyMySQL-0.7.2/pymysql/tests/test_basic.py 2015-02-28 19:44:12.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/tests/test_basic.py 2016-05-24 09:22:27.000000000 +0200 @@ -42,11 +42,15 @@ c.execute("delete from test_datatypes") - # check sequence type - c.execute("insert into test_datatypes (i, l) values (2,4), (6,8), (10,12)") - c.execute("select l from test_datatypes where i in %s order by i", ((2,6),)) - r = c.fetchall() - self.assertEqual(((4,),(8,)), r) + # check sequences type + for seq_type in (tuple, list, set, frozenset): + c.execute("insert into test_datatypes (i, l) values (2,4), (6,8), (10,12)") + seq = seq_type([2,6]) + c.execute("select l from test_datatypes where i in %s order by i", (seq,)) + r = c.fetchall() + self.assertEqual(((4,),(8,)), r) + c.execute("delete from test_datatypes") + finally: c.execute("drop table test_datatypes") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/test_cursor.py new/PyMySQL-0.7.5/pymysql/tests/test_cursor.py --- old/PyMySQL-0.7.2/pymysql/tests/test_cursor.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/tests/test_cursor.py 2016-06-14 10:59:53.000000000 +0200 @@ -34,18 +34,8 @@ c2 = conn.cursor() - with warnings.catch_warnings(record=True) as log: - warnings.filterwarnings("always") - - c2.execute("select 1") - - self.assertGreater(len(log), 0) - self.assertEqual( - "Previous unbuffered result was left incomplete", - str(log[-1].message)) - self.assertEqual( - c2.fetchone(), (1,) - ) + c2.execute("select 1") + self.assertEqual(c2.fetchone(), (1,)) self.assertIsNone(c2.fetchone()) def test_cleanup_rows_buffered(self): @@ -89,13 +79,26 @@ self.assertIsNotNone(m, 'error parse %(id_name)s') self.assertEqual(m.group(3), ' ON duplicate update', 'group 3 not ON duplicate update, bug in RE_INSERT_VALUES?') - # cursor._executed myst bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" + # cursor._executed must bee "insert into test (data) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)" # list args - data = xrange(10) + data = range(10) cursor.executemany("insert into test (data) values (%s)", data) - self.assertTrue(cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %s not in one query') + self.assertTrue(cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %s not in one query') # dict args - data_dict = [{'data': i} for i in xrange(10)] + data_dict = [{'data': i} for i in range(10)] cursor.executemany("insert into test (data) values (%(data)s)", data_dict) - self.assertTrue(cursor._executed.endswith(",(7),(8),(9)"), 'execute many with %(data)s not in one query') + self.assertTrue(cursor._executed.endswith(b",(7),(8),(9)"), 'execute many with %(data)s not in one query') + + # %% in column set + cursor.execute("""\ + CREATE TABLE percent_test ( + `A%` INTEGER, + `B%` INTEGER)""") + try: + q = "INSERT INTO percent_test (`A%%`, `B%%`) VALUES (%s, %s)" + self.assertIsNotNone(pymysql.cursors.RE_INSERT_VALUES.match(q)) + cursor.executemany(q, [(3, 4), (5, 6)]) + self.assertTrue(cursor._executed.endswith(b"(3, 4),(5, 6)"), "executemany with %% not in one query") + finally: + cursor.execute("DROP TABLE IF EXISTS percent_test") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/test_example.py new/PyMySQL-0.7.5/pymysql/tests/test_example.py --- old/PyMySQL-0.7.2/pymysql/tests/test_example.py 2014-08-29 13:32:54.000000000 +0200 +++ new/PyMySQL-0.7.5/pymysql/tests/test_example.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,32 +0,0 @@ -import pymysql -from pymysql.tests import base - -class TestExample(base.PyMySQLTestCase): - def test_example(self): - conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='mysql') - - - cur = conn.cursor() - - cur.execute("SELECT Host,User FROM user") - - # print cur.description - - # r = cur.fetchall() - # print r - # ...or... - u = False - - for r in cur.fetchall(): - u = u or conn.user in r - - self.assertTrue(u) - - cur.close() - conn.close() - -__all__ = ["TestExample"] - -if __name__ == "__main__": - import unittest - unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/test_issues.py new/PyMySQL-0.7.5/pymysql/tests/test_issues.py --- old/PyMySQL-0.7.2/pymysql/tests/test_issues.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/tests/test_issues.py 2016-06-28 13:58:08.000000000 +0200 @@ -457,7 +457,7 @@ # select WKT query = "SELECT AsText(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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/pymysql/tests/test_load_local.py new/PyMySQL-0.7.5/pymysql/tests/test_load_local.py --- old/PyMySQL-0.7.2/pymysql/tests/test_load_local.py 2016-02-24 11:30:58.000000000 +0100 +++ new/PyMySQL-0.7.5/pymysql/tests/test_load_local.py 2016-06-14 10:59:53.000000000 +0200 @@ -1,4 +1,4 @@ -from pymysql import OperationalError, Warning +from pymysql import cursors, OperationalError, Warning from pymysql.tests import base import os @@ -42,6 +42,28 @@ finally: c.execute("DROP TABLE test_load_local") + def test_unbuffered_load_file(self): + """Test unbuffered load local infile with a valid file""" + conn = self.connections[0] + c = conn.cursor(cursors.SSCursor) + c.execute("CREATE TABLE test_load_local (a INTEGER, b INTEGER)") + filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'data', + 'load_local_data.txt') + try: + c.execute( + ("LOAD DATA LOCAL INFILE '{0}' INTO TABLE " + + "test_load_local FIELDS TERMINATED BY ','").format(filename) + ) + c.execute("SELECT COUNT(*) FROM test_load_local") + self.assertEqual(22749, c.fetchone()[0]) + finally: + c.close() + conn.close() + conn.connect() + c = conn.cursor() + c.execute("DROP TABLE test_load_local") + def test_load_warnings(self): """Test load local infile produces the appropriate warnings""" conn = self.connections[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/runtests.py new/PyMySQL-0.7.5/runtests.py --- old/PyMySQL-0.7.2/runtests.py 2015-02-04 18:10:29.000000000 +0100 +++ new/PyMySQL-0.7.5/runtests.py 2016-05-18 11:03:00.000000000 +0200 @@ -10,6 +10,7 @@ @atexit.register def report_uncollectable(): + import gc if not gc.garbage: print("No garbages!") return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/setup.cfg new/PyMySQL-0.7.5/setup.cfg --- old/PyMySQL-0.7.2/setup.cfg 2016-02-24 11:40:57.000000000 +0100 +++ new/PyMySQL-0.7.5/setup.cfg 2016-06-28 14:18:51.000000000 +0200 @@ -7,7 +7,7 @@ max-line-length = 119 [egg_info] -tag_svn_revision = 0 -tag_date = 0 tag_build = +tag_date = 0 +tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyMySQL-0.7.2/setup.py new/PyMySQL-0.7.5/setup.py --- old/PyMySQL-0.7.2/setup.py 2016-02-24 11:39:26.000000000 +0100 +++ new/PyMySQL-0.7.5/setup.py 2016-06-14 10:59:53.000000000 +0200 @@ -20,15 +20,14 @@ license="MIT", packages=find_packages(), classifiers=[ + 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', - 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Topic :: Database',