Skip to content

Commit

Permalink
Properly compare RedisLists on different Redis dbs (#572)
Browse files Browse the repository at this point in the history
* Properly compare RedisLists on different Redis dbs

Before this PR, `list1 == list2` would fail if both were `RedisList`s
but on different Redis databases. This is because we'd try to reuse the
same Redis pipeline for both lists.

* Don't coverage test unusual code branches

* Bump version number
  • Loading branch information
brainix authored Dec 30, 2021
1 parent 6161d8e commit d045ca8
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 27 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Pottery: Redis for Humans 🌎🌍🌏

[Redis](http://redis.io/) is awesome, but [Redis
commands](http://redis.io/commands) are not always fun. Pottery is a Pythonic
way to access Redis. If you know how to use Python dicts, then you already
know how to use Pottery. Pottery is useful for accessing Redis more easily,
and also for implementing microservice resilience patterns; and it has been
battle tested in production at scale.
commands](http://redis.io/commands) are not always intuitive. Pottery is a
Pythonic way to access Redis. If you know how to use Python dicts, then you
already know how to use Pottery. Pottery is useful for accessing Redis more
easily, and also for implementing microservice resilience patterns; and it has
been battle tested in production at scale.

[![Build status](https://img.shields.io/github/workflow/status/brainix/pottery/Python%20package/master)](https://github.com/brainix/pottery/actions?query=branch%3Amaster)
[![Latest released version](https://badge.fury.io/py/pottery.svg)](https://badge.fury.io/py/pottery)
Expand Down
12 changes: 6 additions & 6 deletions pottery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
# --------------------------------------------------------------------------- #
'''Redis for Humans.
Redis is awesome, but Redis commands are not always fun. Pottery is a Pythonic
way to access Redis. If you know how to use Python dicts, then you already
know how to use Pottery. Pottery is useful for accessing Redis more easily,
and also for implementing microservice resilience patterns; and it has been
battle tested in production at scale.
Redis is awesome, but Redis commands are not always intuitive. Pottery is a
Pythonic way to access Redis. If you know how to use Python dicts, then you
already know how to use Pottery. Pottery is useful for accessing Redis more
easily, and also for implementing microservice resilience patterns; and it has
been battle tested in production at scale.
'''


Expand All @@ -32,7 +32,7 @@


__title__: Final[str] = 'pottery'
__version__: Final[str] = '2.3.1'
__version__: Final[str] = '2.3.2'
__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
8 changes: 5 additions & 3 deletions pottery/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ def redis_cache(*, # NoQA: C901
on each call.
'''

redis = _default_redis if redis is None else redis
if redis is None: # pragma: no cover
redis = _default_redis

def decorator(func: F) -> F:
nonlocal redis, key
Expand Down Expand Up @@ -308,7 +309,7 @@ def __retriable_setdefault(self,
self._cache._encode(default),
)

def __retry(self, callable: Callable[[], Any], try_num: int = 0) -> Any:
def __retry(self, callable: Callable[[], Any], *, try_num: int = 0) -> Any:
try:
return callable()
except WatchError: # pragma: no cover
Expand All @@ -331,7 +332,8 @@ def update(self, arg: InitArg = tuple(), **kwargs: JSONTypes) -> None: # type:
to_cache = {}
with contextlib.suppress(AttributeError):
arg = cast(InitMap, arg).items()
for dict_key, value in itertools.chain(cast(InitIter, arg), kwargs.items()):
items = itertools.chain(cast(InitIter, arg), kwargs.items())
for dict_key, value in items:
if value is not self._SENTINEL:
to_cache[dict_key] = value
self._misses.discard(dict_key)
Expand Down
2 changes: 1 addition & 1 deletion pottery/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _populate(self, # type: ignore
for key, value in kwargs.items():
if dict_.get(key, 0) == 0:
original = self[key]
else:
else: # pragma: no cover
original = dict_[key]
dict_[key] = original + sign * value

Expand Down
6 changes: 3 additions & 3 deletions pottery/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def _populate(self,
# https://stackoverflow.com/a/38534939
__populate = _populate

def _encode_dict(self, dict_):
def _encode_dict(self, dict_: Mapping[JSONTypes, JSONTypes]) -> Dict[str, str]:
encoded_dict = {
self._encode(key): self._encode(value)
for key, value in dict_.items()
Expand All @@ -100,8 +100,8 @@ def __getitem__(self, key: JSONTypes) -> JSONTypes:
encoded_value = self.redis.hget(self.key, encoded_key)
if encoded_value is None:
raise KeyError(key)
decoded_value = self._decode(encoded_value)
return decoded_value
value = self._decode(encoded_value)
return value

def __setitem__(self, key: JSONTypes, value: JSONTypes) -> None:
'd.__setitem__(key, value) <==> d[key] = value. O(1)'
Expand Down
19 changes: 10 additions & 9 deletions pottery/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ def __getitem__(self, index: Union[slice, int]) -> Any:
start, stop = indices.stop+1, indices.start
pipeline.multi()
pipeline.lrange(self.key, start, stop)
encoded = pipeline.execute()[0]
encoded = encoded[::index.step]
encoded_values = pipeline.execute()[0]
encoded_values = encoded_values[::index.step]
value: Union[List[JSONTypes], JSONTypes] = [
self._decode(value) for value in encoded
self._decode(value) for value in encoded_values
]
else:
index = self.__slice_to_indices(index).start
Expand All @@ -135,10 +135,10 @@ def __getitem__(self, index: Union[slice, int]) -> Any:
cast(str, InefficientAccessWarning.__doc__),
InefficientAccessWarning,
)
encoded = pipeline.lindex(self.key, index)
if encoded is None:
encoded_value = pipeline.lindex(self.key, index)
if encoded_value is None:
raise IndexError('list index out of range')
value = self._decode(encoded)
value = self._decode(cast(bytes, encoded_value))
return value

@_raise_on_error
Expand Down Expand Up @@ -170,7 +170,8 @@ def __setitem__(self, index: int, value: JSONTypes) -> None: # type: ignore
InefficientAccessWarning,
)
pipeline.multi()
pipeline.lset(self.key, index, self._encode(value))
encoded_value = self._encode(value)
pipeline.lset(self.key, index, encoded_value)

@_raise_on_error
def __delitem__(self, index: Union[slice, int]) -> None: # type: ignore
Expand Down Expand Up @@ -265,7 +266,7 @@ def __eq__(self, other: Any) -> bool:

with self._watch(other) as pipeline:
len_xs = cast(int, pipeline.llen(self.key))
if isinstance(other, RedisList):
if isinstance(other, RedisList) and self._same_redis(other):
len_ys = cast(int, pipeline.llen(other.key))
else:
try:
Expand All @@ -290,7 +291,7 @@ def __eq__(self, other: Any) -> bool:
pipeline.lrange(self.key, 0, -1),
)
decoded_xs = (self._decode(x) for x in encoded_xs)
if isinstance(other, RedisList):
if isinstance(other, RedisList) and self._same_redis(other):
encoded_ys = cast(
List[bytes],
pipeline.lrange(other.key, 0, -1),
Expand Down

0 comments on commit d045ca8

Please sign in to comment.