Commit a4873dc6 authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #43 from NextThought/issue42

More consistency between Python and C impls.
parents be6e6ea7 4f3f61ed
...@@ -219,6 +219,8 @@ BTree_check(BTree *self) ...@@ -219,6 +219,8 @@ BTree_check(BTree *self)
return result; return result;
} }
#define _BGET_REPLACE_TYPE_ERROR 1
#define _BGET_ALLOW_TYPE_ERROR 0
/* /*
** _BTree_get ** _BTree_get
** **
...@@ -229,6 +231,14 @@ BTree_check(BTree *self) ...@@ -229,6 +231,14 @@ BTree_check(BTree *self)
** keyarg the key to search for, as a Python object ** keyarg the key to search for, as a Python object
** has_key true/false; when false, try to return the associated ** has_key true/false; when false, try to return the associated
** value; when true, return a boolean ** value; when true, return a boolean
** replace_type_err true/false: When true, ignore the TypeError from
** a key conversion issue, instead
** transforming it into a KeyError set. If
** you are just reading/searching, set to
** true. If you will be adding/updating,
** however, set to false. Or use
** _BGET_REPLACE_TYPE_ERROR
** and _BGET_ALLOW_TYPE_ERROR, respectively.
** Return ** Return
** When has_key false: ** When has_key false:
** If key exists, its associated value. ** If key exists, its associated value.
...@@ -239,14 +249,22 @@ BTree_check(BTree *self) ...@@ -239,14 +249,22 @@ BTree_check(BTree *self)
** If key doesn't exist, 0. ** If key doesn't exist, 0.
*/ */
static PyObject * static PyObject *
_BTree_get(BTree *self, PyObject *keyarg, int has_key) _BTree_get(BTree *self, PyObject *keyarg, int has_key, int replace_type_err)
{ {
KEY_TYPE key; KEY_TYPE key;
PyObject *result = NULL; /* guilty until proved innocent */ PyObject *result = NULL; /* guilty until proved innocent */
int copied = 1; int copied = 1;
COPY_KEY_FROM_ARG(key, keyarg, copied); COPY_KEY_FROM_ARG(key, keyarg, copied);
UNLESS (copied) return NULL; UNLESS (copied)
{
if (replace_type_err && PyErr_ExceptionMatches(PyExc_TypeError))
{
PyErr_Clear();
PyErr_SetObject(PyExc_KeyError, keyarg);
}
return NULL;
}
PER_USE_OR_RETURN(self, NULL); PER_USE_OR_RETURN(self, NULL);
if (self->len == 0) if (self->len == 0)
...@@ -289,7 +307,7 @@ Done: ...@@ -289,7 +307,7 @@ Done:
static PyObject * static PyObject *
BTree_get(BTree *self, PyObject *key) BTree_get(BTree *self, PyObject *key)
{ {
return _BTree_get(self, key, 0); return _BTree_get(self, key, 0, _BGET_REPLACE_TYPE_ERROR);
} }
/* Create a new bucket for the BTree or TreeSet using the class attribute /* Create a new bucket for the BTree or TreeSet using the class attribute
...@@ -1940,7 +1958,7 @@ BTree_getm(BTree *self, PyObject *args) ...@@ -1940,7 +1958,7 @@ BTree_getm(BTree *self, PyObject *args)
UNLESS (PyArg_ParseTuple(args, "O|O", &key, &d)) UNLESS (PyArg_ParseTuple(args, "O|O", &key, &d))
return NULL; return NULL;
if ((r=_BTree_get(self, key, 0))) if ((r=_BTree_get(self, key, 0, _BGET_REPLACE_TYPE_ERROR)))
return r; return r;
UNLESS (PyErr_ExceptionMatches(PyExc_KeyError)) UNLESS (PyErr_ExceptionMatches(PyExc_KeyError))
return NULL; return NULL;
...@@ -1952,7 +1970,7 @@ BTree_getm(BTree *self, PyObject *args) ...@@ -1952,7 +1970,7 @@ BTree_getm(BTree *self, PyObject *args)
static PyObject * static PyObject *
BTree_has_key(BTree *self, PyObject *key) BTree_has_key(BTree *self, PyObject *key)
{ {
return _BTree_get(self, key, 1); return _BTree_get(self, key, 1, _BGET_REPLACE_TYPE_ERROR);
} }
static PyObject * static PyObject *
...@@ -1965,7 +1983,7 @@ BTree_setdefault(BTree *self, PyObject *args) ...@@ -1965,7 +1983,7 @@ BTree_setdefault(BTree *self, PyObject *args)
if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj)) if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj))
return NULL; return NULL;
value = _BTree_get(self, key, 0); value = _BTree_get(self, key, 0, _BGET_ALLOW_TYPE_ERROR);
if (value != NULL) if (value != NULL)
return value; return value;
...@@ -1998,7 +2016,7 @@ BTree_pop(BTree *self, PyObject *args) ...@@ -1998,7 +2016,7 @@ BTree_pop(BTree *self, PyObject *args)
if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj)) if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj))
return NULL; return NULL;
value = _BTree_get(self, key, 0); value = _BTree_get(self, key, 0, _BGET_ALLOW_TYPE_ERROR);
if (value != NULL) if (value != NULL)
{ {
/* Delete key and associated value. */ /* Delete key and associated value. */
...@@ -2043,7 +2061,7 @@ BTree_pop(BTree *self, PyObject *args) ...@@ -2043,7 +2061,7 @@ BTree_pop(BTree *self, PyObject *args)
static int static int
BTree_contains(BTree *self, PyObject *key) BTree_contains(BTree *self, PyObject *key)
{ {
PyObject *asobj = _BTree_get(self, key, 1); PyObject *asobj = _BTree_get(self, key, 1, _BGET_REPLACE_TYPE_ERROR);
int result = -1; int result = -1;
if (asobj != NULL) if (asobj != NULL)
...@@ -2051,6 +2069,11 @@ BTree_contains(BTree *self, PyObject *key) ...@@ -2051,6 +2069,11 @@ BTree_contains(BTree *self, PyObject *key)
result = INT_AS_LONG(asobj) ? 1 : 0; result = INT_AS_LONG(asobj) ? 1 : 0;
Py_DECREF(asobj); Py_DECREF(asobj);
} }
else if (PyErr_ExceptionMatches(PyExc_KeyError))
{
PyErr_Clear();
result = 0;
}
return result; return result;
} }
......
...@@ -269,7 +269,7 @@ def _no_default_comparison(key): ...@@ -269,7 +269,7 @@ def _no_default_comparison(key):
lt = None # pragma: no cover PyPy3 lt = None # pragma: no cover PyPy3
if (lt is None and if (lt is None and
getattr(key, '__cmp__', None) is None): getattr(key, '__cmp__', None) is None):
raise TypeError("Can't use default __cmp__") raise TypeError("Object has default comparison")
class Bucket(_BucketBase): class Bucket(_BucketBase):
...@@ -863,7 +863,12 @@ class _Tree(_Base): ...@@ -863,7 +863,12 @@ class _Tree(_Base):
return child._findbucket(key) return child._findbucket(key)
def __contains__(self, key): def __contains__(self, key):
return key in (self._findbucket(self._to_key(key)) or ()) try:
tree_key = self._to_key(key)
except TypeError:
# Can't convert the key, so can't possibly be in the tree
return False
return key in (self._findbucket(tree_key) or ())
def has_key(self, key): def has_key(self, key):
index = self._search(key) index = self._search(key)
......
...@@ -143,6 +143,14 @@ class _TestIOBTreesBase(TypeTest): ...@@ -143,6 +143,14 @@ class _TestIOBTreesBase(TypeTest):
def _noneraises(self): def _noneraises(self):
self._makeOne()[None] = 1 self._makeOne()[None] = 1
def testStringAllowedInContains(self):
self.assertFalse('key' in self._makeOne())
def testStringKeyRaisesKeyErrorWhenMissing(self):
self.assertRaises(KeyError, self._makeOne().__getitem__, 'key')
def testStringKeyReturnsDefaultFromGetWhenMissing(self):
self.assertEqual(self._makeOne().get('key', 42), 42)
class TestIOBTrees(_TestIOBTreesBase, unittest.TestCase): class TestIOBTrees(_TestIOBTreesBase, unittest.TestCase):
......
...@@ -109,7 +109,7 @@ class OOBTreeTest(BTreeTests, unittest.TestCase): ...@@ -109,7 +109,7 @@ class OOBTreeTest(BTreeTests, unittest.TestCase):
self.assertEqual(list(tree.byValue(22)), self.assertEqual(list(tree.byValue(22)),
[(y, x) for x, y in reversed(ITEMS[22:])]) [(y, x) for x, y in reversed(ITEMS[22:])])
def testRejectDefaultComparison(self): def testRejectDefaultComparisonOnSet(self):
# Check that passing int keys w default comparison fails. # Check that passing int keys w default comparison fails.
# Only applies to new-style class instances. Old-style # Only applies to new-style class instances. Old-style
# instances are too hard to introspect. # instances are too hard to introspect.
...@@ -126,6 +126,11 @@ class OOBTreeTest(BTreeTests, unittest.TestCase): ...@@ -126,6 +126,11 @@ class OOBTreeTest(BTreeTests, unittest.TestCase):
self.assertRaises(TypeError, lambda : t.__setitem__(C(), 1)) self.assertRaises(TypeError, lambda : t.__setitem__(C(), 1))
with self.assertRaises(TypeError) as raising:
t[C()] = 1
self.assertEqual(raising.exception.args[0], "Object has default comparison")
if PY2: # we only check for __cmp__ on Python2 if PY2: # we only check for __cmp__ on Python2
class With___cmp__(object): class With___cmp__(object):
...@@ -145,6 +150,15 @@ class OOBTreeTest(BTreeTests, unittest.TestCase): ...@@ -145,6 +150,15 @@ class OOBTreeTest(BTreeTests, unittest.TestCase):
t.clear() t.clear()
def testAcceptDefaultComparisonOnGet(self):
# Issue #42
t = self._makeOne()
class C(object):
pass
self.assertEqual(t.get(C(), 42), 42)
self.assertRaises(KeyError, t.__getitem__, C())
self.assertFalse(C() in t)
class OOBTreePyTest(OOBTreeTest): class OOBTreePyTest(OOBTreeTest):
# #
......
...@@ -4,7 +4,19 @@ ...@@ -4,7 +4,19 @@
4.3.2 (unreleased) 4.3.2 (unreleased)
------------------ ------------------
- TBD - Make the CPython implementation consistent with the pure-Python
implementation and no longer raise ``TypeError`` for an object key
(in object-keyed trees) with default comparison on ``__getitem__``,
``get`` or ``in`` operations. Instead, the results will be a
``KeyError``, the default value, and ``False``, respectively.
Previously, CPython raised a ``TypeError`` in those cases, while the
Python implementation behaved as specified.
Likewise, non-integer keys in integer-keyed trees
will raise ``KeyError``, return the default and return ``False``,
respectively, in both implementations. Previously, pure-Python
raised a ``KeyError``, returned the default, and raised a
``TypeError``, while CPython raised ``TypeError`` in all three cases.
4.3.1 (2016-05-16) 4.3.1 (2016-05-16)
------------------ ------------------
...@@ -21,7 +33,7 @@ ...@@ -21,7 +33,7 @@
- When testing ``PURE_PYTHON`` environments under ``tox``, avoid poisoning - When testing ``PURE_PYTHON`` environments under ``tox``, avoid poisoning
the user's global wheel cache. the user's global wheel cache.
- Ensure that he pure-Python implementation, used on PyPy and when a C - Ensure that the pure-Python implementation, used on PyPy and when a C
compiler isn't available for CPython, pickles identically to the C compiler isn't available for CPython, pickles identically to the C
version. Unpickling will choose the best available implementation. version. Unpickling will choose the best available implementation.
This change prevents interoperability problems and database corruption if This change prevents interoperability problems and database corruption if
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment