python: Handle slight differences between Python 2 and 3.
authorJustus Winter <justus@g10code.com>
Tue, 13 Sep 2016 08:44:14 +0000 (10:44 +0200)
committerJustus Winter <justus@g10code.com>
Tue, 13 Sep 2016 11:29:43 +0000 (13:29 +0200)
* lang/python/helpers.c (pyDataWriteCb): Handle Python integers being
returned on Python 2.
(pyDataSeekCb): Likewise.
* lang/python/pyme/core.py (Data.__init__): Fix testing for string
argument.
(Data.new_from_filepart): Likewise.
* lang/python/pyme/util.py (is_a_string): New function.
* lang/python/tests/t-encrypt-large.py (read_cb): Force evaluation of
generator.
* lang/python/tests/t-idiomatic.py: Partly skip test on Python 2.
* lang/python/tests/t-verify.py (check_result): Here, the difference
between 2 and 3 really matters.  We cannot change the char *
conversion in Python 2 without breaking all existing applications, and
using bytestrings in Python 3 would be very inconvenient.

Signed-off-by: Justus Winter <justus@g10code.com>
lang/python/helpers.c
lang/python/pyme/core.py
lang/python/pyme/util.py
lang/python/tests/t-encrypt-large.py
lang/python/tests/t-idiomatic.py
lang/python/tests/t-verify.py

index 5b13fee..bc8aed4 100644 (file)
@@ -833,17 +833,21 @@ static ssize_t pyDataWriteCb(void *hook, const void *buffer, size_t size)
     goto leave;
   }
 
-  if (! PyLong_Check(retval)) {
+#if PY_MAJOR_VERSION < 3
+  if (PyInt_Check(retval))
+    result = PyInt_AsSsize_t(retval);
+  else
+#endif
+  if (PyLong_Check(retval))
+    result = PyLong_AsSsize_t(retval);
+  else {
     PyErr_Format(PyExc_TypeError,
-                 "expected int from read callback, got %s",
+                 "expected int from write callback, got %s",
                  retval->ob_type->tp_name);
     _pyme_stash_callback_exception(self);
     result = -1;
-    goto leave;
   }
 
-  result = PyLong_AsSsize_t(retval);
-
  leave:
   Py_XDECREF(retval);
   return result;
@@ -894,21 +898,25 @@ static off_t pyDataSeekCb(void *hook, off_t offset, int whence)
     goto leave;
   }
 
-  if (! PyLong_Check(retval)) {
+#if PY_MAJOR_VERSION < 3
+  if (PyInt_Check(retval))
+    result = PyInt_AsLong(retval);
+  else
+#endif
+  if (PyLong_Check(retval))
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+    result = PyLong_AsLongLong(retval);
+#else
+    result = PyLong_AsLong(retval);
+#endif
+  else {
     PyErr_Format(PyExc_TypeError,
-                 "expected int from read callback, got %s",
+                 "expected int from seek callback, got %s",
                  retval->ob_type->tp_name);
     _pyme_stash_callback_exception(self);
     result = -1;
-    goto leave;
   }
 
-#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
-  result = PyLong_AsLongLong(retval);
-#else
-  result = PyLong_AsLong(retval);
-#endif
-
  leave:
   Py_XDECREF(retval);
   return result;
index 4bbbc17..a71426b 100644 (file)
@@ -884,7 +884,7 @@ class Data(GpgmeWrapper):
         elif file != None and offset != None and length != None:
             self.new_from_filepart(file, offset, length)
         elif file != None:
-            if type(file) == type("x"):
+            if util.is_a_string(file):
                 self.new_from_file(file, copy)
             else:
                 self.new_from_fd(file)
@@ -961,7 +961,7 @@ class Data(GpgmeWrapper):
         filename = None
         fp = None
 
-        if type(file) == type("x"):
+        if util.is_a_string(file):
             filename = file
         else:
             fp = gpgme.fdopen(file.fileno(), file.mode)
index c4c9e18..bf25ccb 100644 (file)
@@ -16,6 +16,8 @@
 #    License along with this library; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
+import sys
+
 def process_constants(prefix, scope):
     """Called by the constant modules to load up the constants from the C
     library starting with PREFIX.  Matching constants will be inserted
@@ -36,3 +38,13 @@ def percent_escape(s):
         '%{0:2x}'.format(ord(c))
         if c == '+' or c == '"' or c == '%' or ord(c) <= 0x20 else c
         for c in s)
+
+# Python2/3 compatibility
+if sys.version_info[0] == 3:
+    # Python3
+    def is_a_string(x):
+        return isinstance(x, str)
+else:
+    # Python2
+    def is_a_string(x):
+        return isinstance(x, basestring)
index 69aed48..29f9de2 100755 (executable)
@@ -37,7 +37,7 @@ def read_cb(amount):
     ntoread -= chunk
     assert ntoread >= 0
     assert chunk >= 0
-    return bytes(random.randrange(256) for i in range(chunk))
+    return bytes(bytearray(random.randrange(256) for i in range(chunk)))
 
 nwritten = 0
 def write_cb(data):
index 1989c92..726bbb9 100755 (executable)
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import sys
 import io
 import os
 import tempfile
@@ -60,17 +61,21 @@ with tempfile.TemporaryFile() as source, \
 
     sign_and_verify(source, signed, sink)
 
-# XXX: Python's io.BytesIo.truncate does not work as advertised.
-# http://bugs.python.org/issue27261
-bio = io.BytesIO()
-bio.truncate(1)
-if len(bio.getvalue()) != 1:
-    # This version of Python is affected, preallocate buffer.
-    preallocate = 128*b'\x00'
-else:
-    preallocate = b''
+if sys.version_info[0] == 3:
+    # Python2's io.BytesIO does not implement the buffer interface,
+    # hence we cannot use it as sink.
 
-# Demonstrate automatic wrapping of objects implementing the buffer
-# interface, and the use of data objects with the 'with' statement.
-with io.BytesIO(preallocate) as signed, pyme.Data() as sink:
-    sign_and_verify(b"Hallo Leute\n", signed, sink)
+    # XXX: Python's io.BytesIo.truncate does not work as advertised.
+    # http://bugs.python.org/issue27261
+    bio = io.BytesIO()
+    bio.truncate(1)
+    if len(bio.getvalue()) != 1:
+        # This version of Python is affected, preallocate buffer.
+        preallocate = 128*b'\x00'
+    else:
+        preallocate = b''
+
+    # Demonstrate automatic wrapping of objects implementing the buffer
+    # interface, and the use of data objects with the 'with' statement.
+    with io.BytesIO(preallocate) as signed, pyme.Data() as sink:
+        sign_and_verify(b"Hallo Leute\n", signed, sink)
index b88bd07..ed5a91a 100755 (executable)
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import sys
 import os
 import pyme
 from pyme import core, constants, errors
@@ -67,8 +68,11 @@ def check_result(result, summary, validity, fpr, status, notation):
 
     if notation:
         expected_notations = {
-            "bar": b"\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f".decode() +
-            " das waren Umlaute und jetzt ein prozent%-Zeichen",
+            "bar": (b"\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f" +
+                    b" das waren Umlaute und jetzt ein prozent%-Zeichen"
+                    if sys.version_info[0] < 3 else
+                    b"\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f".decode() +
+                    " das waren Umlaute und jetzt ein prozent%-Zeichen"),
             "foobar.1":  "this is a notation data with 2 lines",
             None: "http://www.gu.org/policy/",
         }