Port to Python 3.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 22 Oct 2019 18:12:28 +0000 (19:12 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 11:44:21 +0000 (12:44 +0100)
Most of this is `#ifdef ...' ... `#endif' noise, with a few tweaks
thrown in.

Some notes on specific parts of the port.

  * buffer.c: The Python 3 buffer protocol is completely different.
    Read buffers work differently, but aren't problematic; write buffers
    can now be held open for an extended period, so we need the locking
    machinery that was added recently.

  * catacomb.c: Module initialization has changed, but isn't a great
    deal more difficult.

  * catacomb/__init__.py, catacomb/pwsafe.py, pock, pwsafe: There are a
    number of places which need language-version switches, but none of
    them is especially interesting.  This diff is noisier than it should
    be because I couldn't adjust the indentation in advance.

  * mp.c: With the abolition of a separate fixnum type, `mp_frompylong'
    needed to express the fast path from a fixnum in a different way.

  * pwsafe: The hacking to alter the error-handling behaviour associated
    with the `stdout' stream is deeply unpleasant.  Sorry.

  * pyke/pyke.h: Most of the porting work happens here, with alternative
    definitions for the various macros introduced earlier.

  * .gitignore: Ignore Python 3 `__pycache__/' turds.  Python 3 leaves
    its pre-tokenized files in `__pycache__/' directories, which somehow
    manage to be much more objectionable than the loose Python 2 `*.pyc'
    files.  Ignore these.

  * debian/: Add the necessary things to build a Python 3 extension
    package.

26 files changed:
.gitignore
MANIFEST.in
buffer.c
bytestring.c
catacomb.c
catacomb/__init__.py
catacomb/pwsafe.py
debian/control
debian/python-catacomb.install [new file with mode: 0644]
debian/python3-catacomb.install [new file with mode: 0644]
debian/rules
ec.c
field.c
group.c
mp.c
pgen.c
pock
pwsafe
pyke/mapping.c
pyke/pyke-mLib.c
pyke/pyke.c
pyke/pyke.h
t/t-bytes.py
t/t-convert.py
t/t-mp.py
t/testutils.py

index f81c80d..d245190 100644 (file)
@@ -1,4 +1,5 @@
 *.pyc
+__pycache__/
 
 /COPYING
 /MANIFEST
index 176ec8a..84b81d1 100644 (file)
@@ -26,3 +26,4 @@ recursive-include catacomb *.py
 ## Debian.
 include debian/rules debian/control debian/changelog
 include debian/copyright debian/compat debian/source/format
+include debian/*.install
index fe7633d..0d3d165 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -58,10 +58,18 @@ static void buf_pydealloc(PyObject *me)
   FREEOBJ(me);
 }
 
+#ifdef PY3
+  static int rbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f)
+  {
+    buf *b = BUF_B(me);
+    return (PyBuffer_FillInfo(vw, me, BCUR(b), BLEFT(b), 1, f));
+  }
+#else
   static Py_ssize_t rbuf_pysegcount(PyObject *me, Py_ssize_t *nn)
     { if (nn) *nn = BSZ(BUF_B(me)); return (1); }
   static Py_ssize_t rbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q)
     { assert(seg == 0); *q = BCUR(BUF_B(me)); return (BLEFT(BUF_B(me))); }
+#endif
 
 static PyObject *rbmeth_skip(PyObject *me, PyObject *arg)
 {
@@ -256,10 +264,15 @@ static const PyMethodDef rbuf_pymethods[] = {
 };
 
 static const PyBufferProcs rbuf_pybuffer = {
+#ifdef PY3
+  rbuf_pygetbuf,                       /* @bf_getbuffer@ */
+  0,                                   /* @bf_releasebuffer@ */
+#else
   rbuf_pyreadbuf,                      /* @bf_getreadbuffer@ */
   0,                                   /* @bf_getwritebuffer@ */
   rbuf_pysegcount,                     /* @bf_getsegcount@ */
   0                                    /* @bf_getcharbuffer@ */
+#endif
 };
 
 static const PyTypeObject rbuf_pytype_skel = {
@@ -355,10 +368,21 @@ end:
   return ((PyObject *)me);
 }
 
+#ifdef PY3
+  static int wbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f)
+  {
+    buf *b = BUF_B(me);
+    if (PyBuffer_FillInfo(vw, me, BBASE(b), BLEN(b), 0, f)) return (-1);
+    BUF_LK(me)++; return (0);
+  }
+  static void wbuf_pyrlsbuf(PyObject *me, Py_buffer *vw)
+    { BUF_LK(me)--; }
+#else
   static Py_ssize_t wbuf_pysegcount(PyObject *me, Py_ssize_t *nn)
     { if (nn) *nn = BLEN(BUF_B(me)); return (1); }
   static Py_ssize_t wbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q)
     { assert(seg == 0); *q = BBASE(BUF_B(me)); return (BLEN(BUF_B(me))); }
+#endif
 
 static PyObject *wbmeth_zero(PyObject *me, PyObject *arg)
 {
@@ -504,10 +528,15 @@ static const PyMethodDef wbuf_pymethods[] = {
 };
 
 static const PyBufferProcs wbuf_pybuffer = {
+#ifdef PY3
+  wbuf_pygetbuf,                       /* @bf_getbuffer@ */
+  wbuf_pyrlsbuf                                /* @bf_releasebuffer@ */
+#else
   wbuf_pyreadbuf,                      /* @bf_getreadbuffer@ */
   0,                                   /* @bf_getwritebuffer@ */
   wbuf_pysegcount,                     /* @bf_getsegcount@ */
   0                                    /* @bf_getcharbuffer@ */
+#endif
 };
 
 static const PyTypeObject wbuf_pytype_skel = {
index ced62b4..89f3e4a 100644 (file)
@@ -42,7 +42,9 @@ static PyObject *allocate(PyTypeObject *ty, size_t n)
 #if defined(CACHE_HASH) || PY_VERSION_HEX >= 0x02030000
   x->ob_shash = -1;
 #endif
+#ifdef PY2
   x->ob_sstate = SSTATE_NOT_INTERNED;
+#endif
   return ((PyObject *)x);
 }
 
@@ -172,7 +174,11 @@ static PyObject *bytestring_pyitem(PyObject *me, Py_ssize_t i)
   PyObject *rc = 0;
 
   if (i < 0 || i >= BIN_LEN(me)) IXERR("out of range");
+#ifdef PY3
+  rc = getulong(BIN_PTR(me)[i]&0xff);
+#else
   rc = bytestring_pywrap(BIN_PTR(me) + i, 1);
+#endif
 end:
   return (rc);
 }
@@ -206,8 +212,8 @@ static PyObject *bytestring_pysubscript(PyObject *me, PyObject *ix)
     if (i < 0) i += BIN_LEN(me);
     rc = bytestring_pyitem(me, i);
   } else if (PySlice_Check(ix)) {
-    if (PySlice_GetIndicesEx((PySliceObject *)ix, BIN_LEN(me),
-                            &i, &j, &k, &n))
+    if (PySlice_GetIndicesEx(PY23((PySliceObject *), NOTHING)ix,
+                            BIN_LEN(me), &i, &j, &k, &n))
       return (0);
     if (k == 1) return bytestring_pyslice(me, i, j);
     rc = bytestring_pywrap(0, n);
@@ -266,7 +272,9 @@ static const PyNumberMethods bytestring_pynumber = {
   0,                                   /* @nb_add@ */
   0,                                   /* @nb_subtract@ */
   0,                                   /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   0,                                   /* @nb_power@ */
index c1e91ff..b3d7e52 100644 (file)
@@ -321,7 +321,22 @@ static void init_random(void)
 #endif
 }
 
-EXPORT void init_base(void)
+#ifdef PY3
+static PyModuleDef moddef = {
+  PyModuleDef_HEAD_INIT,
+  "catacomb._base",                    /* @m_name@ */
+  "Low-level module for Catacomb bindings.  Use `catacomb' instead.",
+                                       /* @m_doc@ */
+  0,                                   /* @m_size@ */
+  0,                                   /* @m_methods@ */
+  0,                                   /* @m_slots@ */
+  0,                                   /* @m_traverse@ */
+  0,                                   /* @m_clear@ */
+  0                                    /* @m_free@ */
+};
+#endif
+
+EXPORT PyMODINIT_FUNC PY23(init_base, PyInit__base)(void)
 {
   PyObject *mod;
 
@@ -330,10 +345,18 @@ EXPORT void init_base(void)
   INIT_MODULES;
   INITTYPE(thingtab, root);
   init_random();
+#ifdef PY3
+  moddef.m_methods = donemethods();
+  mod = PyModule_Create(&moddef);
+#else
   mod = Py_InitModule("catacomb._base", donemethods());
+#endif
   INSERT_MODULES;
   INSERT("_MiscTable", thingtab_pytype);
   INSERT("smallprimes", smallprimes());
+#ifdef PY3
+  return (mod);
+#endif
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index 5483cca..0e5c31c 100644 (file)
@@ -55,7 +55,8 @@ if _dlflags >= 0:
       else: pass # can't do this.
   _sys.setdlopenflags(_dlflags)
 
-import _base
+if _sys.version_info >= (3,): from . import _base
+else: import _base
 
 if _odlflags >= 0:
   _sys.setdlopenflags(_odlflags)
@@ -78,14 +79,23 @@ def default_lostexchook(why, ty, val, tb):
 lostexchook = default_lostexchook
 
 ## Text/binary conversions.
-def _bin(s): return s
+if _sys.version_info >= (3,):
+  def _bin(s): return s.encode('iso8859-1')
+else:
+  def _bin(s): return s
 
 ## Iterating over dictionaries.
-def _iteritems(dict): return dict.iteritems()
-def _itervalues(dict): return dict.itervalues()
+if _sys.version_info >= (3,):
+  def _iteritems(dict): return dict.items()
+  def _itervalues(dict): return dict.values()
+else:
+  def _iteritems(dict): return dict.iteritems()
+  def _itervalues(dict): return dict.itervalues()
 
 ## The built-in bignum type.
-_long = long
+try: long
+except NameError: _long = int
+else: _long = long
 
 ## How to fix a name back into the right identifier.  Alas, the rules are not
 ## consistent.
@@ -177,14 +187,32 @@ def _pp_dict(pp, items):
   _pp_commas(pp, p, items)
 
 ###--------------------------------------------------------------------------
+### Mappings.
+
+if _sys.version_info >= (3,):
+  class _tmp:
+    def __str__(me): return '%s(%r)' % (type(me).__name__, list(me))
+    __repr__ = __str__
+    def _repr_pretty_(me, pp, cyclep):
+      ind = _pp_bgroup_tyname(pp, me, '([')
+      _pp_commas(pp, pp.pretty, me)
+      pp.end_group(ind, '])')
+  _augment(_base._KeyView, _tmp)
+  _augment(_base._ValueView, _tmp)
+  _augment(_base._ItemView, _tmp)
+
+###--------------------------------------------------------------------------
 ### Bytestrings.
 
 class _tmp:
   def fromhex(x):
     return ByteString(_unhexify(x))
   fromhex = staticmethod(fromhex)
-  def hex(me): return _hexify(me)
-  __hex__ = hex
+  if _sys.version_info >= (3,):
+    def hex(me): return _hexify(me).decode()
+  else:
+    def hex(me): return _hexify(me)
+    __hex__ = hex
   def __repr__(me):
     return 'bytes(%r)' % me.hex()
 _augment(ByteString, _tmp)
@@ -388,8 +416,9 @@ class BaseRat (object):
   def __rtruediv__(me, you):
     n, d = _split_rat(you)
     return type(me)(me._d*n, me._n*d)
-  __div__ = __truediv__
-  __rdiv__ = __rtruediv__
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
   def _order(me, you, op):
     n, d = _split_rat(you)
     return op(me._n*d, n*me._d)
@@ -425,8 +454,9 @@ class _tmp:
   def __rtruediv__(me, you):
     if isinstance(you, float): return you/_long(me)
     else: return IntRat(you, me)
-  __div__ = __truediv__
-  __rdiv__ = __rtruediv__
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
   _repr_pretty_ = _pp_str
 _augment(MP, _tmp)
 
@@ -439,8 +469,9 @@ class _tmp:
   def quadsolve(x, y): return x.reduce().quadsolve(y)
   def __truediv__(me, you): return GFRat(me, you)
   def __rtruediv__(me, you): return GFRat(you, me)
-  __div__ = __truediv__
-  __rdiv__ = __rtruediv__
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
   _repr_pretty_ = _pp_str
 _augment(GF, _tmp)
 
index 8fa5ee6..3133d1e 100644 (file)
@@ -31,19 +31,28 @@ from __future__ import with_statement
 import binascii as _B
 import errno as _E
 import os as _OS
-from cStringIO import StringIO as _StringIO
+import sys as _SYS
+
+if _SYS.version_info >= (3,): from io import StringIO as _StringIO
+else: from cStringIO import StringIO as _StringIO
 
 import catacomb as _C
 
 ###--------------------------------------------------------------------------
 ### Python version portability.
 
-def _iterkeys(dict): return dict.iterkeys()
-def _itervalues(dict): return dict.itervalues()
-def _iteritems(dict): return dict.iteritems()
-
-def _bin(text): return text
-def _text(bin): return bin
+if _SYS.version_info >= (3,):
+  def _iterkeys(dict): return dict.keys()
+  def _itervalues(dict): return dict.values()
+  def _iteritems(dict): return dict.items()
+  def _bin(text): return text.encode(errors = "surrogateescape")
+  def _text(bin): return bin.decode(errors = "surrogateescape")
+else:
+  def _iterkeys(dict): return dict.iterkeys()
+  def _itervalues(dict): return dict.itervalues()
+  def _iteritems(dict): return dict.iteritems()
+  def _bin(text): return text
+  def _text(bin): return bin
 
 _NUL = _bin('\0')
 _CIPHER = _bin('cipher:')
index 7b7400d..2957d2e 100644 (file)
@@ -2,9 +2,10 @@ Source: catacomb-python
 Section: python
 Priority: extra
 XS-Python-Version: >= 2.6, << 2.8
+XS-Python3-Version: >= 3.0
 Maintainer: Mark Wooding <mdw@distorted.org.uk>
 Build-Depends: debhelper (>= 10), dh-python, pkg-config,
-       python (>= 2.6.6-3~), python-all-dev,
+       python (>= 2.6.6-3~), python-all-dev, python3-all-dev,
        mlib-dev (>= 2.4.99~), catacomb-dev (>= 2.5.0)
 Standards-Version: 3.8.0
 
@@ -26,3 +27,21 @@ Description: Python bindings for the Catacomb cryptographic library.
  clarity of source code and portability more than performance.
  .
  This package allows the Catacomb library to be used in Python programs.
+
+Package: python3-catacomb
+Architecture: any
+XB-Python-Version: ${python3:Versions}
+Depends: ${shlibs:Depends}, ${python3:Depends}
+Provides: ${python3:Provides}
+Description: Python 3 bindings for the Catacomb cryptographic library.
+ Catacomb is a cryptographic library.  It implements a large number of
+ encryption algorithms, hash functions, message authentication codes
+ and random number generators.  It has a multi-precision maths library,
+ for implementing public key schemes such as RSA, DSA and Diffie-Hellman.
+ It contains rudimentary key-management tools.
+ .
+ The objective of Catacomb is to make a crypto library which is
+ relatively straightforward to audit for security.  Its focus is on
+ clarity of source code and portability more than performance.
+ .
+ This package allows the Catacomb library to be used in Python 3 programs.
diff --git a/debian/python-catacomb.install b/debian/python-catacomb.install
new file mode 100644 (file)
index 0000000..4d1b425
--- /dev/null
@@ -0,0 +1,3 @@
+debian/tmp/usr/bin
+debian/tmp/usr/share/man
+debian/tmp/usr/lib/python2.*
diff --git a/debian/python3-catacomb.install b/debian/python3-catacomb.install
new file mode 100644 (file)
index 0000000..3cff559
--- /dev/null
@@ -0,0 +1 @@
+debian/tmp/usr/lib/python3.*
index 04d6f4e..fea60f6 100755 (executable)
@@ -1,10 +1,13 @@
 #! /usr/bin/make -f
-%:; dh $@ --parallel --with python2
+%:; dh $@ --parallel --with python2,python3
 
-export PYTHONS := $(shell pyversions -r)
+export PYTHON := $(shell pyversions -d)
+export PYTHONS := $(shell pyversions -r && py3versions -r)
 
 override_dh_auto_test:
        dh_auto_test -- OPTS-check=-V
 
 override_dh_auto_install:
-       dh_auto_install -- prefix=/usr
+       dh_auto_install -- prefix=/usr \
+               $(foreach p,$(filter-out $(PYTHON),$(PYTHONS)), \
+                       OPTS-install/$p="--install-scripts=/usr/bin-$p")
diff --git a/ec.c b/ec.c
index d631a91..459c272 100644 (file)
--- a/ec.c
+++ b/ec.c
@@ -589,6 +589,7 @@ end:
   return (rc);
 }
 
