diff --git a/pottery/__init__.py b/pottery/__init__.py index ac0a1e05..f80e595c 100644 --- a/pottery/__init__.py +++ b/pottery/__init__.py @@ -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' @@ -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 @@ -76,6 +78,8 @@ 'TooManyExtensions', 'ExtendUnlockedLock', 'ReleaseUnlockedLock', + 'PotteryWarning', + 'InefficientAccessWarning', 'BloomFilter', 'CachedOrderedDict', diff --git a/pottery/exceptions.py b/pottery/exceptions.py index 95a5806c..5f6b2ecd 100644 --- a/pottery/exceptions.py +++ b/pottery/exceptions.py @@ -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.' diff --git a/pottery/list.py b/pottery/list.py index 8741db04..80130178 100644 --- a/pottery/list.py +++ b/pottery/list.py @@ -20,6 +20,7 @@ import functools import itertools import uuid +import warnings from typing import Any from typing import Callable from typing import Iterable @@ -35,6 +36,7 @@ from .annotations import F from .base import Base from .base import JSONTypes +from .exceptions import InefficientAccessWarning from .exceptions import KeyExistsError @@ -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() @@ -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 @@ -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() @@ -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() diff --git a/tests/base.py b/tests/base.py index f432b15c..fdb2c0f2 100644 --- a/tests/base.py +++ b/tests/base.py @@ -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()