mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-26 02:43:43 +03:00
Merge branch 'range_sort'
This commit is contained in:
commit
283dbccf56
2
NEWS
2
NEWS
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user