+#ifdef PY2
 static PyObject *ecpt_pylong(PyObject *me)
 {
   ec p = EC_INIT;
@@ -600,6 +601,7 @@ end:
   EC_DESTROY(&p);
   return (rc);
 }
+#endif
 
 static PyObject *ecpt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
 {
@@ -639,7 +641,9 @@ static const PyNumberMethods ecpt_pynumber = {
   0,                                   /* @nb_add@ */
   0,                                   /* @nb_subtract@ */
   0,                                   /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   0,                                   /* @nb_power@ */
@@ -653,17 +657,23 @@ static const PyNumberMethods ecpt_pynumber = {
   0,                                   /* @nb_and@ */
   0,                                   /* @nb_xor@ */
   0,                                   /* @nb_or@ */
+#ifdef PY2
   0,                                   /* @nb_coerce@ */
+#endif
   ecpt_pyint,                          /* @nb_int@ */
-  ecpt_pylong,                         /* @nb_long@ */
+  PY23(ecpt_pylong, 0),                        /* @nb_long@ */
   0,                                   /* @nb_float@ */
+#ifdef PY2
   0,                                   /* @nb_oct@ */
   0,                                   /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
@@ -731,7 +741,7 @@ static const PyTypeObject ecpt_pytype_skel = {
 
 static const PyMemberDef ecpt_pymembers[] = {
 #define MEMBERSTRUCT ecpt_pyobj
-  MEMRNM(curve, T_OBJECT, ob_type, READONLY,
+  MEMRNM(curve, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY,
                                    "P.curve -> elliptic curve containing P")
 #undef MEMBERSTRUCT
   { 0 }
@@ -765,7 +775,9 @@ static const PyNumberMethods ecptcurve_pynumber = {
   ecpt_pyadd,                          /* @nb_add@ */
   ecpt_pysub,                          /* @nb_subtract@ */
   ecpt_pymul,                          /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   0,                                   /* @nb_power@ */
@@ -779,12 +791,16 @@ static const PyNumberMethods ecptcurve_pynumber = {
   0,                                   /* @nb_and@ */
   0,                                   /* @nb_xor@ */
   0,                                   /* @nb_or@ */
+#ifdef PY2
   0,                                   /* @nb_coerce@ */
+#endif
   0,                                   /* @nb_int@ */
   0,                                   /* @nb_long@ */
   0,                                   /* @nb_float@ */
+#ifdef PY2
   0,                                   /* @nb_oct@ */
   0,                                   /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
diff --git a/field.c b/field.c
index 88793b0..b29d8a9 100644 (file)
--- a/field.c
+++ b/field.c
@@ -221,6 +221,7 @@ end:
 static Py_hash_t fe_pyhash(PyObject *me)
   { return (mphash(FE_X(me))); }
 
+#ifdef PY2
 static int fe_pycoerce(PyObject **x, PyObject **y)
 {
   mp *z;
@@ -241,6 +242,7 @@ static int fe_pycoerce(PyObject **x, PyObject **y)
 end:
   return (-1);
 }
+#endif
 
 static PyObject *fe_pyint(PyObject *x)
 {
@@ -253,6 +255,7 @@ static PyObject *fe_pyint(PyObject *x)
   return (rc);
 }
 
+#ifdef PY2
 static PyObject *fe_pylong(PyObject *x)
 {
   mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));
@@ -260,6 +263,7 @@ static PyObject *fe_pylong(PyObject *x)
   MP_DROP(xx);
   return (rc);
 }
+#endif
 
 #define BASEOP(name, radix, pre)                                       \
   static PyObject *fe_py##name(PyObject *x) {                          \
@@ -268,7 +272,9 @@ static PyObject *fe_pylong(PyObject *x)
     MP_DROP(xx);                                                       \
     return (rc);                                                       \
   }
+#ifdef PY2
 BASEOP(oct, 8, "0");
+#endif
 BASEOP(hex, 16, "0x");
 #undef BASEOP
 
@@ -322,7 +328,7 @@ static PyObject *feget__value(PyObject *me, void *hunoz)
 
 static const PyMemberDef fe_pymembers[] = {
 #define MEMBERSTRUCT fe_pyobj
-  MEMRNM(field, T_OBJECT, ob_type, READONLY,
+  MEMRNM(field, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY,
                        "X.field -> field containing X")
 #undef MEMBERSTRUCT
   { 0 }
@@ -354,7 +360,9 @@ static const PyNumberMethods fe_pynumber = {
   fe_pyadd,                            /* @nb_add@ */
   fe_pysub,                            /* @nb_subtract@ */
   fe_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
   fe_pydiv,                            /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   fe_pyexp,                            /* @nb_power@ */
@@ -368,17 +376,23 @@ static const PyNumberMethods fe_pynumber = {
   0,                                   /* @nb_and@ */
   0,                                   /* @nb_xor@ */
   0,                                   /* @nb_or@ */
+#ifdef PY2
   fe_pycoerce,                         /* @nb_coerce@ */
+#endif
   fe_pyint,                            /* @nb_int@ */
-  fe_pylong,                           /* @nb_long@ */
+  PY23(fe_pylong, 0),                  /* @nb_long@ */
   0 /* meaningless */,                 /* @nb_float@ */
+#ifdef PY2
   fe_pyoct,                            /* @nb_oct@ */
   fe_pyhex,                            /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
diff --git a/group.c b/group.c
index b51cb6a..6b26d9d 100644 (file)
--- a/group.c
+++ b/group.c
@@ -666,6 +666,7 @@ static PyObject *ge_pystr(PyObject *me)
   return (rc);
 }
 
+#ifdef PY2
 static PyObject *ge_pylong(PyObject *me)
 {
   mp *x = 0;
@@ -678,6 +679,7 @@ end:
   mp_drop(x);
   return (rc);
 }
+#endif
 
 static PyObject *ge_pyint(PyObject *me)
 {
@@ -957,7 +959,9 @@ static const PyNumberMethods ge_pynumber = {
   0,                                   /* @nb_add@ */
   0,                                   /* @nb_subtract@ */
   ge_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
   ge_pydiv,                            /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   ge_pyexp,                            /* @nb_power@ */
@@ -971,17 +975,23 @@ static const PyNumberMethods ge_pynumber = {
   0,                                   /* @nb_and@ */
   0,                                   /* @nb_xor@ */
   0,                                   /* @nb_or@ */
+#ifdef PY2
   0,                                   /* @nb_coerce@ */
+#endif
   ge_pyint,                            /* @nb_int@ */
-  ge_pylong,                           /* @nb_long@ */
+  PY23(ge_pylong, 0),                  /* @nb_long@ */
   0 /* meaningless */,                 /* @nb_float@ */
+#ifdef PY2
   0,                                   /* @nb_oct@ */
   0,                                   /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
diff --git a/mp.c b/mp.c
index 3f96a4e..e299a7d 100644 (file)
--- a/mp.c
+++ b/mp.c
@@ -56,6 +56,12 @@ mp *mp_frompylong(PyObject *obj)
   mp *x;
   mpw *p;
 
+#ifdef PY3
+  int ov;
+  long j = PyLong_AsLongAndOverflow(obj, &ov);
+  if (!ov) return mp_fromlong(MP_NEW, j);
+#endif
+
   sz = Py_SIZE(l);
   if (sz < 0) sz = -sz;
   bits = (unsigned long)sz * PyLong_SHIFT;
@@ -229,8 +235,10 @@ mp *tomp(PyObject *o)
       return (0);
     return (x);
   }
+#ifdef PY2
   else if (PyInt_Check(o))
     return (mp_fromlong(MP_NEW, PyInt_AS_LONG(o)));
+#endif
   else if ((l = PyNumber_Long(o)) != 0) {
     x = mp_frompylong(l);
     Py_DECREF(l);
@@ -503,7 +511,9 @@ static mp *gf_modinv_checked(mp *d, mp *x, mp *p)
 #define BASEOP(name, radix, pre)                                       \
   static PyObject *mp_py##name(PyObject *x)                            \
     { return mp_topystring(MP_X(x), radix, 0, pre, 0); }
+#ifdef PY2
 BASEOP(oct, 8, "0");
+#endif
 BASEOP(hex, 16, "0x");
 #undef BASEOP
 
@@ -515,8 +525,10 @@ static PyObject *mp_pyint(PyObject *x)
   if (!mp_tolong_checked(MP_X(x), &l, 0)) return (PyInt_FromLong(l));
   else return mp_topylong(MP_X(x));
 }
+#ifdef PY2
 static PyObject *mp_pylong(PyObject *x)
   { return (mp_topylong(MP_X(x))); }
+#endif
 static PyObject *mp_pyfloat(PyObject *x)
 {
   PyObject *l = mp_topylong(MP_X(x));
@@ -525,6 +537,7 @@ static PyObject *mp_pyfloat(PyObject *x)
   return (PyFloat_FromDouble(f));
 }
 
+#ifdef PY2
 #define COERCE(pre, PRE)                                               \
   static int pre##_pycoerce(PyObject **x, PyObject **y)                        \
   {                                                                    \
@@ -544,6 +557,7 @@ static PyObject *mp_pyfloat(PyObject *x)
 COERCE(mp, MP)
 COERCE(gf, GF)
 #undef COERCE
+#endif
 
 static PyObject *mp_pyrichcompare(PyObject *x, PyObject *y, int op)
 {
@@ -559,8 +573,10 @@ static PyObject *mp_pyrichcompare(PyObject *x, PyObject *y, int op)
   return (rc);
 }
 
+#ifdef PY2
 static int mp_pycompare(PyObject *x, PyObject *y)
   { return mp_cmp(MP_X(x), MP_X(y)); }
+#endif
 
 static PyObject *mp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
 {
@@ -930,7 +946,9 @@ static const PyNumberMethods mp_pynumber = {
   mp_pyadd,                            /* @nb_add@ */
   mp_pysub,                            /* @nb_subtract@ */
   mp_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   mp_pymod,                            /* @nb_remainder@ */
   mp_pydivmod,                         /* @nb_divmod@ */
   mp_pyexp,                            /* @nb_power@ */
@@ -944,17 +962,23 @@ static const PyNumberMethods mp_pynumber = {
   mp_pyand2c,                          /* @nb_and@ */
   mp_pyxor2c,                          /* @nb_xor@ */
   mp_pyor2c,                           /* @nb_or@ */
+#ifdef PY2
   mp_pycoerce,                         /* @nb_coerce@ */
+#endif
   mp_pyint,                            /* @nb_int@ */
-  mp_pylong,                           /* @nb_long@ */
+  PY23(mp_pylong, 0),                  /* @nb_long@ */
   mp_pyfloat,                          /* @nb_float@ */
+#ifdef PY2
   mp_pyoct,                            /* @nb_oct@ */
   mp_pyhex,                            /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
@@ -981,7 +1005,7 @@ static const PyTypeObject mp_pytype_skel = {
   0,                                   /* @tp_print@ */
   0,                                   /* @tp_getattr@ */
   0,                                   /* @tp_setattr@ */
-  mp_pycompare,                                /* @tp_compare@ */
+  PY23(mp_pycompare, 0),               /* @tp_compare@/@tp_as_async@ */
   mp_pyrepr,                           /* @tp_repr@ */
   PYNUMBER(mp),                                /* @tp_as_number@ */
   0,                                   /* @tp_as_sequence@ */
@@ -1000,13 +1024,16 @@ static const PyTypeObject mp_pytype_skel = {
   "Multiprecision integers, similar to `long' but more efficient and\n"
   "versatile.  Support all the standard arithmetic operations, with\n"
   "implicit conversions from `PrimeFilter', and other objects which\n"
-  "convert to `long'.\n"
+  "convert to `" PY23("long", "int") "'.\n"
   "\n"
   "Constructor MP(X, [radix = R]) attempts to convert X to an `MP'.  If\n"
   "X is a string, it's read in radix-R form, or we look for a prefix\n"
   "if R = 0.  Other acceptable things are field elements, elliptic curve\n"
+  PY23(
   "points, group elements, Python `int' and `long' objects, and anything\n"
-  "with an integer conversion.\n"
+  "with an integer conversion.\n",
+  "points, group elements, Python `int' objects, and anything with an\n"
+  "integer conversion.\n")
   "\n"
   "Notes:\n"
   "\n"
@@ -2115,7 +2142,9 @@ static const PyNumberMethods gf_pynumber = {
   gf_pyadd,                            /* @nb_add@ */
   gf_pysub,                            /* @nb_subtract@ */
   gf_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   gf_pymod,                            /* @nb_remainder@ */
   gf_pydivmod,                         /* @nb_divmod@ */
   gf_pyexp,                            /* @nb_power@ */
@@ -2129,17 +2158,23 @@ static const PyNumberMethods gf_pynumber = {
   gf_pyand,                            /* @nb_and@ */
   gf_pyxor,                            /* @nb_xor@ */
   gf_pyor,                             /* @nb_or@ */
+#ifdef PY2
   gf_pycoerce,                         /* @nb_coerce@ */
+#endif
   mp_pyint,                            /* @nb_int@ */
-  mp_pylong,                           /* @nb_long@ */
+  PY23(mp_pylong, 0),                  /* @nb_long@ */
   0 /* doesn't make any sense */,      /* @nb_float@ */
+#ifdef PY2
   mp_pyoct,                            /* @nb_oct@ */
   mp_pyhex,                            /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
@@ -2188,8 +2223,11 @@ static const PyTypeObject gf_pytype_skel = {
   "Constructor GF(X, [radix = R]) attempts to convert X to a `GF'.  If\n"
   "X is a string, it's read in radix-R form, or we look for a prefix\n"
   "if R = 0.  Other acceptable things are field elements, elliptic curve\n"
+  PY23(
   "points, group elements, Python `int' and `long' objects, and anything\n"
-  "with an integer conversion.\n"
+  "with an integer conversion.\n",
+  "points, group elements, Python `int' objects, and anything with an\n"
+  "integer conversion.\n")
   "\n"
   "The name is hopelessly wrong from a technical point of view, but\n"
   "but it's much easier to type than `p2' or `c2' or whatever.\n"
diff --git a/pgen.c b/pgen.c
index 777ec32..3c2a792 100644 (file)
--- a/pgen.c
+++ b/pgen.c
@@ -125,8 +125,10 @@ static PyObject *pfilt_pyint(PyObject *me)
   return (rc);
 }
 
+#ifdef PY2
 static PyObject *pfilt_pylong(PyObject *me)
   { return (mp_topylong(PFILT_F(me)->m)); }
+#endif
 
 static PyObject *pfget_x(PyObject *me, void *hunoz)
   { return (mp_pywrap(MP_COPY(PFILT_F(me)->m))); }
@@ -156,7 +158,9 @@ static const PyNumberMethods pfilt_pynumber = {
   0,                                   /* @nb_add@ */
   0,                                   /* @nb_subtract@ */
   0,                                   /* @nb_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_divide@ */
+#endif
   0,                                   /* @nb_remainder@ */
   0,                                   /* @nb_divmod@ */
   0,                                   /* @nb_power@ */
@@ -170,17 +174,23 @@ static const PyNumberMethods pfilt_pynumber = {
   0,                                   /* @nb_and@ */
   0,                                   /* @nb_xor@ */
   0,                                   /* @nb_or@ */
+#ifdef PY2
   0,                                   /* @nb_coerce@ */
+#endif
   pfilt_pyint,                         /* @nb_int@ */
-  pfilt_pylong,                                /* @nb_long@ */
+  PY23(pfilt_pylong, 0),               /* @nb_long@ */
   0,                                   /* @nb_float@ */
+#ifdef PY2
   0,                                   /* @nb_oct@ */
   0,                                   /* @nb_hex@ */
+#endif
 
   0,                                   /* @nb_inplace_add@ */
   0,                                   /* @nb_inplace_subtract@ */
   0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
   0,                                   /* @nb_inplace_divide@ */
+#endif
   0,                                   /* @nb_inplace_remainder@ */
   0,                                   /* @nb_inplace_power@ */
   0,                                   /* @nb_inplace_lshift@ */
diff --git a/pock b/pock
index f4aeaa9..5d7cc50 100644 (file)
--- a/pock
+++ b/pock
@@ -27,7 +27,7 @@
 ###--------------------------------------------------------------------------
 ### Imported modules.
 
-from sys import argv, stdin, stdout, stderr
+import sys as SYS; from sys import argv, stdin, stdout, stderr
 import os as OS
 import itertools as I
 import math as M
@@ -35,6 +35,10 @@ import optparse as OP
 
 import catacomb as C
 
+if SYS.version_info >= (3,):
+  xrange = range
+  range = lambda *args: list(xrange(*args))
+
 ###--------------------------------------------------------------------------
 ### Utilities.
 
diff --git a/pwsafe b/pwsafe
index dd46261..40a64ce 100644 (file)
--- a/pwsafe
+++ b/pwsafe
@@ -30,7 +30,7 @@
 from __future__ import with_statement
 
 from os import environ
-from sys import argv, exit, stdin, stdout, stderr
+import sys as SYS; from sys import argv, exit, stdin, stdout, stderr
 from getopt import getopt, GetoptError
 from fnmatch import fnmatch
 import re
@@ -41,8 +41,23 @@ from catacomb.pwsafe import *
 ###--------------------------------------------------------------------------
 ### Python version portability.
 
-def _text(bin): return bin
-def _bin(text): return text
+if SYS.version_info >= (3,):
+  import io as IO
+  def hack_stream(stream):
+    _enc = stream.encoding
+    _lbuf = stream.line_buffering
+    _nl = stream.newlines
+    return IO.TextIOWrapper(stream.detach(),
+                            encoding = _enc,
+                            line_buffering = _lbuf,
+                            newline = _nl,
+                            errors = "surrogateescape")
+  SYS.stdout = stdout = hack_stream(stdout)
+  def _text(bin): return bin.decode(errors = "surrogateescape")
+  def _bin(text): return text.encode(errors = "surrogateescape")
+else:
+  def _text(bin): return bin
+  def _bin(text): return text
 
 def _excval(): return SYS.exc_info()[1]
 
index 3ad1ac7..ad02d25 100644 (file)
@@ -257,6 +257,354 @@ static const PyTypeObject itemiter_pytype_skel = {
   0                                    /* @tp_is_gc@ */
 };
 
+/*----- Mapping views -----------------------------------------------------*/
+
+#ifdef PY3
+
+static PyTypeObject *keyview_pytype, *itemview_pytype, *valview_pytype;
+
+typedef struct view_pyobj {
+  PyObject_HEAD
+  PyObject *map;
+} view_pyobj;
+#define VIEW_MAP(o) (((view_pyobj *)(o))->map)
+
+static PyObject *gmap_mkview(PyObject *me, PyTypeObject *ty)
+{
+  view_pyobj *v = PyObject_NEW(view_pyobj, ty);
+  v->map = me; Py_INCREF(me);
+  return ((PyObject *)v);
+}
+
+static void view_pydealloc(PyObject *me)
+  { Py_DECREF(VIEW_MAP(me)); FREEOBJ(me); }
+
+#define BINOP(op, update)                                              \
+  static PyObject *view_py##op(PyObject *me, PyObject *you)            \
+  {                                                                    \
+    PyObject *set = 0;                                                 \
+    PyObject *rc = 0;                                                  \
+                                                                       \
+    set = PySet_New(me); if (!set) goto end;                           \
+    if (!PyObject_CallMethod(set, #update, "(O)", you)) goto end;      \
+    rc = set; set = 0;                                                 \
+  end:                                                                 \
+    Py_XDECREF(set);                                                   \
+    return (rc);                                                       \
+  }
+BINOP(and, intersection_update)
+BINOP(or, update)
+BINOP(xor, symmetric_difference_update)
+#undef BINOP
+
+static int all_contained_p(PyObject *x, PyObject *y)
+{
+  PyObject *i = 0, *e;
+  int b, rc = -1;
+
+  i = PyObject_GetIter(x); if (!i) goto end;
+  for (;;) {
+    e = PyIter_Next(i); if (!e) break;
+    b = PySequence_Contains(y, e); Py_DECREF(e); if (b < 0) goto end;
+    if (!b) { rc = 0; goto end; }
+  }
+  if (PyErr_Occurred()) goto end;
+  rc = 1;
+end:
+  Py_XDECREF(i);
+  return (rc);
+}
+
+static Py_ssize_t view_pysize(PyObject *me)
+  { return (PyMapping_Size(VIEW_MAP(me))); }
+
+static PyObject *view_pyrichcompare(PyObject *me, PyObject *you, int op)
+{
+  PyObject *map = ITER_MAP(me);
+  Py_ssize_t mysz, yoursz;
+  int b;
+
+  mysz = PyMapping_Size(map); if (mysz < 0) return (0);
+  yoursz = PyObject_Size(you);
+  if (yoursz < 0) { PyErr_Clear(); RETURN_NOTIMPL; }
+
+  switch (op) {
+    case Py_EQ:
+      if (mysz != yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_NE:
+      if (mysz != yoursz) RETURN_TRUE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_LT:
+      if (mysz >= yoursz) RETURN_FALSE;
+      b = all_contained_p(me, you);
+      break;
+    case Py_LE:
+      if (mysz > yoursz) RETURN_FALSE;
+      b = all_contained_p(me, you);
+      break;
+    case Py_GE:
+      if (mysz < yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    case Py_GT:
+      if (mysz <= yoursz) RETURN_FALSE;
+      b = all_contained_p(you, me);
+      break;
+    default:
+      abort();
+  }
+  if (b < 0) return (0);
+  return (getbool(b));
+}
+
+static PyObject *keyview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), keyiter_pytype)); }
+
+static int keyview_pyhaskey(PyObject *me, PyObject *k)
+{
+  PyObject *map = VIEW_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  return (gmops->lookup(map, k, 0) ? 1 : PyErr_Occurred() ? -1 : 0);
+}
+
+static int itemview_pyhaskey(PyObject *me, PyObject *it)
+{
+  PyObject *map = VIEW_MAP(me);
+  const struct gmap_ops *gmops = GMAP_OPS(map);
+  void *e;
+  int b;
+  PyObject *v;
+
+  if (!PyTuple_Check(it) || PyTuple_GET_SIZE(it) != 2) return (0);
+  e = gmops->lookup(map, PyTuple_GET_ITEM(it, 0), 0);
+  if (!e) return (PyErr_Occurred() ? -1 : 0);
+  v = gmops->entry_value(map, e); if (!v) return (-1);
+  b = PyObject_RichCompareBool(v, PyTuple_GET_ITEM(it, 1), Py_EQ);
+  Py_DECREF(v); return (b);
+}
+
+static PyObject *valview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), valiter_pytype)); }
+
+static PyObject *itemview_pyiter(PyObject *me)
+  { return (gmap_mkiter(VIEW_MAP(me), itemiter_pytype)); }
+
+static const PyNumberMethods view_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  0,                                   /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  0,                                   /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  view_pyand,                          /* @nb_and@ */
+  view_pyxor,                          /* @nb_xor@ */
+  view_pyor,                           /* @nb_or@ */
+  0,                                   /* @nb_coerce@ */
+  0,                                   /* @nb_int@ */
+  0,                                   /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+};
+
+static const PySequenceMethods keyview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  keyview_pyhaskey,                    /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject keyview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_KeyView",                          /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(keyview),                 /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "View of a mapping's keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  view_pyrichcompare,                  /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  keyview_pyiter,                      /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PySequenceMethods valview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  0,                                   /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject valview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_ValueView",                                /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(valview),                 /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "View of a mapping's values.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  valview_pyiter,                      /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PySequenceMethods itemview_pysequence = {
+  view_pysize,                         /* @sq_length@ */
+  0,                                   /* @sq_concat@ */
+  0,                                   /* @sq_repeat@ */
+  0,                                   /* @sq_item@ */
+  0,                                   /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  itemview_pyhaskey,                   /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyTypeObject itemview_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_ItemView",                         /* @tp_name@ */
+  sizeof(view_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  view_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(view),                      /* @tp_as_number@ */
+  PYSEQUENCE(itemview),                        /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "View of a mapping's key/value items.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  view_pyrichcompare,                  /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  itemview_pyiter,                     /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+#endif
+
 /*----- Other mapping protocol support ------------------------------------*/
 
 Py_ssize_t gmap_pysize(PyObject *me)
@@ -319,6 +667,19 @@ const PySequenceMethods gmap_pysequence = {
   0                                    /* @sq_inplace_repeat@ */
 };
 
+#ifdef PY3
+
+PyObject *gmapmeth_keys(PyObject *me)
+  { return (gmap_mkview(me, keyview_pytype)); }
+
+PyObject *gmapmeth_values(PyObject *me)
+  { return (gmap_mkview(me, valview_pytype)); }
+
+PyObject *gmapmeth_items(PyObject *me)
+  { return (gmap_mkview(me, itemview_pytype)); }
+
+#else
+
 PyObject *gmapmeth_has_key(PyObject *me, PyObject *arg)
 {
   PyObject *k;
@@ -408,6 +769,8 @@ PyObject *gmapmeth_itervalues(PyObject *me)
 PyObject *gmapmeth_iteritems(PyObject *me)
   { return (gmap_mkiter(me, itemiter_pytype)); }
 
+#endif
+
 PyObject *gmap_pyiter(PyObject *me)
   { return gmap_mkiter(me, keyiter_pytype); }
 
@@ -493,8 +856,12 @@ static int update_core(PyObject *me, PyObject *map)
   unsigned foundp;
   int rc = -1;
 
-  v = PyObject_CallMethod(map, "iteritems", 0);
+  v = PyObject_CallMethod(map, PY23("iteritems", "items"), 0);
+#ifdef PY3
+  if (v) { i = PyObject_GetIter(v); Py_DECREF(v); v = 0; }
+#else
   i = v; v = 0;
+#endif
 
   if (i) {
     for (;;) {
@@ -576,6 +943,11 @@ void pyke_gmap_pyinit(void)
   INITTYPE(keyiter, root);
   INITTYPE(itemiter, root);
   INITTYPE(valiter, root);
+#ifdef PY3
+  INITTYPE(keyview, root);
+  INITTYPE(valview, root);
+  INITTYPE(itemview, root);
+#endif
 }
 
 void pyke_gmap_pyinsert(PyObject *mod)
@@ -583,6 +955,11 @@ void pyke_gmap_pyinsert(PyObject *mod)
   INSERT("_KeyIter", keyiter_pytype);
   INSERT("_ValueIter", valiter_pytype);
   INSERT("_ItemIter", itemiter_pytype);
+#ifdef PY3
+  INSERT("_KeyView", keyview_pytype);
+  INSERT("_ValueView", valview_pytype);
+  INSERT("_ItemView", itemview_pytype);
+#endif
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index fd6f895..30f6735 100644 (file)
@@ -52,8 +52,10 @@ PyObject *getk64(kludge64 u)
   Py_DECREF(i); i = t;
   if ((j = PyLong_FromUnsignedLong(LO64(u))) == 0) goto end;
   if ((t = PyNumber_InPlaceOr(i, j)) == 0) goto end;
+#  ifdef PY2
   Py_DECREF(i); i = t;
   if ((t = PyNumber_Int(i)) == 0) goto end;
+#  endif
   rc = t;
 end:
   Py_XDECREF(i);
index 03f1c1f..330f15c 100644 (file)
@@ -50,11 +50,13 @@ int convulong(PyObject *o, void *pp)
   PyObject *t;
 
   if (!o) VALERR("can't delete");
+#ifdef PY2
   if (PyInt_Check(o)) {
     long i = PyInt_AS_LONG(o);
     if (i < 0) VALERR("must be nonnegative");
     *p = i;
   } else
+#endif
   {
     if ((t = PyNumber_Long(o)) == 0) goto end;
     *p = PyLong_AsUnsignedLong(t);
@@ -110,6 +112,7 @@ int convbin(PyObject *o, void *pp)
     r->sz = BIN_LEN(o);
     return (1);
   }
+#ifdef PY2
   if (PyUnicode_Check(o)) {
     o = _PyUnicode_AsDefaultEncodedString(o, 0);
     if (!o) return (0);
@@ -117,6 +120,7 @@ int convbin(PyObject *o, void *pp)
     r->sz = PyString_GET_SIZE(o);
     return (1);
   }
+#endif
   return (PyObject_AsReadBuffer(o, &r->p, &r->sz) ? 0 : 1);
 }
 
@@ -274,6 +278,9 @@ void *newtype(PyTypeObject *metaty,
   if (ty->ht_name)
     ty->ht_type.tp_name = TEXT_STR(ty->ht_name);
   ty->ht_slots = 0;
+#ifdef PY3
+  ty->ht_qualname = 0;
+#endif
   (void)PyObject_INIT(&ty->ht_type, metaty);
   Py_INCREF(metaty);
   return (ty);
@@ -281,6 +288,10 @@ void *newtype(PyTypeObject *metaty,
 
 void typeready(PyTypeObject *ty)
 {
+#ifdef PY3
+  PyHeapTypeObject *hty = (PyHeapTypeObject *)ty;
+  hty->ht_qualname = hty->ht_name;
+#endif
   PyType_Ready(ty);
   PyDict_SetItemString(ty->tp_dict, "__module__", modname);
 }
@@ -312,7 +323,8 @@ PyObject *mkexc(PyObject *mod, PyObject *base,
     while (mm->ml_name) {
       if ((func = PyCFunction_NewEx((/*unconst*/ PyMethodDef *)mm,
                                    0, mod)) == 0 ||
-         (meth = PyMethod_New(func, 0, exc)) == 0 ||
+         (meth = PY23(PyMethod_New(func, 0, exc),
+                      PyInstanceMethod_New(func))) == 0 ||
          PyDict_SetItemString(dict, mm->ml_name, meth))
        goto fail;
       Py_DECREF(func); func = 0;
index 06c30c2..a60fd76 100644 (file)
@@ -64,6 +64,15 @@ PRIVATE_SYMBOLS;
 
 /*----- Python version compatibility hacks --------------------------------*/
 
+/* Explicit version switching. */
+#if PY_VERSION_HEX >= 0x03000000
+#  define PY3 1
+#  define PY23(two, three) three
+#else
+#  define PY2 1
+#  define PY23(two, three) two
+#endif
+
 /* The handy `Py_TYPE' and `Py_SIZE' macros turned up in 2.6.  Define them if
  * they're not already here.
  */
@@ -82,6 +91,18 @@ PRIVATE_SYMBOLS;
 #  define PyVarObject_HEAD_INIT(super, sz) PyObject_HEAD_INIT(super) sz,
 #endif
 
+/* Python 3 doesn't have `int', only `long', even though it's called `int' at
+ * the Python level.  Provide some obvious macros to fill in the gaps.
+ */
+#ifdef PY3
+#  define PyInt_Check PyLong_Check
+#  define PyInt_FromLong PyLong_FromLong
+#  define PyInt_AS_LONG PyLong_AS_LONG
+#  define PyInt_AsLong PyLong_AsLong
+#  define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask
+#  define PyNumber_Int PyNumber_Long
+#endif
+
 /* Python 3.2 changed the type of hash values, so paper over this annoying
  * difference.
  */
@@ -89,6 +110,13 @@ PRIVATE_SYMBOLS;
   typedef long Py_hash_t;
 #endif
 
+/* Python 3 always has the `CHECKTYPES' behaviour, and doesn't define the
+ * flag.
+ */
+#ifdef PY3
+#  define Py_TPFLAGS_CHECKTYPES 0
+#endif
+
 /* Plain octet strings.  Python 2 calls these `str', while Python 3 calls
  * them `bytes'.  We call them `bin' here, and define the following.
  *
@@ -115,6 +143,25 @@ PRIVATE_SYMBOLS;
  #   * `YN' is a format character for `PyArg_ParseTuple...' for retrieving an
  *     octet string and length from any sort-of vaguely binary-ish object.
  */
+#ifdef PY3
+#  define BINOBJ PyBytesObject
+#  define BIN_TYPE PyBytes_Type
+#  define BIN_CHECK(obj) PyBytes_Check(obj)
+#  define BIN_PTR(obj) PyBytes_AS_STRING(obj)
+#  define BIN_LEN(obj) PyBytes_GET_SIZE(obj)
+#  define BIN_FROMSTR(str) PyBytes_FromString(str)
+#  define BIN_FROMSTRLEN(str, len) PyBytes_FromStringAndSize(str, len)
+#  define BIN_FORMAT PyBytes_FromFormat
+#  define BIN_VFORMAT PyBytes_FromFormatV
+#  define BIN_PREPAREWRITE(obj, ptr, sz) do {                          \
+     (obj) = PyBytes_FromStringAndSize(0, (sz));                       \
+     (ptr) = PyBytes_AS_STRING(obj);                                   \
+   } while (0)
+#  define BIN_DONEWRITE(obj, sz) do Py_SIZE(obj) = (sz); while (0)
+#  define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
+#  define Y "y"
+#  define YN "y#"
+#else
 #  define BINOBJ PyStringObject
 #  define BIN_TYPE PyString_Type
 #  define BIN_CHECK(obj) PyString_Check(obj)
@@ -132,6 +179,7 @@ PRIVATE_SYMBOLS;
 #  define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
 #  define Y "s"
 #  define YN "s#"
+#endif
 
 /* Text strings.  Both Python 2 and Python 3 call these `str', but they're
  * very different because a Python 3 `str' is Unicode inside.  When dealing
@@ -161,6 +209,52 @@ PRIVATE_SYMBOLS;
  *
  * (Use `s' and `s#' in `PyArg_ParseTuple...'.)
  */
+#ifdef PY3
+#  define TEXTOBJ PyUnicodeObject
+#  define TEXT_TYPE PyUnicode_Type
+#  define TEXT_CHECK(obj) PyUnicode_Check(obj)
+#  if PY_VERSION_HEX >= 0x03030000
+#    define TEXT_PTR(obj) PyUnicode_AsUTF8(obj)
+#    define TEXT_STR(obj) PyUnicode_AsUTF8(obj)
+#    define TEXT_PTRLEN(obj, ptr, len) do {                            \
+       Py_ssize_t len_;                                                        \
+       (ptr) = PyUnicode_AsUTF8AndSize((obj), &len_);                  \
+       (len) = len_;                                                   \
+     } while (0)
+#    define TEXT_PREPAREWRITE(obj, ptr, sz) do {                       \
+       (obj) = PyUnicode_New((sz), 127);                               \
+       (ptr) = PyUnicode_DATA(obj);                                    \
+     } while (0)
+#    define TEXT_DONEWRITE(obj, len) do {                              \
+       size_t len_ = (len);                                            \
+       assert(PyUnicode_IS_COMPACT_ASCII(obj));                                \
+       ((char *)PyUnicode_DATA(obj))[len_] = 0;                                \
+       ((PyASCIIObject *)(obj))->length = len_;                                \
+     } while (0)
+#  else
+#    define TEXT_PTR(obj) _PyUnicode_AsString(obj)
+#    define TEXT_STR(obj) _PyUnicode_AsString(obj)
+#    define TEXT_PTRLEN(obj, ptr, len) do {                            \
+       Py_ssize_t len_;                                                        \
+       (ptr) = _PyUnicode_AsStringAndSize((obj), &len_);               \
+       (len) = len_;                                                   \
+     } while (0)
+#    define TEXT_PREPAREWRITE(obj, ptr, sz) do {                       \
+       (obj) = PyBytes_FromStringAndSize(0, (sz));                     \
+       (ptr) = PyBytes_AS_STRING(obj);                                 \
+     } while (0)
+#    define TEXT_DONEWRITE(obj, len) do {                              \
+       PyObject *new_;                                                 \
+       Py_SIZE(obj) = (len);                                           \
+       new_ = PyUnicode_FromEncodedObject(obj, 0, 0);                  \
+       assert(new_); Py_DECREF(obj); (obj) = new_;                     \
+     } while (0)
+#  endif
+#  define TEXT_FORMAT PyUnicode_FromFormat
+#  define TEXT_VFORMAT PyUnicode_FromFormatV
+#  define TEXT_FROMSTR(str) PyUnicode_FromString(str)
+#  define TEXT_FROMSTRLEN(str, len) PyUnicode_FromStringAndSize(str, len)
+#else
 #  define TEXTOBJ PyStringObject
 #  define TEXT_TYPE PyString_Type
 #  define TEXT_CHECK(obj) PyString_Check(obj)
@@ -179,6 +273,7 @@ PRIVATE_SYMBOLS;
 #  define TEXT_DONEWRITE(obj, sz) do { Py_SIZE(obj) = (sz); } while (0)
 #  define TEXT_FROMSTR(str) PyString_FromString(str)
 #  define TEXT_FROMSTRLEN(str, len) PyString_FromStringAndSize(str, len)
+#endif
 
 /*----- Utilities for returning values and exceptions ---------------------*/
 
@@ -556,6 +651,13 @@ typedef struct gmap_pyobj {
 #define GMAP_NAMETHDECL(func, doc)                                     \
   extern PyObject *gmapmeth_##func(PyObject *);
 
+#ifdef PY3
+#  define GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                       \
+    NAMETH(keys,       "D.keys() -> LIST")                             \
+    NAMETH(values,     "D.values() -> LIST")                           \
+    NAMETH(items,      "D.items() -> LIST")                            \
+    KWMETH(get,                "D.get(KEY, [default = None]) -> VALUE")
+#else
 #  define GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                       \
     METH  (has_key,    "D.has_key(KEY) -> BOOL")                       \
     NAMETH(keys,       "D.keys() -> LIST")                             \
@@ -565,6 +667,7 @@ typedef struct gmap_pyobj {
     NAMETH(itervalues, "D.itervalues() -> ITER")                       \
     NAMETH(iteritems,  "D.iteritems() -> ITER")                        \
     KWMETH(get,                "D.get(KEY, [default = None]) -> VALUE")
+#endif
 
 #define GMAP_DOMETHODS(METH, KWMETH, NAMETH)                           \
   GMAP_DOROMETHODS(METH, KWMETH, NAMETH)                               \
index 36a3f9f..27b7e49 100644 (file)
@@ -47,10 +47,16 @@ class TestByteString (U.TestCase):
 
     x = C.ByteString(T.bin("once upon a time there was a string"))
 
-    ## Check that simple indexing works.
-    me.assertEqual(type(x[3]), C.ByteString)
-    me.assertEqual(x[3], 'e')
-    me.assertEqual(x[-5], 't')
+    ## Check that simple indexing works.  Alas the behaviour differs between
+    ## Python major versions.
+    if T.PY3:
+      me.assertEqual(type(x[3]), int)
+      me.assertEqual(x[3], 101)
+      me.assertEqual(x[-5], 116)
+    else:
+      me.assertEqual(type(x[3]), C.ByteString)
+      me.assertEqual(x[3], 'e')
+      me.assertEqual(x[-5], 't')
 
     ## Check out-of-range detection.
     x[34]; me.assertRaises(IndexError, lambda: x[35])
index 08c660f..cadf7b5 100644 (file)
@@ -54,7 +54,7 @@ class TestConvert (U.TestCase):
     for bad in [lambda x: [x]]:
       me.assertRaises(TypeError, pow, C.MP(5), bad(2))
       me.assertRaises(TypeError, pow, C.MP(5), bad(2), 7)
-      if not T.DEBUGP:
+      if not (T.PY2 and T.DEBUGP):
         ## Debug builds of Python 2 crash here, and it's not our fault.  Run
         ##
         ## $ python2.7-dbg -c 'pow(long(5), 2, [7])'
@@ -84,7 +84,7 @@ class TestConvert (U.TestCase):
       me.assertRaises(TypeError, pow, bad(5), C.GF(2), bad(7))
       me.assertRaises(TypeError, pow, bad(5), bad(2), C.GF(7))
       me.assertRaises(TypeError, pow, C.GF(5), bad(2), bad(7))
-      if not T.DEBUGP:
+      if not (T.PY2 and T.DEBUGP):
         ## Python bug: see above.
         me.assertRaises(TypeError, pow, C.GF(5), 2, bad(7))
 
index 93a1e2f..a1dd657 100644 (file)
--- a/t/t-mp.py
+++ b/t/t-mp.py
@@ -71,7 +71,8 @@ class TestMP (U.TestCase):
     me.assertEqual(str(y), '6556380541834372447694561492436749633')
     me.assertEqual(repr(y), 'MP(6556380541834372447694561492436749633)')
     me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
-    me.assertEqual(oct(y), '047353320450112516611472622536175135706501')
+    me.assertEqual(oct(y), T.py23('0', '0o') +
+                   '47353320450112516611472622536175135706501')
     try: bin
     except NameError: pass
     else: me.assertEqual(bin(C.MP(661438603)),
@@ -417,7 +418,8 @@ class TestGF (U.TestCase):
     me.assertEqual(str(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
     me.assertEqual(repr(y), 'GF(0x4eeb684a0954ec4ceb255e3e9778d41)')
     me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
-    me.assertEqual(oct(y), '047353320450112516611472622536175135706501')
+    me.assertEqual(oct(y), T.py23('0', '0o') +
+                   '47353320450112516611472622536175135706501')
     try: bin
     except NameError: pass
     else: me.assertEqual(bin(C.GF(661438603)),
index b63a902..0ba49c6 100644 (file)
@@ -37,17 +37,33 @@ import unittest as U
 ### Main code.
 
 ## Some compatibility hacks.
-import itertools as I
-def bin(x): return x
-range = xrange
-long = long
-imap = I.imap
-def byteseq(seq): return "".join(map(chr, seq))
-def iterkeys(m): return m.iterkeys()
-def itervalues(m): return m.itervalues()
-def iteritems(m): return m.iteritems()
-from cStringIO import StringIO
-MAXFIXNUM = SYS.maxint
+if SYS.version_info >= (3,):
+  PY2, PY3 = False, True
+  def bin(x): return x.encode('iso8859-1')
+  def py23(x, y): return y
+  range = range
+  byteseq = bytes
+  long = int
+  imap = map
+  def iterkeys(m): return m.keys()
+  def itervalues(m): return m.values()
+  def iteritems(m): return m.items()
+  from io import StringIO
+  MAXFIXNUM = SYS.maxsize
+else:
+  import itertools as I
+  PY2, PY3 = True, False
+  def bin(x): return x
+  def py23(x, y): return x
+  range = xrange
+  long = long
+  imap = I.imap
+  def byteseq(seq): return "".join(map(chr, seq))
+  def iterkeys(m): return m.iterkeys()
+  def itervalues(m): return m.itervalues()
+  def iteritems(m): return m.iteritems()
+  from cStringIO import StringIO
+  MAXFIXNUM = SYS.maxint
 
 DEBUGP = hasattr(SYS, "gettotalrefcount")
 
@@ -127,24 +143,86 @@ class ImmutableMappingTextMixin (U.TestCase):
       any = True
       if k >= limk: limk = k + 1
       me.assertTrue(me._mkkey(k) in map)
-      me.assertTrue(map.has_key(me._mkkey(k)))
+      if PY2: me.assertTrue(map.has_key(me._mkkey(k)))
       me.assertEqual(me._getvalue(map[me._mkkey(k)]), v)
       me.assertEqual(me._getvalue(map.get(me._mkkey(k))), v)
     if any: me.assertTrue(me._mkkey(k) in map)
-    me.assertFalse(map.has_key(me._mkkey(limk)))
+    if PY2: me.assertFalse(map.has_key(me._mkkey(limk)))
     me.assertRaises(KeyError, lambda: map[me._mkkey(limk)])
     me.assertEqual(map.get(me._mkkey(limk)), None)
-    for listfn, getfn in [(lambda x: x.keys(), me._getkey),
-                          (lambda x: x.values(), me._getvalue),
-                          (lambda x: x.items(), me._getitem)]:
-      rlist, mlist = listfn(map), listfn(model)
-      me.assertEqual(type(rlist), list)
-      rlist = B.map(getfn, rlist)
-      rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist)
-    for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey),
-                          (lambda x: x.itervalues(), me._getvalue),
-                          (lambda x: x.iteritems(), me._getitem)]:
-      me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model)))
+
+    if PY3:
+      empty = set()
+
+      for k, v in iteritems(map):
+        me.assertTrue(k in map.keys())
+        me.assertTrue((k, v) in map.items())
+      me.assertFalse(me._mkkey(limk) in map.keys())
+
+      for viewfn, getfn in [(lambda x: x.keys(), me._getkey),
+                            (lambda x: x.items(), me._getitem)]:
+        rview, rview2, mview = viewfn(map), viewfn(map), viewfn(model)
+        me.assertEqual(set(imap(getfn, rview)), set(mview))
+        me.assertEqual(rview, rview2)
+        me.assertEqual(rview, set(rview2))
+        me.assertEqual(rview | empty, set(rview))
+        me.assertEqual(rview | rview2, set(rview))
+        me.assertEqual(rview ^ empty, set(rview))
+        me.assertEqual(rview ^ rview, empty)
+        me.assertEqual(rview & empty, empty)
+        me.assertEqual(len(rview), len(model))
+
+        if any: subset = set(rview2); subset.pop()
+        superset = set(rview2); superset.add(object())
+
+        me.assertFalse(rview < rview2)
+        me.assertTrue(rview < superset)
+        me.assertFalse(superset < rview)
+        me.assertFalse(rview < empty)
+        if any:
+          me.assertTrue(empty < rview)
+          me.assertTrue(subset < rview)
+          me.assertFalse(rview < subset)
+
+        me.assertTrue(rview <= rview2)
+        me.assertTrue(rview <= superset)
+        me.assertFalse(superset <= rview)
+        if any:
+          me.assertTrue(empty <= rview)
+          me.assertFalse(rview <= empty)
+          me.assertTrue(subset <= rview)
+          me.assertFalse(rview <= subset)
+
+        me.assertTrue(rview >= rview2)
+        me.assertTrue(superset >= rview)
+        me.assertFalse(rview >= superset)
+        if any:
+          me.assertTrue(rview >= empty)
+          me.assertFalse(empty >= rview)
+          me.assertTrue(rview >= subset)
+          me.assertFalse(subset >= rview)
+
+        me.assertFalse(rview > rview2)
+        me.assertTrue(superset > rview)
+        me.assertFalse(rview > superset)
+        me.assertFalse(empty > rview)
+        if any:
+          me.assertTrue(rview > empty)
+          me.assertTrue(rview > subset)
+          me.assertFalse(subset > rview)
+
+    else:
+      for listfn, getfn in [(lambda x: x.keys(), me._getkey),
+                            (lambda x: x.values(), me._getvalue),
+                            (lambda x: x.items(), me._getitem)]:
+        rlist, mlist = listfn(map), listfn(model)
+        me.assertEqual(type(rlist), list)
+        rlist = B.map(getfn, rlist)
+        rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist)
+      for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey),
+                            (lambda x: x.itervalues(), me._getvalue),
+                            (lambda x: x.iteritems(), me._getitem)]:
+        me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model)))
 
 class MutableMappingTestMixin (ImmutableMappingTextMixin):
 
@@ -156,8 +234,16 @@ class MutableMappingTestMixin (ImmutableMappingTextMixin):
     map = emptymapfn()
     me.assertEqual(len(map), 0)
 
-    def check_views():
-      me.check_immutable_mapping(map, model)
+    if not PY3:
+      def check_views():
+        me.check_immutable_mapping(map, model)
+    else:
+      kview, iview, vview = map.keys(), map.items(), map.values()
+      def check_views():
+        me.check_immutable_mapping(map, model)
+        me.assertEqual(set(imap(me._getkey, kview)), model.keys())
+        me.assertEqual(set(imap(me._getitem, iview)), model.items())
+        me.assertEqual(set(imap(me._getvalue, vview)), set(model.values()))
 
     model = { 1: 101, 2: 202, 4: 404 }
     for k, v in iteritems(model): map[me._mkkey(k)] = me._mkvalue(v)