Skip to content

Commit

Permalink
Warn when doing O(n) operations on RedisLists (#511)
Browse files Browse the repository at this point in the history
* Warn when doing O(n) operations on RedisLists

* Silence all Pottery warnings during unit test runs

* Bump version number
  • Loading branch information
brainix authored Dec 9, 2021
1 parent 7c39157 commit 4de91c8
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 6 deletions.
6 changes: 5 additions & 1 deletion pottery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


__title__ = 'pottery'
__version__ = '2.0.0'
__version__ = '2.0.1'
__description__ = __doc__.split(sep='\n\n', maxsplit=1)[0]
__url__ = 'https://github.com/brainix/pottery'
__author__ = 'Rajiv Bakulesh Shah'
Expand All @@ -49,6 +49,8 @@
from .exceptions import TooManyExtensions # isort:skip
from .exceptions import ExtendUnlockedLock # isort:skip
from .exceptions import ReleaseUnlockedLock # isort:skip
from .exceptions import PotteryWarning # isort:skip
from .exceptions import InefficientAccessWarning # isort:skip

from .bloom import BloomFilter # isort:skip
from .cache import CachedOrderedDict # isort:skip
Expand Down Expand Up @@ -76,6 +78,8 @@
'TooManyExtensions',
'ExtendUnlockedLock',
'ReleaseUnlockedLock',
'PotteryWarning',
'InefficientAccessWarning',

'BloomFilter',
'CachedOrderedDict',
Expand Down
7 changes: 7 additions & 0 deletions pottery/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,10 @@ class ReleaseUnlockedLock(PrimitiveError, RuntimeError):

class QuorumIsImpossible(PrimitiveError, RuntimeError):
'Too many Redis masters threw RedisErrors; quorum can not be achieved.'


class PotteryWarning(Warning):
'Base warning class for Pottery containers.'

class InefficientAccessWarning(PotteryWarning):
'Doing an O(n) Redis operation.'
31 changes: 26 additions & 5 deletions pottery/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import functools
import itertools
import uuid
import warnings
from typing import Any
from typing import Callable
from typing import Iterable
Expand All @@ -35,6 +36,7 @@
from .annotations import F
from .base import Base
from .base import JSONTypes
from .exceptions import InefficientAccessWarning
from .exceptions import KeyExistsError


Expand Down Expand Up @@ -124,8 +126,12 @@ def __getitem__(self, index: Union[slice, int]) -> Any:
@_raise_on_error
def __setitem__(self, index: int, value: JSONTypes) -> None: # type: ignore
'l.__setitem__(index, value) <==> l[index] = value. O(n)'
if isinstance(index, slice):
with self._watch() as pipeline:
with self._watch() as pipeline:
if isinstance(index, slice):
warnings.warn(
cast(str, InefficientAccessWarning.__doc__),
InefficientAccessWarning,
)
encoded_values = [self._encode(value) for value in value]
indices = self.__slice_to_indices(index)
pipeline.multi()
Expand All @@ -137,9 +143,16 @@ def __setitem__(self, index: int, value: JSONTypes) -> None: # type: ignore
num += 1
if num:
pipeline.lrem(self.key, num, 0)
else:
index = self.__slice_to_indices(index).start
self.redis.lset(self.key, index, self._encode(value))
else:
index = self.__slice_to_indices(index).start
len_ = cast(int, pipeline.llen(self.key))
if index not in {-1, 0, len_-1}:
warnings.warn(
cast(str, InefficientAccessWarning.__doc__),
InefficientAccessWarning,
)
pipeline.multi()
pipeline.lset(self.key, index, self._encode(value))

@_raise_on_error
def __delitem__(self, index: Union[slice, int]) -> None: # type: ignore
Expand All @@ -156,6 +169,10 @@ def __delete(self, pipeline: Pipeline, index: Union[slice, int]) -> None:
# to set l[index] to a UUID4, then to delete the value UUID4. More
# info:
# http://redis.io/commands/lrem
warnings.warn(
cast(str, InefficientAccessWarning.__doc__),
InefficientAccessWarning,
)
indices, num = self.__slice_to_indices(index), 0
uuid4 = str(uuid.uuid4())
pipeline.multi()
Expand Down Expand Up @@ -190,6 +207,10 @@ def _insert(self,
# UUID4, then to set the value UUID4 back to the original pivot
# pivot value. More info:
# http://redis.io/commands/linsert
warnings.warn(
cast(str, InefficientAccessWarning.__doc__),
InefficientAccessWarning,
)
uuid4 = str(uuid.uuid4())
pivot = cast(bytes, pipeline.lindex(self.key, index))
pipeline.multi()
Expand Down
4 changes: 4 additions & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@
import random
import sys
import unittest
import warnings
from typing import NoReturn

from redis import Redis

from pottery import PotteryWarning


class TestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
logger = logging.getLogger('pottery')
logger.setLevel(logging.CRITICAL)
warnings.filterwarnings('ignore', category=PotteryWarning)

def setUp(self) -> None:
super().setUp()
Expand Down

0 comments on commit 4de91c8

Please sign in to comment.