Commit 269dcdab authored by Jim Fulton's avatar Jim Fulton

Allow None as a special key (sorted smaller than all others).

parent eb8a9f1f
......@@ -113,7 +113,7 @@ class _BucketBase(_Base):
k = keys[i]
if k is key or k == key:
return i
if k < key:
if k is None or (key is not None and k < key):
low = i + 1
else:
high = i
......@@ -256,7 +256,7 @@ _object_lt = getattr(object, '__lt__', _marker)
def _no_default_comparison(key):
# Enforce test that key has non-default comparison.
if key is None:
raise TypeError("Can't use None as a key")
return
if type(key) is object:
raise TypeError("Can't use object() as keys")
lt = getattr(key, '__lt__', None)
......@@ -919,7 +919,7 @@ class _Tree(_Base):
max = self._to_key(max)
index = self._search(max)
if index and data[index].child.minKey() > max:
if index and cmp(data[index].child.minKey(), max) > 0:
index -= 1 #pragma: no cover no idea how to provoke this
return data[index].child.maxKey(max)
......@@ -1014,7 +1014,7 @@ class _Tree(_Base):
self._p_changed = True
# fix up the node key, but not for the 0'th one.
if index > 0 and child.size and key == data[index].key:
if index > 0 and child.size and cmp(key, data[index].key) == 0:
self._p_changed = True
data[index].key = child.minKey()
......@@ -1269,7 +1269,7 @@ class Tree(_Tree):
def byValue(self, min):
return reversed(
sorted((v, k) for (k, v) in self.iteritems() if v >= min))
sorted(((v, k) for (k, v) in self.iteritems() if v >= min)))
def insert(self, key, value):
return bool(self._set(key, value, True)[0])
......
......@@ -33,8 +33,9 @@
-1 -> There was an error, which the caller will detect with PyError_Occurred.
*/
#define COMPARE(lhs, rhs) \
PyObject_RichCompareBool((lhs), (rhs), Py_LT) != 0 ? -1 : \
(PyObject_RichCompareBool((lhs), (rhs), Py_EQ) > 0 ? 0 : 1)
(lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \
(PyObject_RichCompareBool((lhs), (rhs), Py_LT) != 0 ? -1 : \
(PyObject_RichCompareBool((lhs), (rhs), Py_EQ) > 0 ? 0 : 1))))
#else
......@@ -45,7 +46,9 @@
#define TEXT_FROM_STRING PyString_FromString
#define TEXT_FORMAT PyString_Format
#define COMPARE(lhs, rhs) PyObject_Compare((lhs), (rhs))
#define COMPARE(lhs, rhs) \
(lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \
PyObject_Compare((lhs), (rhs))))
#endif
......
......@@ -13,6 +13,7 @@
##############################################################################
import sys
None_ = None
if sys.version_info[0] < 3: #pragma NO COVER Python2
PY2 = True
......@@ -23,7 +24,17 @@ if sys.version_info[0] < 3: #pragma NO COVER Python2
int_types = int, long
xrange = xrange
cmp = cmp
cmp_ = cmp
def cmp(x, y):
if x is None_:
if y is None_:
return 0
else:
return -1
elif y is None_:
return 1
else:
return cmp_(x, y)
_bytes = str
def _ascii(x):
......@@ -44,6 +55,14 @@ else: #pragma NO COVER Python3
xrange = range
def cmp(x, y):
if x is None_:
if y is None_:
return 0
else:
return -1
elif y is None_:
return 1
else:
return (x > y) - (y > x)
_bytes = bytes
......
......@@ -56,6 +56,8 @@ from BTrees.utils import oid_repr
TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3)
from ._compat import cmp
_type2kind = {}
for kv in ('OO',
'II', 'IO', 'OI', 'IF',
......@@ -346,13 +348,13 @@ class Checker(Walker):
def check_sorted(self, obj, path, keys, lo, hi):
i, n = 0, len(keys)
for x in keys:
if lo is not None and not lo <= x:
if lo is not None and not cmp(lo, x) <= 0:
s = "key %r < lower bound %r at index %d" % (x, lo, i)
self.complain(s, obj, path)
if hi is not None and not x < hi:
if hi is not None and not cmp(x, hi) < 0:
s = "key %r >= upper bound %r at index %d" % (x, hi, i)
self.complain(s, obj, path)
if i < n-1 and not x < keys[i+1]:
if i < n-1 and not cmp(x, keys[i+1]) < 0:
s = "key %r at index %d >= key %r at index %d" % (
x, i, keys[i+1], i+1)
self.complain(s, obj, path)
......
......@@ -15,6 +15,9 @@ check_argument_cmp(PyObject *arg)
/* ((PyTypeObject *)object_)->ob_type->tp_richcompare, */
/* arg->ob_type->tp_compare, */
/* ((PyTypeObject *)object_)->ob_type->tp_compare); */
if (arg == Py_None) {
return 1;
}
#ifdef PY3K
if (Py_TYPE(arg)->tp_richcompare == Py_TYPE(object_)->tp_richcompare)
......
......@@ -161,6 +161,32 @@ class OOBTreeTest(BTreeTests, unittest.TestCase):
self.assertRaises(KeyError, t.__getitem__, C())
self.assertFalse(C() in t)
def test_None_is_smallest(self):
t = self._makeOne()
for i in range(999): # Make sure we multiple buckets
t[i] = i*i
t[None] = -1
for i in range(-99,0): # Make sure we multiple buckets
t[i] = i*i
self.assertEqual(list(t), [None] + list(range(-99, 999)))
self.assertEqual(list(t.values()),
[-1] + [i*i for i in range(-99, 999)])
self.assertEqual(t[2], 4)
self.assertEqual(t[-2], 4)
self.assertEqual(t[None], -1)
t[None] = -2
self.assertEqual(t[None], -2)
t2 = t.__class__(t)
del t[None]
self.assertEqual(list(t), list(range(-99, 999)))
if 'Py' in self.__class__.__name__:
return
from BTrees.OOBTree import difference, union, intersection
self.assertEqual(list(difference(t2, t).items()), [(None, -2)])
self.assertEqual(list(union(t, t2)), list(t2))
self.assertEqual(list(intersection(t, t2)), list(t))
@_skip_under_Py3k
def testDeleteNoneKey(self):
# Check that a None key can be deleted in Python 2.
......
``BTrees`` Changelog
====================
- Allow None as a special key (sorted smaller than all others).
4.3.2 (2017-01-05)
------------------
......
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