Merge branch 'range_sort'

This commit is contained in:
Daniele Varrazzo 2014-02-22 23:02:19 +00:00
commit 283dbccf56
5 changed files with 126 additions and 12 deletions

2
NEWS
View File

@ -13,6 +13,8 @@ Bug fixes:
What's new in psycopg 2.5.3 What's new in psycopg 2.5.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added arbitrary but stable order to `Range` objects, thanks to
Chris Withers (:ticket:`#193`).
- Fixed debug build on Windows, thanks to James Emerton. - Fixed debug build on Windows, thanks to James Emerton.

View File

@ -437,8 +437,16 @@ user-defined |range| types can be adapted using `register_range()`.
`!Range` objects are immutable, hashable, and support the ``in`` operator `!Range` objects are immutable, hashable, and support the ``in`` operator
(checking if an element is within the range). They can be tested for (checking if an element is within the range). They can be tested for
equivalence but not for ordering. Empty ranges evaluate to `!False` in equivalence. Empty ranges evaluate to `!False` in boolean context,
boolean context, nonempty evaluate to `!True`. nonempty evaluate to `!True`.
.. versionchanged:: 2.5.3
`!Range` objects can be sorted although, as on the server-side, this
ordering is not particularly meangingful. It is only meant to be used
by programs assuming objects using `!Range` as primary key can be
sorted on them. In previous versions comparing `!Range`\s raises
`!TypeError`.
Although it is possible to instantiate `!Range` objects, the class doesn't Although it is possible to instantiate `!Range` objects, the class doesn't
have an adapter registered, so you cannot normally pass these instances as have an adapter registered, so you cannot normally pass these instances as

View File

@ -133,12 +133,43 @@ class Range(object):
def __hash__(self): def __hash__(self):
return hash((self._lower, self._upper, self._bounds)) return hash((self._lower, self._upper, self._bounds))
def __lt__(self, other): # as the postgres docs describe for the server-side stuff,
raise TypeError( # ordering is rather arbitrary, but will remain stable
'Range objects cannot be ordered; please refer to the PostgreSQL' # and consistent.
' documentation to perform this operation in the database')
__le__ = __gt__ = __ge__ = __lt__ def __lt__(self, other):
if not isinstance(other, Range):
return NotImplemented
for attr in ('_lower', '_upper', '_bounds'):
self_value = getattr(self, attr)
other_value = getattr(other, attr)
if self_value == other_value:
pass
elif self_value is None:
return True
elif other_value is None:
return False
else:
return self_value < other_value
return False
def __le__(self, other):
if self == other:
return True
else:
return self.__lt__(other)
def __gt__(self, other):
if isinstance(other, Range):
return other.__lt__(self)
else:
return NotImplemented
def __ge__(self, other):
if self == other:
return True
else:
return self.__gt__(other)
def register_range(pgrange, pyrange, conn_or_curs, globally=False): def register_range(pgrange, pyrange, conn_or_curs, globally=False):

View File

@ -13,6 +13,7 @@
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details. # License for more details.
from __future__ import with_statement
import re import re
import sys import sys
@ -22,6 +23,7 @@ from functools import wraps
from testutils import unittest, skip_if_no_uuid, skip_before_postgres from testutils import unittest, skip_if_no_uuid, skip_before_postgres
from testutils import ConnectingTestCase, decorate_all_tests from testutils import ConnectingTestCase, decorate_all_tests
from testutils import py3_raises_typeerror
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
@ -1236,12 +1238,73 @@ class RangeTestCase(unittest.TestCase):
self.assertEqual(Range(10, 20), IntRange(10, 20)) self.assertEqual(Range(10, 20), IntRange(10, 20))
self.assertEqual(PositiveIntRange(10, 20), IntRange(10, 20)) self.assertEqual(PositiveIntRange(10, 20), IntRange(10, 20))
def test_not_ordered(self): # as the postgres docs describe for the server-side stuff,
# ordering is rather arbitrary, but will remain stable
# and consistent.
def test_lt_ordering(self):
from psycopg2.extras import Range from psycopg2.extras import Range
self.assertRaises(TypeError, lambda: Range(empty=True) < Range(0,4)) self.assert_(Range(empty=True) < Range(0, 4))
self.assertRaises(TypeError, lambda: Range(1,2) > Range(0,4)) self.assert_(not Range(1, 2) < Range(0, 4))
self.assertRaises(TypeError, lambda: Range(1,2) <= Range()) self.assert_(Range(0, 4) < Range(1, 2))
self.assertRaises(TypeError, lambda: Range(1,2) >= Range()) self.assert_(not Range(1, 2) < Range())
self.assert_(Range() < Range(1, 2))
self.assert_(not Range(1) < Range(upper=1))
self.assert_(not Range() < Range())
self.assert_(not Range(empty=True) < Range(empty=True))
self.assert_(not Range(1, 2) < Range(1, 2))
with py3_raises_typeerror():
self.assert_(1 < Range(1, 2))
with py3_raises_typeerror():
self.assert_(not Range(1, 2) < 1)
def test_gt_ordering(self):
from psycopg2.extras import Range
self.assert_(not Range(empty=True) > Range(0, 4))
self.assert_(Range(1, 2) > Range(0, 4))
self.assert_(not Range(0, 4) > Range(1, 2))
self.assert_(Range(1, 2) > Range())
self.assert_(not Range() > Range(1, 2))
self.assert_(Range(1) > Range(upper=1))
self.assert_(not Range() > Range())
self.assert_(not Range(empty=True) > Range(empty=True))
self.assert_(not Range(1, 2) > Range(1, 2))
with py3_raises_typeerror():
self.assert_(not 1 > Range(1, 2))
with py3_raises_typeerror():
self.assert_(Range(1, 2) > 1)
def test_le_ordering(self):
from psycopg2.extras import Range
self.assert_(Range(empty=True) <= Range(0, 4))
self.assert_(not Range(1, 2) <= Range(0, 4))
self.assert_(Range(0, 4) <= Range(1, 2))
self.assert_(not Range(1, 2) <= Range())
self.assert_(Range() <= Range(1, 2))
self.assert_(not Range(1) <= Range(upper=1))
self.assert_(Range() <= Range())
self.assert_(Range(empty=True) <= Range(empty=True))
self.assert_(Range(1, 2) <= Range(1, 2))
with py3_raises_typeerror():
self.assert_(1 <= Range(1, 2))
with py3_raises_typeerror():
self.assert_(not Range(1, 2) <= 1)
def test_ge_ordering(self):
from psycopg2.extras import Range
self.assert_(not Range(empty=True) >= Range(0, 4))
self.assert_(Range(1, 2) >= Range(0, 4))
self.assert_(not Range(0, 4) >= Range(1, 2))
self.assert_(Range(1, 2) >= Range())
self.assert_(not Range() >= Range(1, 2))
self.assert_(Range(1) >= Range(upper=1))
self.assert_(Range() >= Range())
self.assert_(Range(empty=True) >= Range(empty=True))
self.assert_(Range(1, 2) >= Range(1, 2))
with py3_raises_typeerror():
self.assert_(not 1 >= Range(1, 2))
with py3_raises_typeerror():
self.assert_(Range(1, 2) >= 1)
def skip_if_no_range(f): def skip_if_no_range(f):

View File

@ -329,3 +329,13 @@ def script_to_py3(script):
f2.close() f2.close()
os.remove(filename) os.remove(filename)
class py3_raises_typeerror(object):
def __enter__(self):
pass
def __exit__(self, type, exc, tb):
if sys.version_info[0] >= 3:
assert type is TypeError
return True