diff --git a/cassandra/metadata.py b/cassandra/metadata.py index e410631ae7..96b70ae83b 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -15,6 +15,7 @@ from binascii import unhexlify from bisect import bisect_right from collections import defaultdict, Mapping +from functools import total_ordering from hashlib import md5 from itertools import islice, cycle import json @@ -1492,6 +1493,7 @@ def get_replicas(self, keyspace, token): return [] +@total_ordering class Token(object): """ Abstract class representing a token. @@ -1512,14 +1514,6 @@ def from_key(cls, key): def from_string(cls, token_string): raise NotImplementedError() - def __cmp__(self, other): - if self.value < other.value: - return -1 - elif self.value == other.value: - return 0 - else: - return 1 - def __eq__(self, other): return self.value == other.value diff --git a/cassandra/pool.py b/cassandra/pool.py index c6801de345..e570b0154b 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -16,6 +16,7 @@ Connection pooling and host management. """ +from functools import total_ordering import logging import socket import time @@ -41,6 +42,7 @@ class NoConnectionsAvailable(Exception): pass +@total_ordering class Host(object): """ Represents a single Cassandra node. diff --git a/cassandra/util.py b/cassandra/util.py index 924b5c7905..b73baab7f3 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -15,6 +15,7 @@ from __future__ import with_statement import calendar import datetime +from functools import total_ordering import random import six import uuid @@ -861,6 +862,7 @@ def _serialize_key(self, key): long = int +@total_ordering class Time(object): ''' Idealized time, independent of day. @@ -985,6 +987,7 @@ def __str__(self): self.second, self.nanosecond) +@total_ordering class Date(object): ''' Idealized date: year, month, day diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 0b0f5efda5..9533dc3259 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -33,6 +33,10 @@ read_stringmap, read_inet, write_inet, read_string, write_longstring) from cassandra.query import named_tuple_factory +from cassandra.pool import Host +from cassandra.policies import SimpleConvictionPolicy, ConvictionPolicy +from cassandra.util import Date, Time +from cassandra.metadata import Token class TypeTests(unittest.TestCase): @@ -248,3 +252,135 @@ def test_cql_quote(self): self.assertEqual(cql_quote(u'test'), "'test'") self.assertEqual(cql_quote('test'), "'test'") self.assertEqual(cql_quote(0), '0') + + +class TestOrdering(unittest.TestCase): + def _check_order_consistency(self, smaller, bigger, equal=False): + self.assertLessEqual(smaller, bigger) + self.assertGreaterEqual(bigger, smaller) + if equal: + self.assertEqual(smaller, bigger) + else: + self.assertNotEqual(smaller, bigger) + self.assertLess(smaller, bigger) + self.assertGreater(bigger, smaller) + + def _shuffle_lists(self, *args): + return [item for sublist in zip(*args) for item in sublist] + + def _check_sequence_consistency(self, ordered_sequence, equal=False): + for i, el in enumerate(ordered_sequence): + for previous in ordered_sequence[:i]: + self._check_order_consistency(previous, el, equal) + for posterior in ordered_sequence[i + 1:]: + self._check_order_consistency(el, posterior, equal) + + def test_host_order(self): + """ + Test Host class is ordered consistently + + @since 3.9 + @jira_ticket PYTHON-714 + @expected_result the hosts are ordered correctly + + @test_category data_types + """ + hosts = [Host(addr, SimpleConvictionPolicy) for addr in + ("127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4")] + hosts_equal = [Host(addr, SimpleConvictionPolicy) for addr in + ("127.0.0.1", "127.0.0.1")] + hosts_equal_conviction = [Host("127.0.0.1", SimpleConvictionPolicy), Host("127.0.0.1", ConvictionPolicy)] + self._check_sequence_consistency(hosts) + self._check_sequence_consistency(hosts_equal, equal=True) + self._check_sequence_consistency(hosts_equal_conviction, equal=True) + + def test_date_order(self): + """ + Test Date class is ordered consistently + + @since 3.9 + @jira_ticket PYTHON-714 + @expected_result the dates are ordered correctly + + @test_category data_types + """ + dates_from_string = [Date("2017-01-01"), Date("2017-01-05"), Date("2017-01-09"), Date("2017-01-13")] + dates_from_string_equal = [Date("2017-01-01"), Date("2017-01-01")] + self._check_sequence_consistency(dates_from_string) + self._check_sequence_consistency(dates_from_string_equal, equal=True) + + date_format = "%Y-%m-%d" + + dates_from_value = [ + Date((datetime.datetime.strptime(dtstr, date_format) - + datetime.datetime(1970, 1, 1)).days) + for dtstr in ("2017-01-02", "2017-01-06", "2017-01-10", "2017-01-14") + ] + dates_from_value_equal = [Date(1), Date(1)] + self._check_sequence_consistency(dates_from_value) + self._check_sequence_consistency(dates_from_value_equal, equal=True) + + dates_from_datetime = [Date(datetime.datetime.strptime(dtstr, date_format)) + for dtstr in ("2017-01-03", "2017-01-07", "2017-01-11", "2017-01-15")] + dates_from_datetime_equal = [Date(datetime.datetime.strptime("2017-01-01", date_format)), + Date(datetime.datetime.strptime("2017-01-01", date_format))] + self._check_sequence_consistency(dates_from_datetime) + self._check_sequence_consistency(dates_from_datetime_equal, equal=True) + + dates_from_date = [ + Date(datetime.datetime.strptime(dtstr, date_format).date()) for dtstr in + ("2017-01-04", "2017-01-08", "2017-01-12", "2017-01-16") + ] + dates_from_date_equal = [datetime.datetime.strptime(dtstr, date_format) for dtstr in + ("2017-01-09", "2017-01-9")] + + self._check_sequence_consistency(dates_from_date) + self._check_sequence_consistency(dates_from_date_equal, equal=True) + + self._check_sequence_consistency(self._shuffle_lists(dates_from_string, dates_from_value, + dates_from_datetime, dates_from_date)) + + def test_timer_order(self): + """ + Test Time class is ordered consistently + + @since 3.9 + @jira_ticket PYTHON-714 + @expected_result the times are ordered correctly + + @test_category data_types + """ + time_from_int = [Time(1000), Time(4000), Time(7000), Time(10000)] + time_from_int_equal = [Time(1), Time(1)] + self._check_sequence_consistency(time_from_int) + self._check_sequence_consistency(time_from_int_equal, equal=True) + + time_from_datetime = [Time(datetime.time(hour=0, minute=0, second=0, microsecond=us)) + for us in (2, 5, 8, 11)] + time_from_datetime_equal = [Time(datetime.time(hour=0, minute=0, second=0, microsecond=us)) + for us in (1, 1)] + self._check_sequence_consistency(time_from_datetime) + self._check_sequence_consistency(time_from_datetime_equal, equal=True) + + time_from_string = [Time("00:00:00.000003000"), Time("00:00:00.000006000"), + Time("00:00:00.000009000"), Time("00:00:00.000012000")] + time_from_string_equal = [Time("00:00:00.000004000"), Time("00:00:00.000004000")] + self._check_sequence_consistency(time_from_string) + self._check_sequence_consistency(time_from_string_equal, equal=True) + + self._check_sequence_consistency(self._shuffle_lists(time_from_int, time_from_datetime, time_from_string)) + + def test_token_order(self): + """ + Test Token class is ordered consistently + + @since 3.9 + @jira_ticket PYTHON-714 + @expected_result the tokens are ordered correctly + + @test_category data_types + """ + tokens = [Token(1), Token(2), Token(3), Token(4)] + tokens_equal = [Token(1), Token(1)] + self._check_sequence_consistency(tokens) + self._check_sequence_consistency(tokens_equal, equal=True)