From b9aa0530a855ef3cb23d84e50dea47bf3fc14f99 Mon Sep 17 00:00:00 2001 From: Niko Wenselowski Date: Thu, 9 Jan 2014 18:03:53 +0100 Subject: [PATCH 01/35] Improved support for Python 3. --- MySQLdb/connections.py | 6 +++++- MySQLdb/cursors.py | 4 ++-- setup_posix.py | 6 +++++- tests/test_MySQLdb_capabilities.py | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 908706a3..13e55c4a 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -33,7 +33,11 @@ def defaulterrorhandler(connection, cursor, errorclass, errorvalue): connection.messages.append(error) del cursor del connection - raise errorclass, errorvalue + if errorclass is not None: + raise errorclass(errorvalue) + else: + raise Exception(errorvalue) + re_numeric_part = re.compile(r"^(\d+)") diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index 348a586a..b45ddaf9 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -188,7 +188,7 @@ def execute(self, query, args=None): try: r = None r = self._query(query) - except TypeError, m: + except TypeError as m: if m.args[0] in ("not enough arguments for format string", "not all arguments converted"): self.messages.append((ProgrammingError, m.args[0])) @@ -247,7 +247,7 @@ def executemany(self, query, args): for key, item in a.iteritems())) else: q.append(qv % tuple([db.literal(item) for item in a])) - except TypeError, msg: + except TypeError as msg: if msg.args[0] in ("not enough arguments for format string", "not all arguments converted"): self.errorhandler(self, ProgrammingError, msg.args[0]) diff --git a/setup_posix.py b/setup_posix.py index cfcf33ca..dc9b90bf 100644 --- a/setup_posix.py +++ b/setup_posix.py @@ -1,5 +1,9 @@ import os, sys -from ConfigParser import SafeConfigParser +try: + from ConfigParser import SafeConfigParser +except ImportError: + # Probably running Python 3.x + from configparser import ConfigParser as SafeConfigParser # This dequote() business is required for some older versions # of mysql_config diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index ead69822..31e5f6fc 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -76,7 +76,7 @@ def test_bug_2671682(self): from MySQLdb.constants import ER try: self.cursor.execute("describe some_non_existent_table"); - except self.connection.ProgrammingError, msg: + except self.connection.ProgrammingError as msg: self.assertEquals(msg[0], ER.NO_SUCH_TABLE) def test_bug_3514287(self): From 0773933caead46776ceb5cd86abccfe840e49839 Mon Sep 17 00:00:00 2001 From: Niko Wenselowski Date: Fri, 10 Jan 2014 10:01:17 +0100 Subject: [PATCH 02/35] Changing the way checks for the error code are made. --- tests/test_MySQLdb_capabilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_MySQLdb_capabilities.py b/tests/test_MySQLdb_capabilities.py index 31e5f6fc..c45353fd 100644 --- a/tests/test_MySQLdb_capabilities.py +++ b/tests/test_MySQLdb_capabilities.py @@ -77,7 +77,7 @@ def test_bug_2671682(self): try: self.cursor.execute("describe some_non_existent_table"); except self.connection.ProgrammingError as msg: - self.assertEquals(msg[0], ER.NO_SUCH_TABLE) + self.assertTrue(str(ER.NO_SUCH_TABLE) in str(msg)) def test_bug_3514287(self): c = self.cursor From 4d6151796390915fadafa3b677a49b142b2ceca3 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 15 Apr 2014 10:35:18 -0700 Subject: [PATCH 03/35] Remove MySQLdb/release.py from git because it's generated from metadata.cfg and because I keep getting annoying diffs because of newline differences -- e.g.: $ git diff diff --git a/MySQLdb/release.py b/MySQLdb/release.py index 5c30a6c..4ce9412 100644 --- a/MySQLdb/release.py +++ b/MySQLdb/release.py @@ -1,4 +1,4 @@ - -__author__ = "Andy Dustman " -version_info = (1,2,4,'final',1) -__version__ = "1.2.4" + +__author__ = "Andy Dustman " +version_info = (1,2,4,'final',1) +__version__ = "1.2.4" --- MySQLdb/release.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 MySQLdb/release.py diff --git a/MySQLdb/release.py b/MySQLdb/release.py deleted file mode 100644 index 5c30a6c2..00000000 --- a/MySQLdb/release.py +++ /dev/null @@ -1,4 +0,0 @@ - -__author__ = "Andy Dustman " -version_info = (1,2,4,'final',1) -__version__ = "1.2.4" From 283299a08c55266d1be1fd1424a05f51691bf9e0 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 15 Apr 2014 11:25:48 -0700 Subject: [PATCH 04/35] Support Python 2.7, 3.3, 3.4 These are reasonably modern versions of Python that I think are worth supporting for the planned 1.3.0 release. --- README.md | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0ca0321..c2331558 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ TODO ---- * A bugfix 1.2.4 release -* A 1.3.0 release that will support Python 2.7-3.3 +* A 1.3.0 release that will support Python 2.7-3.4 * The 2.0 version is being renamed [moist][] and is heavily refactored. Projects diff --git a/tox.ini b/tox.ini index e7bb9713..1ac0a014 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py25, py26, py27, py33 +envlist = py27, py33, py34 [testenv] setenv = From 7591a439495c15d8793b503bb802882ab9a074c5 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 15 Apr 2014 10:18:21 -0700 Subject: [PATCH 05/35] tox.ini: Remove ipdb It makes tests take longer to install deps and now causes this error on Python < 2.7, because of IPython 2.0.0 requiring 2.7: ERROR: IPython requires Python Version 2.7 or above. --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index e7bb9713..350e3f76 100644 --- a/tox.ini +++ b/tox.ini @@ -7,5 +7,4 @@ setenv = commands = nosetests {posargs:-w tests -v} deps = - ipdb nose From 5fc7131cc0b96261161d34a107a4ed1ec9e02c05 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 16 Apr 2014 22:31:48 +0900 Subject: [PATCH 06/35] Some Python 3 C-API fixes. --- _mysql.c | 161 ++++++++++++++----------------------------------------- 1 file changed, 41 insertions(+), 120 deletions(-) diff --git a/_mysql.c b/_mysql.c index 5b81c79d..eec3235e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -32,6 +32,7 @@ PERFORMANCE OF THIS SOFTWARE. #define PyInt_FromLong(n) PyLong_FromLong(n) #define PyInt_Check(n) PyLong_Check(n) #define PyInt_AS_LONG(n) PyLong_AS_LONG(n) +#define PyString_FromString(s) PyUnicode_FromString(s) #endif #if PY_VERSION_HEX > 0x02060000 #include "bytesobject.h" @@ -134,11 +135,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) if (!_mysql_server_init_done) { e = _mysql_InternalError; PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); -#ifdef IS_PY3K - PyTuple_SET_ITEM(t, 1, PyUnicode_FromString("server not initialized")); -#else PyTuple_SET_ITEM(t, 1, PyString_FromString("server not initialized")); -#endif PyErr_SetObject(e, t); Py_DECREF(t); return NULL; @@ -148,11 +145,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) e = _mysql_InterfaceError; else if (merr > CR_MAX_ERROR) { PyTuple_SET_ITEM(t, 0, PyInt_FromLong(-1L)); -#ifdef IS_PY3K - PyTuple_SET_ITEM(t, 1, PyUnicode_FromString("error totally whack")); -#else PyTuple_SET_ITEM(t, 1, PyString_FromString("error totally whack")); -#endif PyErr_SetObject(_mysql_InterfaceError, t); Py_DECREF(t); return NULL; @@ -240,11 +233,7 @@ _mysql_Exception(_mysql_ConnectionObject *c) break; } PyTuple_SET_ITEM(t, 0, PyInt_FromLong((long)merr)); -#ifdef IS_PY3K - PyTuple_SET_ITEM(t, 1, PyUnicode_FromString(mysql_error(&(c->connection)))); -#else PyTuple_SET_ITEM(t, 1, PyString_FromString(mysql_error(&(c->connection)))); -#endif PyErr_SetObject(e, t); Py_DECREF(t); return NULL; @@ -1031,11 +1020,7 @@ _mysql_ConnectionObject_sqlstate( PyObject *args) { if (!PyArg_ParseTuple(args, "")) return NULL; -#ifdef IS_PY3K - return PyUnicode_FromString(mysql_sqlstate(&(self->connection))); -#else return PyString_FromString(mysql_sqlstate(&(self->connection))); -#endif } static char _mysql_ConnectionObject_warning_count__doc__[] = @@ -1084,11 +1069,7 @@ _mysql_ConnectionObject_error( { if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); -#ifdef IS_PY3K - return PyUnicode_FromString(mysql_error(&(self->connection))); -#else return PyString_FromString(mysql_error(&(self->connection))); -#endif } static char _mysql_escape_string__doc__[] = @@ -1115,9 +1096,9 @@ _mysql_escape_string( #endif if (!str) return PyErr_NoMemory(); #ifdef IS_PY3K - out = PyUnicode_AS_DATA(str); + out = PyUnicode_AS_DATA(str); #else - out = PyString_AS_STRING(str); + out = PyString_AS_STRING(str); #endif #if MYSQL_VERSION_ID < 32321 len = mysql_escape_string(out, in, size); @@ -2650,67 +2631,26 @@ static MyMemberlist(_mysql_ResultObject_memberlist)[] = { }; static PyObject * -_mysql_ConnectionObject_getattr( +_mysql_ConnectionObject_getattro( _mysql_ConnectionObject *self, - char *name) + PyObject *name) { -#ifndef IS_PY3K - PyObject *res; - - res = Py_FindMethod(_mysql_ConnectionObject_methods, (PyObject *)self, name); - if (res != NULL) - return res; - PyErr_Clear(); -#endif - if (strcmp(name, "closed") == 0) - return PyInt_FromLong((long)!(self->open)); -#if PY_VERSION_HEX < 0x02020000 - return PyMember_Get((char *)self, _mysql_ConnectionObject_memberlist, name); + char *cname; +#ifdef IS_PY3K + cname = PyUnicode_AsUTF8(name); #else - { - MyMemberlist(*l); - for (l = _mysql_ConnectionObject_memberlist; l->name != NULL; l++) { - if (strcmp(l->name, name) == 0) - return PyMember_GetOne((char *)self, l); - } - PyErr_SetString(PyExc_AttributeError, name); - return NULL; - } + cname = PyString_AsString(name); #endif -} - -static PyObject * -_mysql_ResultObject_getattr( - _mysql_ResultObject *self, - char *name) -{ -#ifndef IS_PY3K - PyObject *res; + if (strcmp(cname, "closed") == 0) + return PyInt_FromLong((long)!(self->open)); - res = Py_FindMethod(_mysql_ResultObject_methods, (PyObject *)self, name); - if (res != NULL) - return res; - PyErr_Clear(); -#endif -#if PY_VERSION_HEX < 0x02020000 - return PyMember_Get((char *)self, _mysql_ResultObject_memberlist, name); -#else - { - MyMemberlist(*l); - for (l = _mysql_ResultObject_memberlist; l->name != NULL; l++) { - if (strcmp(l->name, name) == 0) - return PyMember_GetOne((char *)self, l); - } - PyErr_SetString(PyExc_AttributeError, name); - return NULL; - } -#endif + return PyObject_GenericGetAttr(self, name); } static int -_mysql_ConnectionObject_setattr( +_mysql_ConnectionObject_setattro( _mysql_ConnectionObject *self, - char *name, + PyObject *name, PyObject *v) { if (v == NULL) { @@ -2718,24 +2658,13 @@ _mysql_ConnectionObject_setattr( "can't delete connection attributes"); return -1; } -#if PY_VERSION_HEX < 0x02020000 - return PyMember_Set((char *)self, _mysql_ConnectionObject_memberlist, name, v); -#else - { - MyMemberlist(*l); - for (l = _mysql_ConnectionObject_memberlist; l->name != NULL; l++) - if (strcmp(l->name, name) == 0) - return PyMember_SetOne((char *)self, l, v); - } - PyErr_SetString(PyExc_AttributeError, name); - return -1; -#endif + return PyObject_GenericSetAttr(self, name, v); } static int -_mysql_ResultObject_setattr( +_mysql_ResultObject_setattro( _mysql_ResultObject *self, - char *name, + PyObject *name, PyObject *v) { if (v == NULL) { @@ -2743,18 +2672,7 @@ _mysql_ResultObject_setattr( "can't delete connection attributes"); return -1; } -#if PY_VERSION_HEX < 0x02020000 - return PyMember_Set((char *)self, _mysql_ResultObject_memberlist, name, v); -#else - { - MyMemberlist(*l); - for (l = _mysql_ResultObject_memberlist; l->name != NULL; l++) - if (strcmp(l->name, name) == 0) - return PyMember_SetOne((char *)self, l, v); - } - PyErr_SetString(PyExc_AttributeError, name); - return -1; -#endif + return PyObject_GenericSetAttr(self, name, v); } PyTypeObject _mysql_ConnectionObject_Type = { @@ -2769,8 +2687,8 @@ PyTypeObject _mysql_ConnectionObject_Type = { 0, (destructor)_mysql_ConnectionObject_dealloc, /* tp_dealloc */ 0, /*tp_print*/ - (getattrfunc)_mysql_ConnectionObject_getattr, /* tp_getattr */ - (setattrfunc)_mysql_ConnectionObject_setattr, /* tp_setattr */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ 0, /*tp_compare*/ (reprfunc)_mysql_ConnectionObject_repr, /* tp_repr */ @@ -2785,8 +2703,8 @@ PyTypeObject _mysql_ConnectionObject_Type = { 0, /* (hashfunc) tp_hash */ 0, /* (ternaryfunc) tp_call */ 0, /* (reprfunc) tp_str */ - 0, /* (getattrofunc) tp_getattro */ - 0, /* (setattrofunc) tp_setattro */ + (getattrofunc)_mysql_ConnectionObject_getattro, /* tp_getattro */ + (setattrofunc)_mysql_ConnectionObject_setattro, /* tp_setattro */ /* Functions to access object as input/output buffer */ 0, /* (PyBufferProcs *) tp_as_buffer */ @@ -2857,8 +2775,8 @@ PyTypeObject _mysql_ResultObject_Type = { 0, (destructor)_mysql_ResultObject_dealloc, /* tp_dealloc */ 0, /*tp_print*/ - (getattrfunc)_mysql_ResultObject_getattr, /* tp_getattr */ - (setattrfunc)_mysql_ResultObject_setattr, /* tp_setattr */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ 0, /*tp_compare*/ (reprfunc)_mysql_ResultObject_repr, /* tp_repr */ @@ -2873,8 +2791,8 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* (hashfunc) tp_hash */ 0, /* (ternaryfunc) tp_call */ 0, /* (reprfunc) tp_str */ - 0, /* (getattrofunc) tp_getattro */ - 0, /* (setattrofunc) tp_setattro */ + 0, /* tp_getattro */ + (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ /* Functions to access object as input/output buffer */ 0, /* (PyBufferProcs *) tp_as_buffer */ @@ -3059,18 +2977,8 @@ init_mysql(void) #endif { PyObject *dict, *module, *emod, *edict; -#ifdef IS_PY3K - module = PyModule_Create(&_mysqlmodule); - if (!module) return module; /* this really should never happen */ -#else - module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, - (PyObject *)NULL, PYTHON_API_VERSION); - if (!module) return; /* this really should never happen */ -#endif -#ifdef IS_PY3K - Py_TYPE(&_mysql_ConnectionObject_Type) = &PyType_Type; - Py_TYPE(&_mysql_ResultObject_Type) = &PyType_Type; -#else + +#ifndef IS_PY3K _mysql_ConnectionObject_Type.ob_type = &PyType_Type; _mysql_ResultObject_Type.ob_type = &PyType_Type; #endif @@ -3085,6 +2993,19 @@ init_mysql(void) #ifndef IS_PY3K _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; #endif +#endif +#ifdef IS_PY3K + if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) + return NULL; + if (PyType_Ready(&_mysql_ResultObject_Type) < 0) + return NULL; + + module = PyModule_Create(&_mysqlmodule); + if (!module) return module; /* this really should never happen */ +#else + module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, + (PyObject *)NULL, PYTHON_API_VERSION); + if (!module) return; /* this really should never happen */ #endif if (!(dict = PyModule_GetDict(module))) goto error; From 8b8444fd89462ba2303c2ff27cf723fa862d8558 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 16 Apr 2014 23:29:05 +0900 Subject: [PATCH 07/35] Fix memory corruption on Python 3. --- _mysql.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/_mysql.c b/_mysql.c index eec3235e..25d8646e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -60,7 +60,7 @@ PERFORMANCE OF THIS SOFTWARE. # define MyMemberlist(x) struct PyMemberDef x # define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) #ifdef IS_PY3K -# define MyFree(o) PyObject_Del(o) +# define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) #else # define MyFree(ob) ob->ob_type->tp_free((PyObject *)ob) #endif @@ -924,7 +924,7 @@ _mysql_ConnectionObject_commit( if (err) return _mysql_Exception(self); Py_INCREF(Py_None); return Py_None; -} +} static char _mysql_ConnectionObject_rollback__doc__[] = "Rolls backs the current transaction\n\ @@ -2985,14 +2985,8 @@ init_mysql(void) #if PY_VERSION_HEX >= 0x02020000 _mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc; _mysql_ConnectionObject_Type.tp_new = PyType_GenericNew; -#ifndef IS_PY3K - _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; -#endif _mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc; _mysql_ResultObject_Type.tp_new = PyType_GenericNew; -#ifndef IS_PY3K - _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; -#endif #endif #ifdef IS_PY3K if (PyType_Ready(&_mysql_ConnectionObject_Type) < 0) From 690e5e9325b835e31b3db868dc23bdb0dd0b6e5d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 16 Apr 2014 23:33:17 +0900 Subject: [PATCH 08/35] Fix getattr on result object. --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 25d8646e..2dc5f959 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2791,7 +2791,7 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* (hashfunc) tp_hash */ 0, /* (ternaryfunc) tp_call */ 0, /* (reprfunc) tp_str */ - 0, /* tp_getattro */ + (getattrofunc)PyObject_GenericGetAttr, /* tp_getattro */ (setattrofunc)_mysql_ResultObject_setattro, /* tp_setattr */ /* Functions to access object as input/output buffer */ From 80f5a4a66f47a1460905ea5579ff826ba1043f43 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 00:44:43 +0900 Subject: [PATCH 09/35] Fix segfault on Python 2. --- _mysql.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_mysql.c b/_mysql.c index 2dc5f959..f222182e 100644 --- a/_mysql.c +++ b/_mysql.c @@ -2997,6 +2997,8 @@ init_mysql(void) module = PyModule_Create(&_mysqlmodule); if (!module) return module; /* this really should never happen */ #else + _mysql_ConnectionObject_Type.tp_free = _PyObject_GC_Del; + _mysql_ResultObject_Type.tp_free = _PyObject_GC_Del; module = Py_InitModule4("_mysql", _mysql_methods, _mysql___doc__, (PyObject *)NULL, PYTHON_API_VERSION); if (!module) return; /* this really should never happen */ From 2556226b3e7a5e9d1750bd640ae712ad45ca5753 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 01:05:38 +0900 Subject: [PATCH 10/35] Cleanup Remove macros for Python <2.6 --- _mysql.c | 93 ++++++++++++-------------------------------------------- 1 file changed, 20 insertions(+), 73 deletions(-) diff --git a/_mysql.c b/_mysql.c index f222182e..c1a440bb 100644 --- a/_mysql.c +++ b/_mysql.c @@ -34,9 +34,8 @@ PERFORMANCE OF THIS SOFTWARE. #define PyInt_AS_LONG(n) PyLong_AS_LONG(n) #define PyString_FromString(s) PyUnicode_FromString(s) #endif -#if PY_VERSION_HEX > 0x02060000 + #include "bytesobject.h" -#endif #include "pymemcompat.h" #include "structmember.h" #if defined(MS_WINDOWS) @@ -48,29 +47,15 @@ PERFORMANCE OF THIS SOFTWARE. #include "mysqld_error.h" #include "errmsg.h" -#if PY_VERSION_HEX < 0x02020000 -# define MyTuple_Resize(t,n,d) _PyTuple_Resize(t, n, d) -# define MyMember(a,b,c,d,e) {a,b,c,d} -# define MyMemberlist(x) struct memberlist x -# define MyAlloc(s,t) PyObject_New(s,&t) -# define MyFree(o) PyObject_Del(o) -#else -# define MyTuple_Resize(t,n,d) _PyTuple_Resize(t, n) -# define MyMember(a,b,c,d,e) {a,b,c,d,e} -# define MyMemberlist(x) struct PyMemberDef x -# define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) +#define MyTuple_Resize(t,n,d) _PyTuple_Resize(t, n) +#define MyMember(a,b,c,d,e) {a,b,c,d,e} +#define MyMemberlist(x) struct PyMemberDef x +#define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) #ifdef IS_PY3K # define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) #else # define MyFree(ob) ob->ob_type->tp_free((PyObject *)ob) #endif -#endif - -#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) -typedef int Py_ssize_t; -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif static PyObject *_mysql_MySQLError; static PyObject *_mysql_Warning; @@ -505,7 +490,6 @@ _mysql_ResultObject_Initialize( return 0; } -#if PY_VERSION_HEX >= 0x02020000 static int _mysql_ResultObject_traverse( _mysql_ResultObject *self, visitproc visit, @@ -519,7 +503,6 @@ static int _mysql_ResultObject_traverse( return visit(self->conn, arg); return 0; } -#endif static int _mysql_ResultObject_clear( _mysql_ResultObject *self) @@ -556,14 +539,14 @@ _mysql_ConnectionObject_Initialize( "client_flag", "ssl", "local_infile", #ifdef HAVE_MYSQL_OPT_TIMEOUTS - "read_timeout", - "write_timeout", + "read_timeout", + "write_timeout", #endif NULL } ; int connect_timeout = 0; #ifdef HAVE_MYSQL_OPT_TIMEOUTS - int read_timeout = 0; - int write_timeout = 0; + int read_timeout = 0; + int write_timeout = 0; #endif int compress = -1, named_pipe = -1, local_infile = -1; char *init_command=NULL, @@ -763,7 +746,6 @@ _mysql_connect( return (PyObject *) c; } -#if PY_VERSION_HEX >= 0x02020000 static int _mysql_ConnectionObject_traverse( _mysql_ConnectionObject *self, visitproc visit, @@ -773,7 +755,6 @@ static int _mysql_ConnectionObject_traverse( return visit(self->converter, arg); return 0; } -#endif static int _mysql_ConnectionObject_clear( _mysql_ConnectionObject *self) @@ -1282,10 +1263,10 @@ _mysql_escape_dict( if (!PyArg_ParseTuple(args, "O!O:escape_dict", &PyDict_Type, &o, &d)) goto error; if (!PyMapping_Check(d)) { - PyErr_SetString(PyExc_TypeError, - "argument 2 must be a mapping"); - return NULL; - } + PyErr_SetString(PyExc_TypeError, + "argument 2 must be a mapping"); + return NULL; + } if (!(r = PyDict_New())) goto error; while (PyDict_Next(o, &ppos, &pkey, &item)) { quoted = _escape_item(item, d); @@ -2709,35 +2690,22 @@ PyTypeObject _mysql_ConnectionObject_Type = { /* Functions to access object as input/output buffer */ 0, /* (PyBufferProcs *) tp_as_buffer */ - /* Flags to define presence of optional/expanded features */ -#if PY_VERSION_HEX < 0x02020000 - Py_TPFLAGS_DEFAULT, /* (long) tp_flags */ -#else + /* (tp_flags) Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, -#endif _mysql_connect__doc__, /* (char *) tp_doc Documentation string */ -#if PY_VERSION_HEX >= 0x02000000 - /* Assigned meaning in release 2.0 */ -#if PY_VERSION_HEX >= 0x02020000 + /* call function for all accessible objects */ (traverseproc) _mysql_ConnectionObject_traverse, /* tp_traverse */ /* delete references to contained objects */ (inquiry) _mysql_ConnectionObject_clear, /* tp_clear */ -#else - /* not supporting pre-2.2 GC */ - 0, - 0, -#endif -#if PY_VERSION_HEX >= 0x02010000 - /* Assigned meaning in release 2.1 */ + /* rich comparisons */ 0, /* (richcmpfunc) tp_richcompare */ /* weak reference enabler */ 0, /* (long) tp_weaklistoffset */ -#if PY_VERSION_HEX >= 0x02020000 - /* Added in release 2.2 */ + /* Iterators */ 0, /* (getiterfunc) tp_iter */ 0, /* (iternextfunc) tp_iternext */ @@ -2758,9 +2726,6 @@ PyTypeObject _mysql_ConnectionObject_Type = { 0, /* (PyObject *) tp_bases */ 0, /* (PyObject *) tp_mro method resolution order */ 0, /* (PyObject *) tp_defined */ -#endif /* python 2.2 */ -#endif /* python 2.1 */ -#endif /* python 2.0 */ } ; PyTypeObject _mysql_ResultObject_Type = { @@ -2798,35 +2763,22 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* (PyBufferProcs *) tp_as_buffer */ /* Flags to define presence of optional/expanded features */ -#if PY_VERSION_HEX < 0x02020000 - Py_TPFLAGS_DEFAULT, /* (long) tp_flags */ -#else Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, -#endif _mysql_ResultObject__doc__, /* (char *) tp_doc Documentation string */ -#if PY_VERSION_HEX >= 0x02000000 - /* Assigned meaning in release 2.0 */ -#if PY_VERSION_HEX >= 0x02020000 + /* call function for all accessible objects */ (traverseproc) _mysql_ResultObject_traverse, /* tp_traverse */ /* delete references to contained objects */ (inquiry) _mysql_ResultObject_clear, /* tp_clear */ -#else - /* not supporting pre-2.2 GC */ - 0, - 0, -#endif -#if PY_VERSION_HEX >= 0x02010000 - /* Assigned meaning in release 2.1 */ + /* rich comparisons */ 0, /* (richcmpfunc) tp_richcompare */ /* weak reference enabler */ 0, /* (long) tp_weaklistoffset */ -#if PY_VERSION_HEX >= 0x02020000 - /* Added in release 2.2 */ + /* Iterators */ 0, /* (getiterfunc) tp_iter */ 0, /* (iternextfunc) tp_iternext */ @@ -2847,9 +2799,6 @@ PyTypeObject _mysql_ResultObject_Type = { 0, /* (PyObject *) tp_bases */ 0, /* (PyObject *) tp_mro method resolution order */ 0, /* (PyObject *) tp_defined */ -#endif /* python 2.2 */ -#endif /* python 2.1 */ -#endif /* python 2.0 */ }; static PyMethodDef @@ -3081,5 +3030,3 @@ init_mysql(void) return module; #endif } - - From c4d89484496c1041df325e10532589e1200c0ce7 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 01:13:43 +0900 Subject: [PATCH 11/35] Remove more macros for ancient Python. --- _mysql.c | 59 +++++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/_mysql.c b/_mysql.c index c1a440bb..b7bf3cc3 100644 --- a/_mysql.c +++ b/_mysql.c @@ -47,9 +47,6 @@ PERFORMANCE OF THIS SOFTWARE. #include "mysqld_error.h" #include "errmsg.h" -#define MyTuple_Resize(t,n,d) _PyTuple_Resize(t, n) -#define MyMember(a,b,c,d,e) {a,b,c,d,e} -#define MyMemberlist(x) struct PyMemberDef x #define MyAlloc(s,t) (s *) t.tp_alloc(&t,0) #ifdef IS_PY3K # define MyFree(o) Py_TYPE(o)->tp_free((PyObject*)o) @@ -1505,7 +1502,7 @@ _mysql__fetch_row( goto error; } if (!row) { - if (MyTuple_Resize(r, i, 0) == -1) goto error; + if (_PyTuple_Resize(r, i) == -1) goto error; break; } v = convert_row(self, row); @@ -1568,7 +1565,7 @@ _mysql_ResultObject_fetch_row( if (rowsadded == -1) goto error; skiprows += rowsadded; if (rowsadded < maxrows) break; - if (MyTuple_Resize(&r, skiprows+maxrows, 0) == -1) + if (_PyTuple_Resize(&r, skiprows+maxrows) == -1) goto error; } } else { @@ -2509,42 +2506,42 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { {NULL, NULL} /* sentinel */ }; -static MyMemberlist(_mysql_ConnectionObject_memberlist)[] = { - MyMember( +static struct PyMemberDef _mysql_ConnectionObject_memberlist[] = { + { "open", T_INT, offsetof(_mysql_ConnectionObject,open), READONLY, "True if connection is open" - ), - MyMember( + }, + { "converter", T_OBJECT, offsetof(_mysql_ConnectionObject,converter), 0, "Type conversion mapping" - ), - MyMember( + }, + { "server_capabilities", T_UINT, offsetof(_mysql_ConnectionObject,connection.server_capabilities), READONLY, "Capabilites of server; consult MySQLdb.constants.CLIENT" - ), - MyMember( - "port", - T_UINT, - offsetof(_mysql_ConnectionObject,connection.port), - READONLY, - "TCP/IP port of the server connection" - ), - MyMember( - "client_flag", - T_UINT, - READONLY, - offsetof(_mysql_ConnectionObject,connection.client_flag), - "Client flags; refer to MySQLdb.constants.CLIENT" - ), + }, + { + "port", + T_UINT, + offsetof(_mysql_ConnectionObject,connection.port), + READONLY, + "TCP/IP port of the server connection" + }, + { + "client_flag", + T_UINT, + READONLY, + offsetof(_mysql_ConnectionObject,connection.client_flag), + "Client flags; refer to MySQLdb.constants.CLIENT" + }, {NULL} /* Sentinel */ }; @@ -2600,14 +2597,14 @@ static PyMethodDef _mysql_ResultObject_methods[] = { {NULL, NULL} /* sentinel */ }; -static MyMemberlist(_mysql_ResultObject_memberlist)[] = { - MyMember( +static struct PyMemberDef _mysql_ResultObject_memberlist[] = { + { "converter", T_OBJECT, offsetof(_mysql_ResultObject,converter), READONLY, "Type conversion mapping" - ), + }, {NULL} /* Sentinel */ }; @@ -2712,7 +2709,7 @@ PyTypeObject _mysql_ConnectionObject_Type = { /* Attribute descriptor and subclassing stuff */ (struct PyMethodDef *)_mysql_ConnectionObject_methods, /* tp_methods */ - (MyMemberlist(*))_mysql_ConnectionObject_memberlist, /* tp_members */ + (struct PyMemberDef *)_mysql_ConnectionObject_memberlist, /* tp_members */ 0, /* (struct getsetlist *) tp_getset; */ 0, /* (struct _typeobject *) tp_base; */ 0, /* (PyObject *) tp_dict */ @@ -2785,7 +2782,7 @@ PyTypeObject _mysql_ResultObject_Type = { /* Attribute descriptor and subclassing stuff */ (struct PyMethodDef *) _mysql_ResultObject_methods, /* tp_methods */ - (MyMemberlist(*)) _mysql_ResultObject_memberlist, /*tp_members */ + (struct PyMemberDef *) _mysql_ResultObject_memberlist, /*tp_members */ 0, /* (struct getsetlist *) tp_getset; */ 0, /* (struct _typeobject *) tp_base; */ 0, /* (PyObject *) tp_dict */ From 9440a0d4df6fe4e173b51004cdedaf9066a951bb Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Wed, 16 Apr 2014 14:13:06 -0700 Subject: [PATCH 12/35] Handle types in a Python 3 friendly way --- MySQLdb/compat.py | 8 ++++++++ MySQLdb/connections.py | 7 ++++--- MySQLdb/cursors.py | 11 +++-------- metadata.cfg | 1 + tests/capabilities.py | 2 ++ 5 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 MySQLdb/compat.py diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py new file mode 100644 index 00000000..4d84afe2 --- /dev/null +++ b/MySQLdb/compat.py @@ -0,0 +1,8 @@ +import sys + +if sys.version_info[0] == 3: + unicode = str + unichr = chr +else: + unicode = unicode + unichr = unichr diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 13e55c4a..a7adc99e 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -7,10 +7,11 @@ """ from MySQLdb import cursors +from MySQLdb.compat import unicode from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \ DatabaseError, OperationalError, IntegrityError, InternalError, \ NotSupportedError, ProgrammingError -import types, _mysql +import _mysql import re @@ -233,8 +234,8 @@ def string_decoder(s): self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder)) self.converter[FIELD_TYPE.BLOB].append((None, string_decoder)) - self.encoders[types.StringType] = string_literal - self.encoders[types.UnicodeType] = unicode_literal + self.encoders[bytes] = string_literal + self.encoders[unicode] = unicode_literal self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS if self._transactional: if autocommit is not None: diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index b45ddaf9..c19f2564 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -7,13 +7,8 @@ import re import sys -try: - from types import ListType, TupleType, UnicodeType -except ImportError: - # Python 3 - ListType = list - TupleType = tuple - UnicodeType = str + +from MySQLdb.compat import unicode restr = r""" \s @@ -305,7 +300,7 @@ def callproc(self, procname, args=()): q = "CALL %s(%s)" % (procname, ','.join(['@_%s_%d' % (procname, i) for i in range(len(args))])) - if type(q) is UnicodeType: + if isinstance(q, unicode): q = q.encode(db.unicode_literal.charset) self._query(q) self._executed = q diff --git a/metadata.cfg b/metadata.cfg index 49841c17..ea31c018 100644 --- a/metadata.cfg +++ b/metadata.cfg @@ -44,6 +44,7 @@ classifiers: Topic :: Database :: Database Engines/Servers py_modules: _mysql_exceptions + MySQLdb.compat MySQLdb.converters MySQLdb.connections MySQLdb.cursors diff --git a/tests/capabilities.py b/tests/capabilities.py index 076361cb..5b122cb5 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -10,6 +10,8 @@ import unittest from configdb import connection_factory +from MySQLdb.compat import unichr + class DatabaseTest(unittest.TestCase): From f8c046ecc84696b037462658e95e9ad584a90d6e Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Wed, 16 Apr 2014 15:29:05 -0700 Subject: [PATCH 13/35] .travis.yml: Add py33 and py34 but allow fail --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4fdfdae..4accb99f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,15 @@ language: python python: - - "2.6" - - "2.7" - - "pypy" + - 2.6 + - 2.7 + - pypy + - 3.3 + - 3.4 +matrix: + allow_failures: + - python: 3.3 + - python: 3.4 + fast_finish: true install: python setup.py install before_script: mysql -e 'create database mysqldb_test charset utf8;' script: TESTDB=travis.cnf nosetests From acc984c2474714180b6734e52dabc21a95aa7fe3 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 19:41:01 +0900 Subject: [PATCH 14/35] Fix escaping. escape_string() -- only accepts bytes string_literal() -- Use ASCII to encode str(o) --- _mysql.c | 107 ++++++++++--------------------------------------------- 1 file changed, 18 insertions(+), 89 deletions(-) diff --git a/_mysql.c b/_mysql.c index b7bf3cc3..4c16b660 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1066,18 +1066,10 @@ _mysql_escape_string( PyObject *str; char *in, *out; int len, size; - if (!PyArg_ParseTuple(args, "s#:escape_string", &in, &size)) return NULL; -#ifdef IS_PY3K - str = PyUnicode_FromStringAndSize((char *) NULL, size*2+1); -#else - str = PyString_FromStringAndSize((char *) NULL, size*2+1); -#endif + if (!PyArg_ParseTuple(args, "y#:escape_string", &in, &size)) return NULL; + str = PyBytes_FromStringAndSize((char *) NULL, size*2+1); if (!str) return PyErr_NoMemory(); -#ifdef IS_PY3K - out = PyUnicode_AS_DATA(str); -#else - out = PyString_AS_STRING(str); -#endif + out = PyBytes_AS_STRING(str); #if MYSQL_VERSION_ID < 32321 len = mysql_escape_string(out, in, size); #else @@ -1087,11 +1079,7 @@ _mysql_escape_string( else len = mysql_escape_string(out, in, size); #endif -#ifdef IS_PY3K - if (PyUnicode_Resize(&str, len) < 0) return NULL; -#else - if (_PyString_Resize(&str, len) < 0) return NULL; -#endif + if (_PyBytes_Resize(&str, len) < 0) return NULL; return (str); } @@ -1117,18 +1105,18 @@ _mysql_string_literal( s = PyObject_Str(o); if (!s) return NULL; #ifdef IS_PY3K - in = PyUnicode_AS_DATA(s); - size = PyUnicode_GetSize(s); - str = PyUnicode_FromStringAndSize((char *) NULL, size*2+3); - if (!str) return PyErr_NoMemory(); - out = PyUnicode_AS_DATA(str); -#else - in = PyString_AsString(s); - size = PyString_GET_SIZE(s); - str = PyString_FromStringAndSize((char *) NULL, size*2+3); - if (!str) return PyErr_NoMemory(); - out = PyString_AS_STRING(str); + { + PyObject *t = PyUnicode_AsASCIIString(s); + if (!t) return NULL; + Py_DECREF(s); + s = t; + } #endif + in = PyBytes_AsString(s); + size = PyBytes_GET_SIZE(s); + str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); + if (!str) return PyErr_NoMemory(); + out = PyBytes_AS_STRING(str); #if MYSQL_VERSION_ID < 32321 len = mysql_escape_string(out+1, in, size); #else @@ -1139,11 +1127,7 @@ _mysql_string_literal( len = mysql_escape_string(out+1, in, size); #endif *out = *(out+len+1) = '\''; -#ifdef IS_PY3K - if (PyUnicode_Resize(&str, len+2) < 0) return NULL; -#else - if (_PyString_Resize(&str, len+2) < 0) return NULL; -#endif + if (_PyBytes_Resize(&str, len+2) < 0) return NULL; Py_DECREF(s); return (str); } @@ -1643,11 +1627,7 @@ _mysql_ConnectionObject_character_set_name( #else s = "latin1"; #endif -#ifdef IS_PY3K - return PyUnicode_FromString(s); -#else return PyString_FromString(s); -#endif } #if MYSQL_VERSION_ID >= 50007 @@ -1708,18 +1688,6 @@ _mysql_ConnectionObject_get_character_set_info( check_connection(self); mysql_get_character_set_info(&(self->connection), &cs); if (!(result = PyDict_New())) return NULL; -#ifdef IS_PY3K - if (cs.csname) - PyDict_SetItemString(result, "name", PyUnicode_FromString(cs.csname)); - if (cs.name) - PyDict_SetItemString(result, "collation", PyUnicode_FromString(cs.name)); - if (cs.comment) - PyDict_SetItemString(result, "comment", PyUnicode_FromString(cs.comment)); - if (cs.dir) - PyDict_SetItemString(result, "dir", PyUnicode_FromString(cs.dir)); - PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); - PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); -#else if (cs.csname) PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); if (cs.name) @@ -1730,7 +1698,6 @@ _mysql_ConnectionObject_get_character_set_info( PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); -#endif return result; } #endif @@ -1745,11 +1712,7 @@ _mysql_get_client_info( { if (!PyArg_ParseTuple(args, "")) return NULL; check_server_init(NULL); -#ifdef IS_PY3K - return PyUnicode_FromString(mysql_get_client_info()); -#else return PyString_FromString(mysql_get_client_info()); -#endif } static char _mysql_ConnectionObject_get_host_info__doc__[] = @@ -1764,11 +1727,7 @@ _mysql_ConnectionObject_get_host_info( { if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); -#ifdef IS_PY3K - return PyUnicode_FromString(mysql_get_host_info(&(self->connection))); -#else return PyString_FromString(mysql_get_host_info(&(self->connection))); -#endif } static char _mysql_ConnectionObject_get_proto_info__doc__[] = @@ -1798,11 +1757,7 @@ _mysql_ConnectionObject_get_server_info( { if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); -#ifdef IS_PY3K - return PyUnicode_FromString(mysql_get_server_info(&(self->connection))); -#else return PyString_FromString(mysql_get_server_info(&(self->connection))); -#endif } static char _mysql_ConnectionObject_info__doc__[] = @@ -1820,11 +1775,7 @@ _mysql_ConnectionObject_info( if (!PyArg_ParseTuple(args, "")) return NULL; check_connection(self); s = mysql_info(&(self->connection)); -#ifdef IS_PY3K - if (s) return PyUnicode_FromString(s); -#else if (s) return PyString_FromString(s); -#endif Py_INCREF(Py_None); return Py_None; } @@ -2067,12 +2018,7 @@ _mysql_ConnectionObject_stat( s = mysql_stat(&(self->connection)); Py_END_ALLOW_THREADS if (!s) return _mysql_Exception(self); -#ifdef IS_PY3K - return PyUnicode_FromString(s); -#else return PyString_FromString(s); -#endif - } static char _mysql_ConnectionObject_store_result__doc__[] = @@ -2198,11 +2144,7 @@ _mysql_ConnectionObject_repr( else sprintf(buf, "<_mysql.connection closed at %lx>", (long)self); -#ifdef IS_PY3K - return PyUnicode_FromString(buf); -#else return PyString_FromString(buf); -#endif } static char _mysql_ResultObject_data_seek__doc__[] = @@ -2276,13 +2218,8 @@ _mysql_ResultObject_repr( _mysql_ResultObject *self) { char buf[300]; - sprintf(buf, "<_mysql.result object at %lx>", - (long)self); -#ifdef IS_PY3K - return PyUnicode_FromString(buf); -#else + sprintf(buf, "<_mysql.result object at %lx>", (long)self); return PyString_FromString(buf); -#endif } static PyMethodDef _mysql_ConnectionObject_methods[] = { @@ -2956,11 +2893,7 @@ init_mysql(void) dict, dict))) goto error; if (PyDict_SetItemString(dict, "__version__", -#ifdef IS_PY3K - PyUnicode_FromString(QUOTE(__version__)))) -#else PyString_FromString(QUOTE(__version__)))) -#endif goto error; if (PyDict_SetItemString(dict, "connection", (PyObject *)&_mysql_ConnectionObject_Type)) @@ -3009,13 +2942,8 @@ init_mysql(void) _mysql_NewException(dict, edict, "NotSupportedError"))) goto error; Py_DECREF(emod); -#ifdef IS_PY3K - if (!(_mysql_NULL = PyUnicode_FromString("NULL"))) - goto error; -#else if (!(_mysql_NULL = PyString_FromString("NULL"))) goto error; -#endif if (PyDict_SetItemString(dict, "NULL", _mysql_NULL)) goto error; error: if (PyErr_Occurred()) { @@ -3027,3 +2955,4 @@ init_mysql(void) return module; #endif } +/* vim: set ts=4 sts=4 sw=4 noexpandtab : */ From 9cdef719a6e59dc621c581af824055d8e9019ecc Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 19:50:42 +0900 Subject: [PATCH 15/35] More fix --- _mysql.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/_mysql.c b/_mysql.c index 4c16b660..0a786879 100644 --- a/_mysql.c +++ b/_mysql.c @@ -265,7 +265,7 @@ static PyObject *_mysql_server_init( for (i=0; i< cmd_argc; i++) { item = PySequence_GetItem(cmd_args, i); #ifdef IS_PY3K - s = PyUnicode_AS_DATA(item); + s = PyUnicode_AsUTF8(item); #else s = PyString_AsString(item); #endif @@ -295,7 +295,7 @@ static PyObject *_mysql_server_init( for (i=0; i< groupc; i++) { item = PySequence_GetItem(groups, i); #ifdef IS_PY3K - s = PyUnicode_AS_DATA(item); + s = PyUnicode_AsUTF8(item); #else s = PyString_AsString(item); #endif @@ -578,7 +578,7 @@ _mysql_ConnectionObject_Initialize( #ifdef IS_PY3K #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ - if(t){d=PyUnicode_AS_DATA(t);Py_DECREF(t);}\ + if(t){d=PyUnicode_AsUTF8(t);Py_DECREF(t);}\ PyErr_Clear();} #else #define _stringsuck(d,t,s) {t=PyMapping_GetItemString(s,#d);\ @@ -1334,19 +1334,16 @@ _mysql_field_to_python( { PyObject *v; if (rowitem) { - if (converter != Py_None) + if (converter != Py_None) { v = PyObject_CallFunction(converter, "s#", rowitem, (int)length); - else -#ifdef IS_PY3K - v = PyUnicode_FromStringAndSize(rowitem, - (int)length); -#else - v = PyString_FromStringAndSize(rowitem, + } else { + // TODO: Should we decode here on Python 3? + v = PyBytes_FromStringAndSize(rowitem, (int)length); -#endif + } if (!v) return NULL; } else { From 382fb9f9b3453f579f7bb59af46f127405635945 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 22:24:46 +0900 Subject: [PATCH 16/35] Random fixes. --- MySQLdb/__init__.py | 2 +- MySQLdb/converters.py | 52 ++++++++++++------------------------------- MySQLdb/cursors.py | 43 ++++++++++++++++++++++++++--------- 3 files changed, 48 insertions(+), 49 deletions(-) diff --git a/MySQLdb/__init__.py b/MySQLdb/__init__.py index 87616715..fc414810 100644 --- a/MySQLdb/__init__.py +++ b/MySQLdb/__init__.py @@ -73,7 +73,7 @@ def test_DBAPISet_set_inequality_membership(): assert FIELD_TYPE.DATE != STRING def Binary(x): - return str(x) + return bytes(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 26c1f901..7f6bfb70 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -38,13 +38,15 @@ try: from types import IntType, LongType, FloatType, NoneType, TupleType, ListType, DictType, InstanceType, \ - StringType, UnicodeType, ObjectType, BooleanType, ClassType, TypeType + ObjectType, BooleanType + PY2 = True except ImportError: # Python 3 long = int IntType, LongType, FloatType, NoneType = int, long, float, type(None) TupleType, ListType, DictType, InstanceType = tuple, list, dict, None - StringType, UnicodeType, ObjectType, BooleanType = bytes, str, object, bool + ObjectType, BooleanType = object, bool + PY2 = False import array @@ -95,34 +97,6 @@ def Thing2Literal(o, d): return string_literal(o, d) -def Instance2Str(o, d): - - """ - - Convert an Instance to a string representation. If the __str__() - method produces acceptable output, then you don't need to add the - class to conversions; it will be handled by the default - converter. If the exact class is not found in d, it will use the - first class it can find for which o is an instance. - - """ - - if o.__class__ in d: - return d[o.__class__](o, d) - cl = filter(lambda x,o=o: - type(x) is ClassType - and isinstance(o, x), d.keys()) - if not cl: - cl = filter(lambda x,o=o: - type(x) is TypeType - and isinstance(o, x) - and d[x] is not Instance2Str, - d.keys()) - if not cl: - return d[StringType](o,d) - d[o.__class__] = d[cl[0]] - return d[cl[0]](o, d) - def char_array(s): return array.array('c', s) @@ -140,14 +114,12 @@ def quote_tuple(t, d): TupleType: quote_tuple, ListType: quote_tuple, DictType: escape_dict, - InstanceType: Instance2Str, ArrayType: array2Str, - StringType: Thing2Literal, # default - UnicodeType: Unicode2Str, - ObjectType: Instance2Str, BooleanType: Bool2Str, + Date: Thing2Literal, DateTimeType: DateTime2literal, DateTimeDeltaType: DateTimeDelta2literal, + str: str, # default set: Set2Str, FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, @@ -165,18 +137,22 @@ def quote_tuple(t, d): FIELD_TYPE.TIME: TimeDelta_or_None, FIELD_TYPE.DATE: Date_or_None, FIELD_TYPE.BLOB: [ - (FLAG.BINARY, str), + (FLAG.BINARY, bytes), ], FIELD_TYPE.STRING: [ - (FLAG.BINARY, str), + (FLAG.BINARY, bytes), ], FIELD_TYPE.VAR_STRING: [ - (FLAG.BINARY, str), + (FLAG.BINARY, bytes), ], FIELD_TYPE.VARCHAR: [ - (FLAG.BINARY, str), + (FLAG.BINARY, bytes), ], } +if PY2: + conversions[unicode] = Unicode2Str +else: + conversions[bytes] = bytes try: from decimal import Decimal diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index c19f2564..f63cbee0 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -7,10 +7,11 @@ import re import sys +PY2 = sys.version_info[0] == 2 from MySQLdb.compat import unicode -restr = r""" +restr = br""" \s values \s* @@ -68,9 +69,9 @@ class BaseCursor(object): _defer_warnings = False def __init__(self, connection): - from weakref import proxy + from weakref import ref - self.connection = proxy(connection) + self.connection = ref(connection) self.description = None self.description_flags = None self.rowcount = -1 @@ -91,7 +92,8 @@ def __del__(self): def close(self): """Close the cursor. No further queries will be possible.""" - if not self.connection: return + if self.connection is None or self.connection() is None: + return while self.nextset(): pass self.connection = None @@ -152,9 +154,12 @@ def setoutputsizes(self, *args): """Does nothing, required by DB API.""" def _get_db(self): - if not self.connection: + con = self.connection + if con is not None: + con = con() + if con is None: self.errorhandler(self, ProgrammingError, "cursor closed") - return self.connection + return con def execute(self, query, args=None): @@ -172,14 +177,32 @@ def execute(self, query, args=None): """ del self.messages[:] db = self._get_db() - if isinstance(query, unicode): + if PY2 and isinstance(query, unicode): query = query.encode(db.unicode_literal.charset) + else: + def decode(x): + if isinstance(x, bytes): + x = x.decode('ascii', 'surrogateescape') + return x + if args is not None: if isinstance(args, dict): - query = query % dict((key, db.literal(item)) - for key, item in args.iteritems()) + if PY2: + args = dict((key, db.literal(item)) for key, item in args.iteritems()) + else: + args = dict((key, decode(db.literal(item))) for key, item in args.items()) else: - query = query % tuple([db.literal(item) for item in args]) + if PY2: + args = tuple(map(db.literal, args)) + else: + args = tuple([decode(db.literal(x)) for x in args]) + if not PY2 and isinstance(query, bytes): + query = query.decode(db.unicode_literal.charset) + query = query % args + + if isinstance(query, unicode): + query = query.encode(db.unicode_literal.charset, 'surrogateescape') + try: r = None r = self._query(query) From c66b43cc2200ece4a32485420faf3f4114f0a51c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Thu, 17 Apr 2014 22:36:01 +0900 Subject: [PATCH 17/35] Connection.literal() always returns `str` instance. --- MySQLdb/connections.py | 8 +++++++- MySQLdb/cursors.py | 26 ++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index a7adc99e..b42817f3 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -13,6 +13,9 @@ NotSupportedError, ProgrammingError import _mysql import re +import sys + +PY2 = sys.version_info[0] == 2 def defaulterrorhandler(connection, cursor, errorclass, errorvalue): @@ -280,7 +283,10 @@ def literal(self, o): applications. """ - return self.escape(o, self.encoders) + s = self.escape(o, self.encoders) + if not PY2 and isinstance(s, bytes): + return s.decode('ascii', 'surrogateescape') + return s def begin(self): """Explicitly begin a connection. Non-standard. diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index f63cbee0..f179306a 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -11,7 +11,7 @@ from MySQLdb.compat import unicode -restr = br""" +restr = r""" \s values \s* @@ -179,23 +179,12 @@ def execute(self, query, args=None): db = self._get_db() if PY2 and isinstance(query, unicode): query = query.encode(db.unicode_literal.charset) - else: - def decode(x): - if isinstance(x, bytes): - x = x.decode('ascii', 'surrogateescape') - return x if args is not None: if isinstance(args, dict): - if PY2: - args = dict((key, db.literal(item)) for key, item in args.iteritems()) - else: - args = dict((key, decode(db.literal(item))) for key, item in args.items()) + args = dict((key, db.literal(item)) for key, item in args.iteritems()) else: - if PY2: - args = tuple(map(db.literal, args)) - else: - args = tuple([decode(db.literal(x)) for x in args]) + args = tuple(map(db.literal, args)) if not PY2 and isinstance(query, bytes): query = query.decode(db.unicode_literal.charset) query = query % args @@ -246,8 +235,10 @@ def executemany(self, query, args): del self.messages[:] db = self._get_db() if not args: return - if isinstance(query, unicode): + if PY2 and isinstance(query, unicode): query = query.encode(db.unicode_literal.charset) + elif not PY2 and isinstance(query, bytes): + query = query.decode(db.unicode_literal.charset) m = insert_values.search(query) if not m: r = 0 @@ -277,7 +268,10 @@ def executemany(self, query, args): exc, value, tb = sys.exc_info() del tb self.errorhandler(self, exc, value) - r = self._query('\n'.join([query[:p], ',\n'.join(q), query[e:]])) + qs = '\n'.join([query[:p], ',\n'.join(q), query[e:]]) + if not PY2: + qs = qs.encode(db.unicode_literal.charset, 'surrogateescape') + r = self._query(qs) if not self._defer_warnings: self._warning_check() return r From b5780d29c93302dd39bcb9e30ee6f1954f1d5d53 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 01:11:47 +0900 Subject: [PATCH 18/35] self of module-level function is not NULL on Python 3. --- _mysql.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/_mysql.c b/_mysql.c index 0a786879..34cd1875 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1074,6 +1074,9 @@ _mysql_escape_string( len = mysql_escape_string(out, in, size); #else check_server_init(NULL); + + if (PyModule_Check((PyObject*)self)) + self = NULL; if (self && self->open) len = mysql_real_escape_string(&(self->connection), out, in, size); else @@ -1101,21 +1104,31 @@ _mysql_string_literal( PyObject *str, *s, *o, *d; char *in, *out; int len, size; + if (PyModule_Check((PyObject*)self)) + self = NULL; if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; - s = PyObject_Str(o); - if (!s) return NULL; + if (PyBytes_Check(o)) { + s = o; + Py_INCREF(s); + } else { + s = PyObject_Str(o); + if (!s) return NULL; #ifdef IS_PY3K - { - PyObject *t = PyUnicode_AsASCIIString(s); - if (!t) return NULL; - Py_DECREF(s); - s = t; - } + { + PyObject *t = PyUnicode_AsASCIIString(s); + Py_DECREF(s); + if (!t) return NULL; + s = t; + } #endif + } in = PyBytes_AsString(s); size = PyBytes_GET_SIZE(s); str = PyBytes_FromStringAndSize((char *) NULL, size*2+3); - if (!str) return PyErr_NoMemory(); + if (!str) { + Py_DECREF(s); + return PyErr_NoMemory(); + } out = PyBytes_AS_STRING(str); #if MYSQL_VERSION_ID < 32321 len = mysql_escape_string(out+1, in, size); @@ -1336,7 +1349,11 @@ _mysql_field_to_python( if (rowitem) { if (converter != Py_None) { v = PyObject_CallFunction(converter, +#ifdef IS_PY3K + "y#", +#else "s#", +#endif rowitem, (int)length); } else { From e8c1ebb5b75c728a8c2b0333fe8bc578639ebcca Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 03:42:11 +0900 Subject: [PATCH 19/35] Call decoders with unicode when field is not str and bytes. --- _mysql.c | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/_mysql.c b/_mysql.c index 34cd1875..3b809b89 100644 --- a/_mysql.c +++ b/_mysql.c @@ -438,7 +438,11 @@ _mysql_ResultObject_Initialize( Py_INCREF(Py_None); } else if (PySequence_Check(fun)) { + long flags = fields[i].flags; int j, n2=PySequence_Size(fun); + if (fields[i].charsetnr != 63) { /* maaagic */ + flags &= ~BINARY_FLAG; + } PyObject *fun2=NULL; for (j=0; jtype; PyObject *v; +#ifdef IS_PY3K + // Return bytes for binary and string types. + int binary = 0; + if (field_type == FIELD_TYPE_TINY_BLOB || + field_type == FIELD_TYPE_MEDIUM_BLOB || + field_type == FIELD_TYPE_LONG_BLOB || + field_type == FIELD_TYPE_BLOB || + field_type == FIELD_TYPE_VAR_STRING || + field_type == FIELD_TYPE_STRING) { + binary = 1; + } +#endif if (rowitem) { if (converter != Py_None) { + const char *fmt = "s#"; v = PyObject_CallFunction(converter, #ifdef IS_PY3K - "y#", + binary ? "y#" : "s#", #else "s#", #endif rowitem, (int)length); } else { - // TODO: Should we decode here on Python 3? - v = PyBytes_FromStringAndSize(rowitem, - (int)length); +#ifdef IS_PY3K + if (!binary) { + v = PyUnicode_FromStringAndSize(rowitem, (int)length); + } else +#endif + v = PyBytes_FromStringAndSize(rowitem, (int)length); } if (!v) return NULL; @@ -1378,14 +1396,16 @@ _mysql_row_to_tuple( unsigned int n, i; unsigned long *length; PyObject *r, *c; + MYSQL_FIELD *fields; n = mysql_num_fields(self->result); if (!(r = PyTuple_New(n))) return NULL; length = mysql_fetch_lengths(self->result); + fields = mysql_fetch_fields(self->result); for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); if (!v) goto error; PyTuple_SET_ITEM(r, i, v); } @@ -1403,16 +1423,16 @@ _mysql_row_to_dict( unsigned int n, i; unsigned long *length; PyObject *r, *c; - MYSQL_FIELD *fields; + MYSQL_FIELD *fields; n = mysql_num_fields(self->result); if (!(r = PyDict_New())) return NULL; length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); + fields = mysql_fetch_fields(self->result); for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); if (!v) goto error; if (!PyMapping_HasKeyString(r, fields[i].name)) { PyMapping_SetItemString(r, fields[i].name, v); @@ -1447,11 +1467,11 @@ _mysql_row_to_dict_old( n = mysql_num_fields(self->result); if (!(r = PyDict_New())) return NULL; length = mysql_fetch_lengths(self->result); - fields = mysql_fetch_fields(self->result); + fields = mysql_fetch_fields(self->result); for (i=0; iconverter, i); - v = _mysql_field_to_python(c, row[i], length[i]); + v = _mysql_field_to_python(c, row[i], length[i], &fields[i]); if (!v) goto error; { int len=0; From f188e0f9e4ca919413efb11c6641a5ff9f7f9efc Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 11:29:10 +0900 Subject: [PATCH 20/35] Fix some tests. --- MySQLdb/connections.py | 8 ++++++-- tests/capabilities.py | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index b42817f3..766a0a25 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -212,8 +212,12 @@ def string_literal(obj, dummy=None): return string_literal def _get_unicode_literal(): - def unicode_literal(u, dummy=None): - return db.literal(u.encode(unicode_literal.charset)) + if PY2: + def unicode_literal(u, dummy=None): + return db.literal(u.encode(unicode_literal.charset)) + else: + def unicode_literal(u, dummy=None): + return db.literal(str(u).encode(unicode_literal.charset)) return unicode_literal def _get_string_decoder(): diff --git a/tests/capabilities.py b/tests/capabilities.py index 5b122cb5..ca89dc15 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -27,10 +27,8 @@ def setUp(self): db = connection_factory(**self.connect_kwargs) self.connection = db self.cursor = db.cursor() - # TODO: this needs to be re-evaluated for Python 3 - self.BLOBText = ''.join([chr(i) for i in range(256)] * 100); self.BLOBUText = u''.join([unichr(i) for i in range(16384)]) - self.BLOBBinary = self.db_module.Binary(''.join([chr(i) for i in range(256)] * 16)) + self.BLOBBinary = self.db_module.Binary((u''.join([unichr(i) for i in range(256)] * 16)).encode('latin1')) leak_test = True From 5a66f79f10d1bd5c248e03ae06b74506343051eb Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 11:50:58 +0900 Subject: [PATCH 21/35] Fix segfault on Python 2. --- _mysql.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_mysql.c b/_mysql.c index 3b809b89..8b815fb9 100644 --- a/_mysql.c +++ b/_mysql.c @@ -1075,7 +1075,7 @@ _mysql_escape_string( #else check_server_init(NULL); - if (PyModule_Check((PyObject*)self)) + if (self && PyModule_Check((PyObject*)self)) self = NULL; if (self && self->open) len = mysql_real_escape_string(&(self->connection), out, in, size); @@ -1104,7 +1104,7 @@ _mysql_string_literal( PyObject *str, *s, *o, *d; char *in, *out; int len, size; - if (PyModule_Check((PyObject*)self)) + if (self && PyModule_Check((PyObject*)self)) self = NULL; if (!PyArg_ParseTuple(args, "O|O:string_literal", &o, &d)) return NULL; if (PyBytes_Check(o)) { From 4a6dde81a36b1671b31b537d3fd87d191eed41ab Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 11:54:03 +0900 Subject: [PATCH 22/35] Fix multiply by float. --- tests/capabilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/capabilities.py b/tests/capabilities.py index ca89dc15..cbbe24fe 100644 --- a/tests/capabilities.py +++ b/tests/capabilities.py @@ -137,7 +137,7 @@ def test_truncation(self): columndefs = ( 'col1 INT', 'col2 VARCHAR(255)') def generator(row, col): if col == 0: return row - else: return ('%i' % (row%10))*((255-self.rows/2)+row) + else: return ('%i' % (row%10))*((255-self.rows//2)+row) self.create_table(columndefs) insert_statement = ('INSERT INTO %s VALUES (%s)' % (self.table, From 67ecdc82a60a9a77ecea3691d0d0f0cf8b41834a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 12:04:04 +0900 Subject: [PATCH 23/35] Fix calling Binary() with string. --- tests/dbapi20.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dbapi20.py b/tests/dbapi20.py index ad292ae7..5afc70a7 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -823,8 +823,8 @@ def test_Timestamp(self): # self.assertEqual(str(t1),str(t2)) def test_Binary(self): - b = self.driver.Binary('Something') - b = self.driver.Binary('') + b = self.driver.Binary(b'Something') + b = self.driver.Binary(b'') def test_STRING(self): self.assertTrue(hasattr(self.driver,'STRING'), From 67cf0a089741235ff95896edd929472093f8f0c7 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 12:06:39 +0900 Subject: [PATCH 24/35] StandardError is removed from Python 3 --- tests/dbapi20.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 5afc70a7..e1326d1b 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -177,8 +177,8 @@ def test_paramstyle(self): def test_Exceptions(self): # Make sure required exceptions exist, and are in the # defined heirarchy. - self.assertTrue(issubclass(self.driver.Warning,StandardError)) - self.assertTrue(issubclass(self.driver.Error,StandardError)) + self.assertTrue(issubclass(self.driver.Warning,Exception)) + self.assertTrue(issubclass(self.driver.Error,Exception)) self.assertTrue( issubclass(self.driver.InterfaceError,self.driver.Error) ) From 312f46d6ca4374cc7f0355320a6cc9c33dde5613 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 12:17:34 +0900 Subject: [PATCH 25/35] use_unicode=True by default on Python 3. --- MySQLdb/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 766a0a25..3c7a9779 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -178,7 +178,7 @@ class object, used to create cursors (keyword only) cursorclass = kwargs2.pop('cursorclass', self.default_cursor) charset = kwargs2.pop('charset', '') - if charset: + if charset or not PY2: use_unicode = True else: use_unicode = False From b28d58b3550809486663a84e9f1fbdc3d2e520c6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 12:22:38 +0900 Subject: [PATCH 26/35] Don't allow test fail on Python 3.3 and 3.4 --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4accb99f..d863e73c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,6 @@ python: - 3.3 - 3.4 matrix: - allow_failures: - - python: 3.3 - - python: 3.4 fast_finish: true install: python setup.py install before_script: mysql -e 'create database mysqldb_test charset utf8;' From 63e70526f65093d98d9c4b9184147dd7c7107453 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 13:58:21 +0900 Subject: [PATCH 27/35] Travis doesn't support Python 3.4 yet. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d863e73c..f2647f68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ python: - 2.7 - pypy - 3.3 - - 3.4 +#- 3.4 matrix: fast_finish: true install: python setup.py install From 8c9f3762112bdfdbcc16b7c9f055d6d88ac526ce Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 13:59:30 +0900 Subject: [PATCH 28/35] Use tox on Travis. --- .travis.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2647f68..ebb333d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: python -python: - - 2.6 - - 2.7 - - pypy - - 3.3 -#- 3.4 -matrix: - fast_finish: true -install: python setup.py install +before_install: + - sudo apt-get update -qq -y + - sudo apt-get install python3.4 + +install: + - pip install tox six + before_script: mysql -e 'create database mysqldb_test charset utf8;' -script: TESTDB=travis.cnf nosetests +script: tox From 469adcffe9a684bb543216004c602555f375b55d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 14:53:16 +0900 Subject: [PATCH 29/35] Install python3.4-dev on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebb333d7..35cd3093 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python before_install: - sudo apt-get update -qq -y - - sudo apt-get install python3.4 + - sudo apt-get install python3.4-dev install: - pip install tox six From af18cc9c080f32be4ecec737c7c7f49586af8be1 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 22:59:30 +0900 Subject: [PATCH 30/35] Remove pymemcompat.h --- MANIFEST.in | 1 - _mysql.c | 1 - pymemcompat.h | 87 --------------------------------------------------- 3 files changed, 89 deletions(-) delete mode 100644 pymemcompat.h diff --git a/MANIFEST.in b/MANIFEST.in index 54b1fd5a..4e03cc43 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,7 +6,6 @@ include HISTORY include INSTALL include README.md include GPL-2.0 -include pymemcompat.h include metadata.cfg include site.cfg include setup_common.py diff --git a/_mysql.c b/_mysql.c index 8b815fb9..16649135 100644 --- a/_mysql.c +++ b/_mysql.c @@ -36,7 +36,6 @@ PERFORMANCE OF THIS SOFTWARE. #endif #include "bytesobject.h" -#include "pymemcompat.h" #include "structmember.h" #if defined(MS_WINDOWS) #include diff --git a/pymemcompat.h b/pymemcompat.h deleted file mode 100644 index e7c538c4..00000000 --- a/pymemcompat.h +++ /dev/null @@ -1,87 +0,0 @@ - -/* The idea of this file is that you bundle it with your extension, - #include it, program to Python 2.3's memory API and have your - extension build with any version of Python from 1.5.2 through to - 2.3 (and hopefully beyond). */ - -#ifndef Py_PYMEMCOMPAT_H -#define Py_PYMEMCOMPAT_H - -#include "Python.h" - -/* There are three "families" of memory API: the "raw memory", "object - memory" and "object" families. (This is ignoring the matter of the - cycle collector, about which more is said below). - - Raw Memory: - - PyMem_Malloc, PyMem_Realloc, PyMem_Free - - Object Memory: - - PyObject_Malloc, PyObject_Realloc, PyObject_Free - - Object: - - PyObject_New, PyObject_NewVar, PyObject_Del - - The raw memory and object memory allocators both mimic the - malloc/realloc/free interface from ANSI C, but the object memory - allocator can (and, since 2.3, does by default) use a different - allocation strategy biased towards lots of lots of "small" - allocations. - - The object family is used for allocating Python objects, and the - initializers take care of some basic initialization (setting the - refcount to 1 and filling out the ob_type field) as well as having - a somewhat different interface. - - Do not mix the families! E.g. do not allocate memory with - PyMem_Malloc and free it with PyObject_Free. You may get away with - it quite a lot of the time, but there *are* scenarios where this - will break. You Have Been Warned. - - Also, in many versions of Python there are an insane amount of - memory interfaces to choose from. Use the ones described above. */ - -#if PY_VERSION_HEX < 0x01060000 -/* raw memory interface already present */ - -/* there is no object memory interface in 1.5.2 */ -#define PyObject_Malloc PyMem_Malloc -#define PyObject_Realloc PyMem_Realloc -#define PyObject_Free PyMem_Free - -/* the object interface is there, but the names have changed */ -#define PyObject_New PyObject_NEW -#define PyObject_NewVar PyObject_NEW_VAR -#define PyObject_Del PyMem_Free -#endif - -/* If your object is a container you probably want to support the - cycle collector, which was new in Python 2.0. - - Unfortunately, the interface to the collector that was present in - Python 2.0 and 2.1 proved to be tricky to use, and so changed in - 2.2 -- in a way that can't easily be papered over with macros. - - This file contains macros that let you program to the 2.2 GC API. - Your module will compile against any Python since version 1.5.2, - but the type will only participate in the GC in versions 2.2 and - up. Some work is still necessary on your part to only fill out the - tp_traverse and tp_clear fields when they exist and set tp_flags - appropriately. - - It is possible to support both the 2.0 and 2.2 GC APIs, but it's - not pretty and this comment block is too narrow to contain a - desciption of what's required... */ - -#if PY_VERSION_HEX < 0x020200B1 -#define PyObject_GC_New PyObject_New -#define PyObject_GC_NewVar PyObject_NewVar -#define PyObject_GC_Del PyObject_Del -#define PyObject_GC_Track(op) -#define PyObject_GC_UnTrack(op) -#endif - -#endif /* !Py_PYMEMCOMPAT_H */ From e7545c7c40cf1830ceee77cdeb8b04a9d5b3c754 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 23:25:01 +0900 Subject: [PATCH 31/35] refactoring. --- MySQLdb/compat.py | 4 ++++ MySQLdb/connections.py | 13 ++++++++++--- MySQLdb/converters.py | 38 +++++++++++--------------------------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/MySQLdb/compat.py b/MySQLdb/compat.py index 4d84afe2..70580b61 100644 --- a/MySQLdb/compat.py +++ b/MySQLdb/compat.py @@ -1,8 +1,12 @@ import sys if sys.version_info[0] == 3: + PY2 = False unicode = str unichr = chr + long = int else: + PY2 = True unicode = unicode unichr = unichr + long = long diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 3c7a9779..1b18fa67 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -7,7 +7,7 @@ """ from MySQLdb import cursors -from MySQLdb.compat import unicode +from MySQLdb.compat import unicode, PY2 from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \ DatabaseError, OperationalError, IntegrityError, InternalError, \ NotSupportedError, ProgrammingError @@ -15,8 +15,6 @@ import re import sys -PY2 = sys.version_info[0] == 2 - def defaulterrorhandler(connection, cursor, errorclass, errorvalue): """ @@ -123,6 +121,7 @@ class object, used to create cursors (keyword only) columns are returned as strings. columns are returned as normal strings. Unicode objects will always be encoded to the connection's character set regardless of this setting. + Default to False on Python 2 and True on Python 3. charset If supplied, the connection character set will be changed @@ -207,15 +206,18 @@ class object, used to create cursors (keyword only) db = proxy(self) def _get_string_literal(): + # Note: string_literal() is called for bytes object on Python 3. def string_literal(obj, dummy=None): return db.string_literal(obj) return string_literal def _get_unicode_literal(): if PY2: + # unicode_literal is called for only unicode object. def unicode_literal(u, dummy=None): return db.literal(u.encode(unicode_literal.charset)) else: + # unicode_literal() is called for arbitrary object. def unicode_literal(u, dummy=None): return db.literal(str(u).encode(unicode_literal.charset)) return unicode_literal @@ -288,6 +290,11 @@ def literal(self, o): """ s = self.escape(o, self.encoders) + # Python 3 doesn't support % operation for bytes object. + # We should decode it before using %. + # Decoding with ascii and surrogateescape allows convert arbitrary + # bytes to unicode and back again. + # See http://python.org/dev/peps/pep-0383/ if not PY2 and isinstance(s, bytes): return s.decode('ascii', 'surrogateescape') return s diff --git a/MySQLdb/converters.py b/MySQLdb/converters.py index 7f6bfb70..9937e250 100644 --- a/MySQLdb/converters.py +++ b/MySQLdb/converters.py @@ -35,18 +35,9 @@ from _mysql import string_literal, escape_sequence, escape_dict, escape, NULL from MySQLdb.constants import FIELD_TYPE, FLAG from MySQLdb.times import * +from MySQLdb.compat import PY2, long -try: - from types import IntType, LongType, FloatType, NoneType, TupleType, ListType, DictType, InstanceType, \ - ObjectType, BooleanType - PY2 = True -except ImportError: - # Python 3 - long = int - IntType, LongType, FloatType, NoneType = int, long, float, type(None) - TupleType, ListType, DictType, InstanceType = tuple, list, dict, None - ObjectType, BooleanType = object, bool - PY2 = False +NoneType = type(None) import array @@ -78,8 +69,6 @@ def Unicode2Str(s, d): is connection-dependent.""" return s.encode() -Long2Int = Thing2Str - def Float2Str(o, d): return '%.15g' % o @@ -88,12 +77,10 @@ def None2NULL(o, d): return NULL # duh def Thing2Literal(o, d): - """Convert something into a SQL string literal. If using MySQL-3.23 or newer, string_literal() is a method of the _mysql.MYSQL object, and this function will be overridden with that method when the connection is created.""" - return string_literal(o, d) @@ -107,19 +94,19 @@ def quote_tuple(t, d): return "(%s)" % (','.join(escape_sequence(t, d))) conversions = { - IntType: Thing2Str, - LongType: Long2Int, - FloatType: Float2Str, + int: Thing2Str, + long: Thing2Str, + float: Float2Str, NoneType: None2NULL, - TupleType: quote_tuple, - ListType: quote_tuple, - DictType: escape_dict, + tuple: quote_tuple, + list: quote_tuple, + dict: escape_dict, ArrayType: array2Str, - BooleanType: Bool2Str, + bool: Bool2Str, Date: Thing2Literal, DateTimeType: DateTime2literal, DateTimeDeltaType: DateTimeDelta2literal, - str: str, # default + str: Thing2Literal, # default set: Set2Str, FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, @@ -152,7 +139,7 @@ def quote_tuple(t, d): if PY2: conversions[unicode] = Unicode2Str else: - conversions[bytes] = bytes + conversions[bytes] = Thing2Literal try: from decimal import Decimal @@ -160,6 +147,3 @@ def quote_tuple(t, d): conversions[FIELD_TYPE.NEWDECIMAL] = Decimal except ImportError: pass - - - From b881072414921b225fdf1262ae2698c770d9866f Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 23:26:39 +0900 Subject: [PATCH 32/35] Remove unused import --- MySQLdb/connections.py | 1 - 1 file changed, 1 deletion(-) diff --git a/MySQLdb/connections.py b/MySQLdb/connections.py index 1b18fa67..cd803767 100644 --- a/MySQLdb/connections.py +++ b/MySQLdb/connections.py @@ -13,7 +13,6 @@ NotSupportedError, ProgrammingError import _mysql import re -import sys def defaulterrorhandler(connection, cursor, errorclass, errorvalue): From 56a5f1afa16280b16b6722d3cdac6ff8161269be Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 18 Apr 2014 23:34:14 +0900 Subject: [PATCH 33/35] Add comment. --- MySQLdb/cursors.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MySQLdb/cursors.py b/MySQLdb/cursors.py index f179306a..f241db2f 100644 --- a/MySQLdb/cursors.py +++ b/MySQLdb/cursors.py @@ -177,6 +177,14 @@ def execute(self, query, args=None): """ del self.messages[:] db = self._get_db() + + # NOTE: + # Python 2: query should be bytes when executing %. + # All unicode in args should be encoded to bytes on Python 2. + # Python 3: query should be str (unicode) when executing %. + # All bytes in args should be decoded with ascii and surrogateescape on Python 3. + # db.literal(obj) always returns str. + if PY2 and isinstance(query, unicode): query = query.encode(db.unicode_literal.charset) From 7a6b221acd82741bb64edfeccc29ff3d9b7ce809 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 20 Apr 2014 23:23:55 +0900 Subject: [PATCH 34/35] Python 2.6 is back. --- .travis.yml | 2 +- tox.ini | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35cd3093..a6f14172 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ install: - pip install tox six before_script: mysql -e 'create database mysqldb_test charset utf8;' -script: tox +script: TESTDB=travis.cnf tox diff --git a/tox.ini b/tox.ini index 0996df3d..904c09c9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,7 @@ [tox] -envlist = py27, py33, py34 +envlist = py26,py27, py33, py34 [testenv] -setenv = - TESTDB=travis.cnf commands = nosetests {posargs:-w tests -v} deps = From 392b548a774b3db6e7d941cdb7b65228436afa54 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Mon, 28 Apr 2014 17:49:20 +0900 Subject: [PATCH 35/35] Fix compilation error on Windows. --- _mysql.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_mysql.c b/_mysql.c index 16649135..a1554a50 100644 --- a/_mysql.c +++ b/_mysql.c @@ -438,11 +438,11 @@ _mysql_ResultObject_Initialize( } else if (PySequence_Check(fun)) { long flags = fields[i].flags; + PyObject *fun2=NULL; int j, n2=PySequence_Size(fun); if (fields[i].charsetnr != 63) { /* maaagic */ flags &= ~BINARY_FLAG; } - PyObject *fun2=NULL; for (j=0; j