Skip to content

Commit

Permalink
Don't allow RedisDeques to equal Python lists... (#574)
Browse files Browse the repository at this point in the history
* Don't allow RedisDeques to equal Python lists...

...even if they contain the same elements.

```python
>>> import collections
>>> collections.deque([1, 2, 3]) == [1, 2, 3]
False
```

* Bump version number
  • Loading branch information
brainix authored Dec 31, 2021
1 parent ccfe435 commit bd11210
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 25 deletions.
2 changes: 1 addition & 1 deletion pottery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@


__title__: Final[str] = 'pottery'
__version__: Final[str] = '2.3.3'
__version__: Final[str] = '2.3.4'
__description__: Final[str] = __doc__.split(sep='\n\n', maxsplit=1)[0]
__url__: Final[str] = 'https://github.com/brainix/pottery'
__author__: Final[str] = 'Rajiv Bakulesh Shah'
Expand Down
4 changes: 3 additions & 1 deletion pottery/deque.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
class RedisDeque(RedisList, collections.deque): # type: ignore
'Redis-backed container compatible with collections.deque.'

# Method overrides:
# Overrides:

_ALLOWED_TO_EQUAL = collections.deque # type: ignore

def __init__(self,
iterable: Iterable[JSONTypes] = tuple(),
Expand Down
6 changes: 4 additions & 2 deletions pottery/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
class RedisList(Base, collections.abc.MutableSequence):
'Redis-backed container compatible with Python lists.'

_ALLOWED_TO_EQUAL = list

def __slice_to_indices(self, slice_or_index: Union[slice, int]) -> range:
if isinstance(slice_or_index, slice):
start, stop, step = cast(slice, slice_or_index).indices(len(self))
Expand Down Expand Up @@ -261,13 +263,13 @@ def __eq__(self, other: Any) -> bool:
if self._same_redis(other) and self.key == other.key:
return True

if type(other) not in {list, self.__class__}:
if type(other) not in {self.__class__, self._ALLOWED_TO_EQUAL}:
return False
with self._watch(other):
if len(self) != len(other):
return False

# other is a mutable sequence too, and self and other are the same
# other is a list or RedisList too, and self and other are the same
# length. Make Python lists out of self and other, and compare
# those lists.
warnings.warn(
Expand Down
43 changes: 22 additions & 21 deletions tests/test_deque.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# --------------------------------------------------------------------------- #


import collections
import itertools
import unittest.mock

Expand All @@ -31,34 +32,34 @@ class DequeTests(TestCase):

def test_basic_usage(self):
d = RedisDeque('ghi', redis=self.redis)
assert d == ['g', 'h', 'i']
assert d == collections.deque(['g', 'h', 'i'])

d.append('j')
d.appendleft('f')
assert d == ['f', 'g', 'h', 'i', 'j']
assert d == collections.deque(['f', 'g', 'h', 'i', 'j'])

assert d.pop() == 'j'
assert d.popleft() == 'f'
assert d == ['g', 'h', 'i']
assert d == collections.deque(['g', 'h', 'i'])
assert d[0] == 'g'
assert d[-1] == 'i'

assert list(reversed(d)) == ['i', 'h', 'g']
assert 'h' in d
d.extend('jkl')
assert d == ['g', 'h', 'i', 'j', 'k', 'l']
assert d == collections.deque(['g', 'h', 'i', 'j', 'k', 'l'])
d.rotate(1)
assert d == ['l', 'g', 'h', 'i', 'j', 'k']
assert d == collections.deque(['l', 'g', 'h', 'i', 'j', 'k'])
d.rotate(-1)
assert d == ['g', 'h', 'i', 'j', 'k', 'l']
assert d == collections.deque(['g', 'h', 'i', 'j', 'k', 'l'])

assert RedisDeque(reversed(d), redis=self.redis) == ['l', 'k', 'j', 'i', 'h', 'g']
assert RedisDeque(reversed(d), redis=self.redis) == collections.deque(['l', 'k', 'j', 'i', 'h', 'g'])
d.clear()
with self.assertRaises(IndexError):
d.pop()

d.extendleft('abc')
assert d == ['c', 'b', 'a']
assert d == collections.deque(['c', 'b', 'a'])

def test_init_with_wrong_type_maxlen(self):
with unittest.mock.patch.object(Base, '__del__') as delete, \
Expand All @@ -68,10 +69,10 @@ def test_init_with_wrong_type_maxlen(self):

def test_init_with_maxlen(self):
d = RedisDeque([1, 2, 3, 4, 5, 6], redis=self.redis, maxlen=3)
assert d == [4, 5, 6]
assert d == collections.deque([4, 5, 6])

d = RedisDeque([1, 2, 3, 4, 5, 6], redis=self.redis, maxlen=0)
assert d == []
assert d == collections.deque()

def test_persistent_deque_bigger_than_maxlen(self):
d1 = RedisDeque('ghi', redis=self.redis)
Expand All @@ -87,26 +88,26 @@ def test_maxlen_not_writable(self):
def test_insert_into_full(self):
d = RedisDeque('gh', redis=self.redis, maxlen=3)
d.insert(len(d), 'i')
assert d == ['g', 'h', 'i']
assert d == collections.deque(['g', 'h', 'i'])

with self.assertRaises(IndexError):
d.insert(len(d), 'j')

def test_append_trims_when_full(self):
d = RedisDeque('gh', redis=self.redis, maxlen=3)
d.append('i')
assert d == ['g', 'h', 'i']
assert d == collections.deque(['g', 'h', 'i'])
d.append('j')
assert d == ['h', 'i', 'j']
assert d == collections.deque(['h', 'i', 'j'])
d.appendleft('g')
assert d == ['g', 'h', 'i']
assert d == collections.deque(['g', 'h', 'i'])

def test_extend(self):
d = RedisDeque('ghi', redis=self.redis, maxlen=4)
d.extend('jkl')
assert d == ['i', 'j', 'k', 'l']
assert d == collections.deque(['i', 'j', 'k', 'l'])
d.extendleft('hg')
assert d == ['g', 'h', 'i', 'j']
assert d == collections.deque(['g', 'h', 'i', 'j'])

def test_popleft_from_empty(self):
d = RedisDeque(redis=self.redis)
Expand All @@ -124,29 +125,29 @@ def test_rotate_zero_steps(self):
'Rotating 0 steps is a no-op'
d = RedisDeque(('g', 'h', 'i', 'j', 'k', 'l'), redis=self.redis)
d.rotate(0)
assert d == ['g', 'h', 'i', 'j', 'k', 'l']
assert d == collections.deque(['g', 'h', 'i', 'j', 'k', 'l'])

def test_rotate_empty_deque(self):
'Rotating an empty RedisDeque is a no-op'
d = RedisDeque(redis=self.redis)
d.rotate(2)
assert d == []
assert d == collections.deque()

def test_rotate_right(self):
'A positive number rotates a RedisDeque right'
# I got this example from here:
# https://pymotw.com/2/collections/deque.html#rotating
d = RedisDeque(range(10), redis=self.redis)
d.rotate(2)
assert d == [8, 9, 0, 1, 2, 3, 4, 5, 6, 7]
assert d == collections.deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])

def test_rotate_left(self):
'A negative number rotates a RedisDeque left'
# I got this example from here:
# https://pymotw.com/2/collections/deque.html#rotating
d = RedisDeque(range(10), redis=self.redis)
d.rotate(-2)
assert d == [2, 3, 4, 5, 6, 7, 8, 9, 0, 1]
assert d == collections.deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])

def test_moving_average(self):
'Test RedisDeque-based moving average'
Expand Down Expand Up @@ -177,7 +178,7 @@ def test_delete_nth(self):
e = d.popleft()
d.rotate(3)
assert e == 'j'
assert d == ['g', 'h', 'i', 'k', 'l']
assert d == collections.deque(['g', 'h', 'i', 'k', 'l'])

def test_truthiness(self):
d = RedisDeque('ghi', redis=self.redis)
Expand Down

0 comments on commit bd11210

Please sign in to comment.