From cb6b52945b4c114d39613e6a7b35c73762e5045f Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 12 Dec 2010 21:48:54 +0000 Subject: [PATCH] The library can be compiled with Python 3. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just compiled! No test run yet and many points to review, marked in the code. The patch is largely Martin von Löwis work, simplified after refactoring in the previous commits and adapted to the new code (as the patch was originally for Psycopg 2.0.9) --- psycopg/adapter_asis.c | 2 +- psycopg/adapter_binary.c | 10 ++- psycopg/adapter_datetime.c | 16 +++-- psycopg/adapter_list.c | 10 ++- psycopg/adapter_pboolean.c | 8 +-- psycopg/adapter_pdecimal.c | 6 +- psycopg/adapter_pfloat.c | 4 +- psycopg/adapter_qstring.c | 7 +- psycopg/connection_int.c | 35 +++++++-- psycopg/connection_type.c | 2 +- psycopg/cursor_type.c | 36 ++++++---- psycopg/lobject_type.c | 2 +- psycopg/microprotocols.c | 8 ++- psycopg/notify_type.c | 7 +- psycopg/pqpath.c | 11 +-- psycopg/psycopgmodule.c | 143 +++++++++++++++++++++++++++---------- psycopg/python.h | 62 ++++++++++++++++ psycopg/typecast.c | 24 +++---- psycopg/typecast_basic.c | 17 ++++- psycopg/typecast_binary.c | 24 +++++++ psycopg/xid_type.c | 16 ++--- 21 files changed, 335 insertions(+), 115 deletions(-) diff --git a/psycopg/adapter_asis.c b/psycopg/adapter_asis.c index 0d0a7f67..1aa1d0da 100644 --- a/psycopg/adapter_asis.c +++ b/psycopg/adapter_asis.c @@ -38,7 +38,7 @@ static PyObject * asis_str(asisObject *self) { if (self->wrapped == Py_None) { - return PyString_FromString("NULL"); + return Text_FromUTF8("NULL"); } else { return PyObject_Str(self->wrapped); diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index cf31ccbf..22105070 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -58,7 +58,13 @@ binary_quote(binaryObject *self) size_t len = 0; /* if we got a plain string or a buffer we escape it and save the buffer */ - if (PyString_Check(self->wrapped) || PyBuffer_Check(self->wrapped)) { + if (Bytes_Check(self->wrapped) +#if PY_MAJOR_VERSION < 3 + || PyBuffer_Check(self->wrapped) +#else + || PyMemoryView_Check(self->wrapped) +#endif + ) { /* escape and build quoted buffer */ if (PyObject_AsReadBuffer(self->wrapped, (const void **)&buffer, &buffer_len) < 0) @@ -76,7 +82,7 @@ binary_quote(binaryObject *self) (self->conn && ((connectionObject*)self->conn)->equote) ? "E'%s'::bytea" : "'%s'::bytea" , to); else - self->buffer = PyString_FromString("''::bytea"); + self->buffer = Text_FromUTF8("''::bytea"); PQfreemem(to); } diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index f640684a..19fd97e2 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -62,26 +62,30 @@ pydatetime_str(pydatetimeObject *self) if (self->type <= PSYCO_DATETIME_TIMESTAMP) { PyObject *tz; - /* Select the right PG type to cast into. */ + /* Select the right PG type to cast into. + * fmt contains %s in Py2 and %U in Py3, + * So it can be used with the Text_FromFormatS macro */ char *fmt = NULL; switch (self->type) { case PSYCO_DATETIME_TIME: - fmt = "'%s'::time"; + fmt = "'" Text_S "'::time"; break; case PSYCO_DATETIME_DATE: - fmt = "'%s'::date"; + fmt = "'" Text_S "'::date"; break; case PSYCO_DATETIME_TIMESTAMP: tz = PyObject_GetAttrString(self->wrapped, "tzinfo"); if (!tz) { return NULL; } - fmt = (tz == Py_None) ? "'%s'::timestamp" : "'%s'::timestamptz"; + fmt = (tz == Py_None) + ? "'" Text_S "'::timestamp" + : "'" Text_S "'::timestamptz"; Py_DECREF(tz); break; } iso = PyObject_CallMethod(self->wrapped, "isoformat", NULL); if (iso) { - res = PyString_FromFormat(fmt, PyString_AsString(iso)); + res = Text_FromFormatS(fmt, iso); Py_DECREF(iso); } return res; @@ -89,7 +93,7 @@ pydatetime_str(pydatetimeObject *self) else { PyDateTime_Delta *obj = (PyDateTime_Delta*)self->wrapped; - char buffer[8]; + char buffer[8]; int i; int a = obj->microseconds; diff --git a/psycopg/adapter_list.c b/psycopg/adapter_list.c index 8bd66881..8973e9dc 100644 --- a/psycopg/adapter_list.c +++ b/psycopg/adapter_list.c @@ -45,7 +45,7 @@ list_quote(listObject *self) /* empty arrays are converted to NULLs (still searching for a way to insert an empty array in postgresql */ - if (len == 0) return PyString_FromString("'{}'"); + if (len == 0) return Text_FromUTF8("'{}'"); tmp = PyTuple_New(len); @@ -53,7 +53,7 @@ list_quote(listObject *self) PyObject *quoted; PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i); if (wrapped == Py_None) - quoted = PyString_FromString("NULL"); + quoted = Text_FromUTF8("NULL"); else quoted = microprotocol_getquoted(wrapped, (connectionObject*)self->connection); @@ -67,11 +67,15 @@ list_quote(listObject *self) /* now that we have a tuple of adapted objects we just need to join them and put "ARRAY[] around the result */ - str = PyString_FromString(", "); + str = Text_FromUTF8(", "); joined = PyObject_CallMethod(str, "join", "(O)", tmp); if (joined == NULL) goto error; +#if PY_MAJOR_VERSION < 3 res = PyString_FromFormat("ARRAY[%s]", PyString_AsString(joined)); +#else + res = PyUnicode_FromFormat("ARRAY[%U]", joined); +#endif error: Py_XDECREF(tmp); diff --git a/psycopg/adapter_pboolean.c b/psycopg/adapter_pboolean.c index a74d9f37..cdd3ef4e 100644 --- a/psycopg/adapter_pboolean.c +++ b/psycopg/adapter_pboolean.c @@ -39,17 +39,17 @@ pboolean_str(pbooleanObject *self) { #ifdef PSYCOPG_NEW_BOOLEAN if (PyObject_IsTrue(self->wrapped)) { - return PyString_FromString("true"); + return Text_FromUTF8("true"); } else { - return PyString_FromString("false"); + return Text_FromUTF8("false"); } #else if (PyObject_IsTrue(self->wrapped)) { - return PyString_FromString("'t'"); + return Text_FromUTF8("'t'"); } else { - return PyString_FromString("'f'"); + return Text_FromUTF8("'f'"); } #endif } diff --git a/psycopg/adapter_pdecimal.c b/psycopg/adapter_pdecimal.c index ed79ea1a..fa4acfab 100644 --- a/psycopg/adapter_pdecimal.c +++ b/psycopg/adapter_pdecimal.c @@ -45,7 +45,7 @@ pdecimal_str(pdecimalObject *self) goto end; } else if (check) { - res = PyString_FromString("'NaN'::numeric"); + res = Text_FromUTF8("'NaN'::numeric"); goto end; } @@ -57,7 +57,7 @@ pdecimal_str(pdecimalObject *self) goto end; } if (PyObject_IsTrue(check)) { - res = PyString_FromString("'NaN'::numeric"); + res = Text_FromUTF8("'NaN'::numeric"); goto end; } @@ -66,7 +66,7 @@ pdecimal_str(pdecimalObject *self) goto end; } if (PyObject_IsTrue(check)) { - res = PyString_FromString("'NaN'::numeric"); + res = Text_FromUTF8("'NaN'::numeric"); goto end; } diff --git a/psycopg/adapter_pfloat.c b/psycopg/adapter_pfloat.c index f6ad6361..3260d2cd 100644 --- a/psycopg/adapter_pfloat.c +++ b/psycopg/adapter_pfloat.c @@ -40,9 +40,9 @@ pfloat_str(pfloatObject *self) { double n = PyFloat_AsDouble(self->wrapped); if (isnan(n)) - return PyString_FromString("'NaN'::float"); + return Text_FromUTF8("'NaN'::float"); else if (isinf(n)) - return PyString_FromString("'Infinity'::float"); + return Text_FromUTF8("'Infinity'::float"); else return PyObject_Repr(self->wrapped); } diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c index 72240c84..c1082d8e 100644 --- a/psycopg/adapter_qstring.c +++ b/psycopg/adapter_qstring.c @@ -54,6 +54,7 @@ qstring_quote(qstringObject *self) if (str == NULL) return NULL; } +#if PY_MAJOR_VERSION < 3 /* if the wrapped object is a simple string, we don't know how to (re)encode it, so we pass it as-is */ else if (PyString_Check(self->wrapped)) { @@ -61,6 +62,7 @@ qstring_quote(qstringObject *self) /* INCREF to make it ref-wise identical to unicode one */ Py_INCREF(str); } +#endif /* if the wrapped object is not a string, this is an error */ else { @@ -70,7 +72,7 @@ qstring_quote(qstringObject *self) } /* encode the string into buffer */ - PyString_AsStringAndSize(str, &s, &len); + Bytes_AsStringAndSize(str, &s, &len); /* Call qstring_escape with the GIL released, then reacquire the GIL before verifying that the results can fit into a Python string; raise @@ -94,7 +96,8 @@ qstring_quote(qstringObject *self) return NULL; } - self->buffer = PyString_FromStringAndSize(buffer, qlen); + /* XXX need to decode in connection's encoding in 3.0 */ + self->buffer = Text_FromUTF8AndSize(buffer, qlen); PyMem_Free(buffer); Py_DECREF(str); diff --git a/psycopg/connection_int.c b/psycopg/connection_int.c index aa4eca70..21889938 100644 --- a/psycopg/connection_int.c +++ b/psycopg/connection_int.c @@ -76,7 +76,8 @@ conn_notice_process(connectionObject *self) while (notice != NULL) { PyObject *msg; - msg = PyString_FromString(notice->message); + /* XXX possible other encode I think */ + msg = Text_FromUTF8(notice->message); Dprintf("conn_notice_process: %s", notice->message); @@ -145,8 +146,9 @@ conn_notifies_process(connectionObject *self) (int) pgn->be_pid, pgn->relname); if (!(pid = PyInt_FromLong((long)pgn->be_pid))) { goto error; } - if (!(channel = PyString_FromString(pgn->relname))) { goto error; } - if (!(payload = PyString_FromString(pgn->extra))) { goto error; } + /* XXX in the connection encoding? */ + if (!(channel = Text_FromUTF8(pgn->relname))) { goto error; } + if (!(payload = Text_FromUTF8(pgn->extra))) { goto error; } if (!(notify = PyObject_CallFunctionObjArgs((PyObject *)&NotifyType, pid, channel, payload, NULL))) { @@ -222,15 +224,35 @@ conn_encoding_to_codec(const char *enc) { char *tmp; Py_ssize_t size; - PyObject *pyenc; + PyObject *pyenc = NULL; + PyObject *pybenc = NULL; char *rv = NULL; + /* Find the Py codec name from the PG encoding */ if (!(pyenc = PyDict_GetItemString(psycoEncodings, enc))) { PyErr_Format(OperationalError, "no Python codec for client encoding '%s'", enc); goto exit; } - if (-1 == PyString_AsStringAndSize(pyenc, &tmp, &size)) { + + /* Convert the codec in a bytes string to extract the c string. + * At the end of the block we have pybenc with a new ref. */ + if (PyUnicode_Check(pyenc)) { + if (!(pybenc = PyUnicode_AsEncodedString(pyenc, "ascii", NULL))) { + goto exit; + } + } + else if (Bytes_Check(pyenc)) { + Py_INCREF(pyenc); + pybenc = pyenc; + } + else { + PyErr_Format(PyExc_TypeError, "bad type for encoding: %s", + Py_TYPE(pyenc)->tp_name); + goto exit; + } + + if (-1 == Bytes_AsStringAndSize(pybenc, &tmp, &size)) { goto exit; } @@ -239,6 +261,7 @@ conn_encoding_to_codec(const char *enc) exit: /* pyenc is borrowed: no decref. */ + Py_XDECREF(pybenc); return rv; } @@ -1027,7 +1050,7 @@ conn_tpc_command(connectionObject *self, const char *cmd, XidObject *xid) /* convert the xid into PostgreSQL transaction id while keeping the GIL */ if (!(tid = xid_get_tid(xid))) { goto exit; } - if (!(ctid = PyString_AsString(tid))) { goto exit; } + if (!(ctid = Bytes_AsString(tid))) { goto exit; } Py_BEGIN_ALLOW_THREADS; pthread_mutex_lock(&self->lock); diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index 9e799bee..49c6b942 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -498,7 +498,7 @@ psyco_conn_get_parameter_status(connectionObject *self, PyObject *args) Py_INCREF(Py_None); return Py_None; } - return PyString_FromString(val); + return Text_FromUTF8(val); } diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index d9e925f1..4bf53139 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -87,7 +87,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) just before returning. we also init *new to NULL to exit with an error if we can't complete the mogrification */ n = *new = NULL; - c = PyString_AsString(fmt); + c = Bytes_AsString(fmt); while(*c) { /* handle plain percent symbol in format string */ @@ -116,7 +116,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) for (d = c + 2; *d && *d != ')'; d++); if (*d == ')') { - key = PyString_FromStringAndSize(c+2, (Py_ssize_t) (d-c-2)); + key = Bytes_FromStringAndSize(c+2, (Py_ssize_t) (d-c-2)); value = PyObject_GetItem(var, key); /* key has refcnt 1, value the original value + 1 */ @@ -144,7 +144,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) optimization over the adapting code and can go away in the future if somebody finds a None adapter usefull. */ if (value == Py_None) { - t = PyString_FromString("NULL"); + t = Text_FromUTF8("NULL"); PyDict_SetItem(n, key, t); /* t is a new object, refcnt = 1, key is at 2 */ @@ -220,7 +220,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) d = c+1; if (value == Py_None) { - PyTuple_SET_ITEM(n, index, PyString_FromString("NULL")); + PyTuple_SET_ITEM(n, index, Text_FromUTF8("NULL")); while (*d && !isalpha(*d)) d++; if (*d) *d = 's'; Py_DECREF(value); @@ -267,7 +267,7 @@ static PyObject *_psyco_curs_validate_sql_basic( goto fail; } - if (PyString_Check(sql)) { + if (Bytes_Check(sql)) { /* Necessary for ref-count symmetry with the unicode case: */ Py_INCREF(sql); } @@ -314,7 +314,7 @@ _psyco_curs_merge_query_args(cursorObject *self, the curren exception (we will later restore it if the type or the strings do not match.) */ - if (!(fquery = PyString_Format(query, args))) { + if (!(fquery = Text_Format(query, args))) { PyObject *err, *arg, *trace; int pe = 0; @@ -327,7 +327,7 @@ _psyco_curs_merge_query_args(cursorObject *self, if (PyObject_HasAttrString(arg, "args")) { PyObject *args = PyObject_GetAttrString(arg, "args"); PyObject *str = PySequence_GetItem(args, 0); - const char *s = PyString_AS_STRING(str); + const char *s = Bytes_AS_STRING(str); Dprintf("psyco_curs_execute: -> %s", s); @@ -397,9 +397,15 @@ _psyco_curs_execute(cursorObject *self, } if (self->name != NULL) { + #if PY_MAJOR_VERSION < 3 self->query = PyString_FromFormat( "DECLARE %s CURSOR WITHOUT HOLD FOR %s", self->name, PyString_AS_STRING(fquery)); + #else + self->query = PyUnicode_FromFormat( + "DECLARE %s CURSOR WITHOUT HOLD FOR %U", + self->name, fquery); + #endif Py_DECREF(fquery); } else { @@ -408,9 +414,15 @@ _psyco_curs_execute(cursorObject *self, } else { if (self->name != NULL) { + #if PY_MAJOR_VERSION < 3 self->query = PyString_FromFormat( "DECLARE %s CURSOR WITHOUT HOLD FOR %s", self->name, PyString_AS_STRING(operation)); + #else + self->query = PyUnicode_FromFormat( + "DECLARE %s CURSOR WITHOUT HOLD FOR %U", + self->name, operation); + #endif } else { /* Transfer reference ownership of the str in operation to @@ -423,7 +435,7 @@ _psyco_curs_execute(cursorObject *self, /* At this point, the SQL statement must be str, not unicode */ - res = pq_execute(self, PyString_AS_STRING(self->query), async); + res = pq_execute(self, Bytes_AS_STRING(self->query), async); Dprintf("psyco_curs_execute: res = %d, pgres = %p", res, self->pgres); if (res == -1) { goto fail; } @@ -950,7 +962,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args, PyObject *kwargs) sql[sl-2] = ')'; sql[sl-1] = '\0'; - operation = PyString_FromString(sql); + operation = Text_FromUTF8(sql); PyMem_Free((void*)sql); if (_psyco_curs_execute(self, operation, parameters, self->conn->async)) { @@ -1105,14 +1117,14 @@ static int _psyco_curs_copy_columns(PyObject *columns, char *columnlist) columnlist[0] = '('; while ((col = PyIter_Next(coliter)) != NULL) { - if (!PyString_Check(col)) { + if (!Bytes_Check(col)) { Py_DECREF(col); Py_DECREF(coliter); PyErr_SetString(PyExc_ValueError, "elements in column list must be strings"); return -1; } - PyString_AsStringAndSize(col, &colname, &collen); + Bytes_AsStringAndSize(col, &colname, &collen); if (offset + collen > DEFAULT_COPYBUFF - 2) { Py_DECREF(col); Py_DECREF(coliter); @@ -1417,7 +1429,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs) self->copyfile = file; /* At this point, the SQL statement must be str, not unicode */ - if (pq_execute(self, PyString_AS_STRING(sql), 0) != 1) { goto fail; } + if (pq_execute(self, Bytes_AS_STRING(sql), 0) != 1) { goto fail; } res = Py_None; Py_INCREF(res); diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c index d07becbe..0feffbcb 100644 --- a/psycopg/lobject_type.c +++ b/psycopg/lobject_type.c @@ -119,7 +119,7 @@ psyco_lobj_read(lobjectObject *self, PyObject *args) return NULL; } - res = PyString_FromStringAndSize(buffer, size); + res = Bytes_FromStringAndSize(buffer, size); PyMem_Free(buffer); return res; diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index f41d85f1..067729f2 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -83,7 +83,11 @@ _get_superclass_adapter(PyObject *obj, PyObject *proto) Py_ssize_t i, ii; type = Py_TYPE(obj); - if (!((Py_TPFLAGS_HAVE_CLASS & type->tp_flags) && type->tp_mro)) { + if (!( +#if PY_MAJOR_VERSION < 3 + (Py_TPFLAGS_HAVE_CLASS & type->tp_flags) && +#endif + type->tp_mro)) { /* has no mro */ return NULL; } @@ -134,7 +138,7 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) /* None is always adapted to NULL */ if (obj == Py_None) - return PyString_FromString("NULL"); + return Text_FromUTF8("NULL"); Dprintf("microprotocols_adapt: trying to adapt %s", Py_TYPE(obj)->tp_name); diff --git a/psycopg/notify_type.c b/psycopg/notify_type.c index 3d2308ad..e68daa38 100644 --- a/psycopg/notify_type.c +++ b/psycopg/notify_type.c @@ -78,7 +78,8 @@ notify_init(NotifyObject *self, PyObject *args, PyObject *kwargs) } if (!payload) { - payload = PyString_FromStringAndSize("", 0); + /* XXX review encoding */ + payload = Text_FromUTF8AndSize("", 0); } Py_CLEAR(self->pid); @@ -213,7 +214,7 @@ notify_repr(NotifyObject *self) PyObject *format = NULL; PyObject *args = NULL; - if (!(format = PyString_FromString("Notify(%r, %r, %r)"))) { + if (!(format = Text_FromUTF8("Notify(%r, %r, %r)"))) { goto exit; } @@ -225,7 +226,7 @@ notify_repr(NotifyObject *self) Py_INCREF(self->payload); PyTuple_SET_ITEM(args, 2, self->payload); - rv = PyString_Format(format, args); + rv = Text_Format(format, args); exit: Py_XDECREF(args); diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c index e15fc7ae..745919b9 100644 --- a/psycopg/pqpath.c +++ b/psycopg/pqpath.c @@ -980,14 +980,15 @@ _pq_fetch_tuples(cursorObject *curs) } Dprintf("_pq_fetch_tuples: using cast at %p (%s) for type %d", - cast, PyString_AS_STRING(((typecastObject*)cast)->name), + cast, Bytes_AS_STRING(((typecastObject*)cast)->name), PQftype(curs->pgres,i)); Py_INCREF(cast); PyTuple_SET_ITEM(curs->casts, i, cast); /* 1/ fill the other fields */ PyTuple_SET_ITEM(dtitem, 0, - PyString_FromString(PQfname(curs->pgres, i))); + /* XXX guaranteed to be ASCII/UTF8? */ + Text_FromUTF8(PQfname(curs->pgres, i))); PyTuple_SET_ITEM(dtitem, 1, type); /* 2/ display size is the maximum size of this field result tuples. */ @@ -1066,13 +1067,13 @@ _pq_copy_in_v3(cursorObject *curs) while (1) { o = PyObject_CallFunctionObjArgs(func, size, NULL); - if (!(o && PyString_Check(o) && (length = PyString_GET_SIZE(o)) != -1)) { + if (!(o && Bytes_Check(o) && (length = Bytes_GET_SIZE(o)) != -1)) { error = 1; } if (length == 0 || length > INT_MAX || error == 1) break; Py_BEGIN_ALLOW_THREADS; - res = PQputCopyData(curs->conn->pgconn, PyString_AS_STRING(o), + res = PQputCopyData(curs->conn->pgconn, Bytes_AS_STRING(o), /* Py_ssize_t->int cast was validated above */ (int) length); Dprintf("_pq_copy_in_v3: sent %d bytes of data; res = %d", @@ -1219,7 +1220,7 @@ pq_fetch(cursorObject *curs) /* backend status message */ Py_XDECREF(curs->pgstatus); - curs->pgstatus = PyString_FromString(PQcmdStatus(curs->pgres)); + curs->pgstatus = Text_FromUTF8(PQcmdStatus(curs->pgres)); switch(pgstatus) { diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 9b3f54cd..568cf6ec 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -126,17 +126,38 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) return NULL; } +#if PY_MAJOR_VERSION < 3 if (pyport && PyString_Check(pyport)) { - PyObject *pyint = PyInt_FromString(PyString_AsString(pyport), NULL, 10); - if (!pyint) goto fail; - /* Must use PyInt_AsLong rather than PyInt_AS_LONG, because - * PyInt_FromString can return a PyLongObject: */ - iport = PyInt_AsLong(pyint); - Py_DECREF(pyint); + PyObject *pyint = PyInt_FromString(PyString_AsString(pyport), NULL, 10); + if (!pyint) goto fail; + /* Must use PyInt_AsLong rather than PyInt_AS_LONG, because + * PyInt_FromString can return a PyLongObject: */ + iport = PyInt_AsLong(pyint); + Py_DECREF(pyint); + if (iport == -1 && PyErr_Occurred()) + goto fail; } else if (pyport && PyInt_Check(pyport)) { - iport = PyInt_AsLong(pyport); + iport = PyInt_AsLong(pyport); + if (iport == -1 && PyErr_Occurred()) + goto fail; } +#else + if (pyport && PyUnicode_Check(pyport)) { + PyObject *pyint = PyObject_CallFunction((PyObject*)&PyLong_Type, + "Oi", pyport, 10); + if (!pyint) goto fail; + iport = PyLong_AsLong(pyint); + Py_DECREF(pyint); + if (iport == -1 && PyErr_Occurred()) + goto fail; + } + else if (pyport && PyLong_Check(pyport)) { + iport = PyLong_AsLong(pyport); + if (iport == -1 && PyErr_Occurred()) + goto fail; + } +#endif else if (pyport != NULL) { PyErr_SetString(PyExc_TypeError, "port must be a string or int"); goto fail; @@ -288,13 +309,23 @@ psyco_adapters_init(PyObject *mod) PyTypeObject *type; microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&pfloatType); +#if PY_MAJOR_VERSION < 3 microprotocols_add(&PyInt_Type, NULL, (PyObject*)&asisType); +#endif microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); microprotocols_add(&PyBool_Type, NULL, (PyObject*)&pbooleanType); +#if PY_MAJOR_VERSION < 3 microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); +#endif microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); +#if PY_MAJOR_VERSION < 3 microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); +#else + microprotocols_add(&PyBytes_Type, NULL, (PyObject*)&binaryType); + microprotocols_add(&PyByteArray_Type, NULL, (PyObject*)&binaryType); + microprotocols_add(&PyMemoryView_Type, NULL, (PyObject*)&binaryType); +#endif microprotocols_add(&PyList_Type, NULL, (PyObject*)&listType); if ((type = (PyTypeObject*)psyco_GetDecimalType()) != NULL) @@ -407,7 +438,7 @@ static void psyco_encodings_fill(PyObject *dict) encodingPair *enc; for (enc = encodings; enc->pgenc != NULL; enc++) { - PyObject *value = PyString_FromString(enc->pyenc); + PyObject *value = Text_FromUTF8(enc->pyenc); PyDict_SetItemString(dict, enc->pgenc, value); Py_DECREF(value); } @@ -472,12 +503,18 @@ psyco_errors_init(void) dict = PyDict_New(); if (exctable[i].docstr) { - str = PyString_FromString(exctable[i].docstr); + str = Text_FromUTF8(exctable[i].docstr); PyDict_SetItemString(dict, "__doc__", str); } - if (exctable[i].base == 0) + if (exctable[i].base == 0) { + #if PY_MAJOR_VERSION < 3 base = PyExc_StandardError; + #else + /* StandardError is gone in 3.0 */ + base = NULL; + #endif + } else base = *exctable[i].base; @@ -546,13 +583,16 @@ psyco_set_error(PyObject *exc, PyObject *curs, const char *msg, if (err) { if (pgerror) { - t = PyString_FromString(pgerror); + /* XXX is this always ASCII? If not, it needs + to be decoded properly for Python 3. */ + t = Text_FromUTF8(pgerror); PyObject_SetAttrString(err, "pgerror", t); Py_DECREF(t); } if (pgcode) { - t = PyString_FromString(pgcode); + /* XXX likewise */ + t = Text_FromUTF8(pgcode); PyObject_SetAttrString(err, "pgcode", t); Py_DECREF(t); } @@ -703,12 +743,26 @@ static PyMethodDef psycopgMethods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; +#if PY_MAJOR_VERSION > 2 +static struct PyModuleDef psycopgmodule = { + PyModuleDef_HEAD_INIT, + "_psycopg", + NULL, + -1, + psycopgMethods, + NULL, + NULL, + NULL, + NULL +}; +#endif + PyMODINIT_FUNC -init_psycopg(void) +INIT_MODULE(_psycopg)(void) { static void *PSYCOPG_API[PSYCOPG_API_pointers]; - PyObject *module, *dict; + PyObject *module = NULL, *dict; PyObject *c_api_object; #ifdef PSYCOPG_DEBUG @@ -734,36 +788,36 @@ init_psycopg(void) Py_TYPE(&NotifyType) = &PyType_Type; Py_TYPE(&XidType) = &PyType_Type; - if (PyType_Ready(&connectionType) == -1) return; - if (PyType_Ready(&cursorType) == -1) return; - if (PyType_Ready(&typecastType) == -1) return; - if (PyType_Ready(&qstringType) == -1) return; - if (PyType_Ready(&binaryType) == -1) return; - if (PyType_Ready(&isqlquoteType) == -1) return; - if (PyType_Ready(&pbooleanType) == -1) return; - if (PyType_Ready(&pfloatType) == -1) return; - if (PyType_Ready(&pdecimalType) == -1) return; - if (PyType_Ready(&asisType) == -1) return; - if (PyType_Ready(&listType) == -1) return; - if (PyType_Ready(&chunkType) == -1) return; - if (PyType_Ready(&NotifyType) == -1) return; - if (PyType_Ready(&XidType) == -1) return; + if (PyType_Ready(&connectionType) == -1) goto exit; + if (PyType_Ready(&cursorType) == -1) goto exit; + if (PyType_Ready(&typecastType) == -1) goto exit; + if (PyType_Ready(&qstringType) == -1) goto exit; + if (PyType_Ready(&binaryType) == -1) goto exit; + if (PyType_Ready(&isqlquoteType) == -1) goto exit; + if (PyType_Ready(&pbooleanType) == -1) goto exit; + if (PyType_Ready(&pfloatType) == -1) goto exit; + if (PyType_Ready(&pdecimalType) == -1) goto exit; + if (PyType_Ready(&asisType) == -1) goto exit; + if (PyType_Ready(&listType) == -1) goto exit; + if (PyType_Ready(&chunkType) == -1) goto exit; + if (PyType_Ready(&NotifyType) == -1) goto exit; + if (PyType_Ready(&XidType) == -1) goto exit; #ifdef PSYCOPG_EXTENSIONS Py_TYPE(&lobjectType) = &PyType_Type; - if (PyType_Ready(&lobjectType) == -1) return; + if (PyType_Ready(&lobjectType) == -1) goto exit; #endif /* import mx.DateTime module, if necessary */ #ifdef HAVE_MXDATETIME Py_TYPE(&mxdatetimeType) = &PyType_Type; - if (PyType_Ready(&mxdatetimeType) == -1) return; + if (PyType_Ready(&mxdatetimeType) == -1) goto exit; if (mxDateTime_ImportModuleAndAPI() != 0) { Dprintf("initpsycopg: why marc hide mx.DateTime again?!"); PyErr_SetString(PyExc_ImportError, "can't import mx.DateTime module"); - return; + goto exit; } - if (psyco_adapter_mxdatetime_init()) { return; } + if (psyco_adapter_mxdatetime_init()) { goto exit; } #endif /* import python builtin datetime module, if available */ @@ -771,22 +825,22 @@ init_psycopg(void) if (pyDateTimeModuleP == NULL) { Dprintf("initpsycopg: can't import datetime module"); PyErr_SetString(PyExc_ImportError, "can't import datetime module"); - return; + goto exit; } /* Initialize the PyDateTimeAPI everywhere is used */ PyDateTime_IMPORT; - if (psyco_adapter_datetime_init()) { return; } + if (psyco_adapter_datetime_init()) { goto exit; } Py_TYPE(&pydatetimeType) = &PyType_Type; - if (PyType_Ready(&pydatetimeType) == -1) return; + if (PyType_Ready(&pydatetimeType) == -1) goto exit; /* import psycopg2.tz anyway (TODO: replace with C-level module?) */ pyPsycopgTzModule = PyImport_ImportModule("psycopg2.tz"); if (pyPsycopgTzModule == NULL) { Dprintf("initpsycopg: can't import psycopg2.tz module"); PyErr_SetString(PyExc_ImportError, "can't import psycopg2.tz module"); - return; + goto exit; } pyPsycopgTzLOCAL = PyObject_GetAttrString(pyPsycopgTzModule, "LOCAL"); @@ -794,7 +848,13 @@ init_psycopg(void) PyObject_GetAttrString(pyPsycopgTzModule, "FixedOffsetTimezone"); /* initialize the module and grab module's dictionary */ +#if PY_MAJOR_VERSION < 3 module = Py_InitModule("_psycopg", psycopgMethods); +#else + module = PyModule_Create(&psycopgmodule); +#endif + if (!module) { goto exit; } + dict = PyModule_GetDict(module); /* initialize all the module's exported functions */ @@ -812,9 +872,9 @@ init_psycopg(void) /* set some module's parameters */ PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION); PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver"); - PyModule_AddObject(module, "apilevel", PyString_FromString(APILEVEL)); + PyModule_AddObject(module, "apilevel", Text_FromUTF8(APILEVEL)); PyModule_AddObject(module, "threadsafety", PyInt_FromLong(THREADSAFETY)); - PyModule_AddObject(module, "paramstyle", PyString_FromString(PARAMSTYLE)); + PyModule_AddObject(module, "paramstyle", Text_FromUTF8(PARAMSTYLE)); /* put new types in module dictionary */ PyModule_AddObject(module, "connection", (PyObject*)&connectionType); @@ -866,4 +926,11 @@ init_psycopg(void) #endif Dprintf("initpsycopg: module initialization complete"); + +exit: +#if PY_MAJOR_VERSION > 2 + return module; +#else + return; +#endif } diff --git a/psycopg/python.h b/psycopg/python.h index b514c2c6..8fee7fd7 100644 --- a/psycopg/python.h +++ b/psycopg/python.h @@ -74,4 +74,66 @@ #define FORMAT_CODE_SIZE_T "%zu" #endif +/* Abstract from text type. Only supported for ASCII and UTF-8 */ +#if PY_MAJOR_VERSION < 3 +#define Text_Type PyString_Type +#define Text_Check(s) PyString_Check(s) +#define Text_Format(f,a) PyString_Format(f,a) +#define Text_FromUTF8(s) PyString_FromString(s) +#define Text_FromUTF8AndSize(s,n) PyString_FromStringAndSize(s,n) +/* f must contain exactly a %s placeholder */ +#define Text_FromFormatS(f,s) PyString_FromFormat(f, PyString_AsString(s)) +#define Text_S "%s" +#else +#define Text_Type PyUnicode_Type +#define Text_Check(s) PyUnicode_Check(s) +#define Text_Format(f,a) PyUnicode_Format(f,a) +#define Text_FromUTF8(s) PyUnicode_FromString(s) +#define Text_FromUTF8AndSize(s,n) PyUnicode_FromStringAndSize(s,n) +/* f must contain exactly a %U placeholder */ +#define Text_FromFormatS(f,s) PyUnicode_FromFormat(f, s) +#define Text_S "%U" +#endif + +#if PY_MAJOR_VERSION > 2 +#define PyInt_Type PyLong_Type +#define PyInt_AsLong PyLong_AsLong +#define PyInt_FromLong PyLong_FromLong +#define PyInt_FromSsize_t PyLong_FromSsize_t +#define PyString_FromFormat PyUnicode_FromFormat +#define Py_TPFLAGS_HAVE_ITER 0L +#define Py_TPFLAGS_HAVE_RICHCOMPARE 0L +#ifndef PyNumber_Int +#define PyNumber_Int PyNumber_Long +#endif +#endif /* PY_MAJOR_VERSION > 2 */ + +#if PY_MAJOR_VERSION < 3 +/* XXX BytesType -> Bytes_Type */ +#define BytesType PyString_Type +#define Bytes_Check PyString_Check +#define Bytes_AS_STRING PyString_AS_STRING +#define Bytes_GET_SIZE PyString_GET_SIZE +#define Bytes_Size PyString_Size +#define Bytes_AsString PyString_AsString +#define Bytes_AsStringAndSize PyString_AsStringAndSize +#define Bytes_FromStringAndSize PyString_FromStringAndSize +#else +#define BytesType PyBytes_Type +#define Bytes_Check PyBytes_Check +#define Bytes_AS_STRING PyBytes_AS_STRING +#define Bytes_GET_SIZE PyBytes_GET_SIZE +#define Bytes_Size PyBytes_Size +#define Bytes_AsString PyBytes_AsString +#define Bytes_AsStringAndSize PyBytes_AsStringAndSize +#define Bytes_FromStringAndSize PyBytes_FromStringAndSize +#endif + +/* Mangle the module name into the name of the module init function */ +#if PY_MAJOR_VERSION > 2 +#define INIT_MODULE(m) PyInit_ ## m +#else +#define INIT_MODULE(m) init ## m +#endif + #endif /* !defined(PSYCOPG_PYTHON_H) */ diff --git a/psycopg/typecast.c b/psycopg/typecast.c index 47c3c58b..a58f3a1d 100644 --- a/psycopg/typecast.c +++ b/psycopg/typecast.c @@ -429,24 +429,22 @@ typecast_del(void *self) static PyObject * typecast_call(PyObject *obj, PyObject *args, PyObject *kwargs) { - PyObject *string, *cursor; + char *string; + Py_ssize_t length; + PyObject *cursor; - if (!PyArg_ParseTuple(args, "OO", &string, &cursor)) { + if (!PyArg_ParseTuple(args, "z#O", &string, &length, &cursor)) { return NULL; } // If the string is not a string but a None value we're being called - // from a Python-defined caster. There is no need to convert, just - // return it. - - if (string == Py_None) { - Py_INCREF(string); - return string; + // from a Python-defined caster. + if (!string) { + Py_INCREF(Py_None); + return Py_None; } - return typecast_cast(obj, - PyString_AsString(string), PyString_Size(string), - cursor); + return typecast_cast(obj, string, length, cursor); } PyTypeObject typecastType = { @@ -560,7 +558,7 @@ typecast_from_python(PyObject *self, PyObject *args, PyObject *keywds) if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|O!OO", kwlist, &PyTuple_Type, &v, - &PyString_Type, &name, + &Text_Type, &name, &cast, &base)) { return NULL; } @@ -585,7 +583,7 @@ typecast_from_c(typecastObject_initlist *type, PyObject *dict) } } - name = PyString_FromString(type->name); + name = Text_FromUTF8(type->name); if (!name) goto end; while (type->values[len] != 0) len++; diff --git a/psycopg/typecast_basic.c b/psycopg/typecast_basic.c index 634fc456..20c7b81f 100644 --- a/psycopg/typecast_basic.c +++ b/psycopg/typecast_basic.c @@ -25,6 +25,7 @@ /** INTEGER - cast normal integers (4 bytes) to python int **/ +#if PY_MAJOR_VERSION < 3 static PyObject * typecast_INTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs) { @@ -37,6 +38,9 @@ typecast_INTEGER_cast(const char *s, Py_ssize_t len, PyObject *curs) } return PyInt_FromString((char *)s, NULL, 0); } +#else +#define typecast_INTEGER_cast typecast_LONGINTEGER_cast +#endif /** LONGINTEGER - cast long integers (8 bytes) to python long **/ @@ -59,23 +63,30 @@ static PyObject * typecast_FLOAT_cast(const char *s, Py_ssize_t len, PyObject *curs) { PyObject *str = NULL, *flo = NULL; - char *pend; if (s == NULL) {Py_INCREF(Py_None); return Py_None;} - str = PyString_FromStringAndSize(s, len); - flo = PyFloat_FromString(str, &pend); + str = Text_FromUTF8AndSize(s, len); +#if PY_MAJOR_VERSION < 3 + flo = PyFloat_FromString(str, NULL); +#else + flo = PyFloat_FromString(str); +#endif Py_DECREF(str); return flo; } /** STRING - cast strings of any type to python string **/ +#if PY_MAJOR_VERSION < 3 static PyObject * typecast_STRING_cast(const char *s, Py_ssize_t len, PyObject *curs) { if (s == NULL) {Py_INCREF(Py_None); return Py_None;} return PyString_FromStringAndSize(s, len); } +#else +#define typecast_STRING_cast typecast_UNICODE_cast +#endif /** UNICODE - cast strings of any type to a python unicode object **/ diff --git a/psycopg/typecast_binary.c b/psycopg/typecast_binary.c index 261fc6f2..e93aad57 100644 --- a/psycopg/typecast_binary.c +++ b/psycopg/typecast_binary.c @@ -53,6 +53,9 @@ chunk_repr(chunkObject *self) ); } +#if PY_MAJOR_VERSION < 3 + +/* XXX support 3.0 buffer protocol */ static Py_ssize_t chunk_getreadbuffer(chunkObject *self, Py_ssize_t segment, void **ptr) { @@ -82,6 +85,22 @@ static PyBufferProcs chunk_as_buffer = (charbufferproc) NULL }; +#else + +/* 3.0 buffer interface */ +int chunk_getbuffer(PyObject *_self, Py_buffer *view, int flags) +{ + chunkObject *self = (chunkObject*)_self; + return PyBuffer_FillInfo(view, _self, self->base, self->len, 1, flags); +} +static PyBufferProcs chunk_as_buffer = +{ + chunk_getbuffer, + NULL, +}; + +#endif + #define chunk_doc "memory chunk" PyTypeObject chunkType = { @@ -156,8 +175,13 @@ typecast_BINARY_cast(const char *s, Py_ssize_t l, PyObject *curs) /* size_t->Py_ssize_t cast was validated above: */ chunk->len = (Py_ssize_t) len; +#if PY_MAJOR_VERSION < 3 if ((res = PyBuffer_FromObject((PyObject *)chunk, 0, chunk->len)) == NULL) goto fail; +#else + if ((res = PyMemoryView_FromObject((PyObject*)chunk)) == NULL) + goto fail; +#endif /* PyBuffer_FromObject() created a new reference. We'll release our * reference held in 'chunk' in the 'cleanup' clause. */ diff --git a/psycopg/xid_type.c b/psycopg/xid_type.c index b7251359..51cd4490 100644 --- a/psycopg/xid_type.c +++ b/psycopg/xid_type.c @@ -150,11 +150,11 @@ xid_init(XidObject *self, PyObject *args, PyObject *kwargs) Py_XDECREF(tmp); tmp = self->gtrid; - self->gtrid = PyString_FromString(gtrid); + self->gtrid = Text_FromUTF8(gtrid); Py_XDECREF(tmp); tmp = self->bqual; - self->bqual = PyString_FromString(bqual); + self->bqual = Text_FromUTF8(bqual); Py_XDECREF(tmp); return 0; @@ -233,7 +233,7 @@ xid_repr(XidObject *self) PyObject *args = NULL; if (Py_None == self->format_id) { - if (!(format = PyString_FromString(""))) { + if (!(format = Text_FromUTF8(""))) { goto exit; } if (!(args = PyTuple_New(1))) { goto exit; } @@ -241,7 +241,7 @@ xid_repr(XidObject *self) PyTuple_SET_ITEM(args, 0, self->gtrid); } else { - if (!(format = PyString_FromString(""))) { + if (!(format = Text_FromUTF8(""))) { goto exit; } if (!(args = PyTuple_New(3))) { goto exit; } @@ -253,7 +253,7 @@ xid_repr(XidObject *self) PyTuple_SET_ITEM(args, 2, self->bqual); } - rv = PyString_Format(format, args); + rv = Text_Format(format, args); exit: Py_XDECREF(args); @@ -457,7 +457,7 @@ xid_get_tid(XidObject *self) if (!(ebqual = _xid_encode64(self->bqual))) { goto exit; } /* rv = "%d_%s_%s" % (format_id, egtrid, ebqual) */ - if (!(format = PyString_FromString("%d_%s_%s"))) { goto exit; } + if (!(format = Text_FromUTF8("%d_%s_%s"))) { goto exit; } if (!(args = PyTuple_New(3))) { goto exit; } Py_INCREF(self->format_id); @@ -465,7 +465,7 @@ xid_get_tid(XidObject *self) PyTuple_SET_ITEM(args, 1, egtrid); egtrid = NULL; PyTuple_SET_ITEM(args, 2, ebqual); ebqual = NULL; - if (!(rv = PyString_Format(format, args))) { goto exit; } + if (!(rv = Text_Format(format, args))) { goto exit; } } exit: @@ -621,7 +621,7 @@ XidObject * xid_from_string(PyObject *str) { XidObject *rv; - if (!(PyString_Check(str) || PyUnicode_Check(str))) { + if (!(Bytes_Check(str) || PyUnicode_Check(str))) { PyErr_SetString(PyExc_TypeError, "not a valid transaction id"); return NULL; }