Skip to content

Commit

Permalink
add LSET and LINDEX
Browse files Browse the repository at this point in the history
  • Loading branch information
JosuaKrause committed May 7, 2024
1 parent 70fd06c commit a4f1a80
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 23 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [release candidate]

### Added

- `LSET` and `LINDEX` ([#19])

## [0.7.0] - 2024-04-21

### Added
Expand Down Expand Up @@ -79,3 +85,4 @@
[#15]: https://github.com/JosuaKrause/redipy/pull/15
[#17]: https://github.com/JosuaKrause/redipy/pull/17
[#18]: https://github.com/JosuaKrause/redipy/pull/18
[#19]: https://github.com/JosuaKrause/redipy/pull/19
73 changes: 73 additions & 0 deletions src/redipy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,43 @@ def lrange(self, key: str, start: int, stop: int) -> None:
"""
raise NotImplementedError()

def lset(self, key: str, index: int, value: str) -> None:
"""
Sets the value of a list element at the given index. The index can be
negative to index from the back. The function will raise if the index
is out of bounds.
See also the redis documentation: https://redis.io/commands/lset/
The pipeline value is None.
Args:
key (str): The key.
index (int): The index. Can be negative to index from the back.
value (str): The value.
"""
raise NotImplementedError()

def lindex(self, key: str, index: int) -> None:
"""
Retrieves the value of a list element at the given index. The index can
be negative to index from the back. None is returned for an out of
bounds index.
See also the redis documentation: https://redis.io/commands/lindex/
The pipeline value is the value at the index or None if the index was
out of bounds.
Args:
key (str): The key.
index (int): The index. Can be negative to index from the back.
"""
raise NotImplementedError()

def llen(self, key: str) -> None:
"""
Computes the length of the list associated with the key.
Expand Down Expand Up @@ -1212,6 +1249,42 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
"""
raise NotImplementedError()

def lset(self, key: str, index: int, value: str) -> None:
"""
Sets the value of a list element at the given index. The index can be
negative to index from the back. The function will raise if the index
is out of bounds.
See also the redis documentation: https://redis.io/commands/lset/
Args:
key (str): The key.
index (int): The index. Can be negative to index from the back.
value (str): The value.
"""
raise NotImplementedError()

def lindex(self, key: str, index: int) -> str | None:
"""
Retrieves the value of a list element at the given index. The index can
be negative to index from the back. None is returned for an out of
bounds index.
See also the redis documentation: https://redis.io/commands/lindex/
Args:
key (str): The key.
index (int): The index. Can be negative to index from the back.
Returns:
str | None: The value at the index or None if the index was out of
bounds.
"""
raise NotImplementedError()

def llen(self, key: str) -> int:
"""
Computes the length of the list associated with the key.
Expand Down
43 changes: 42 additions & 1 deletion src/redipy/helpers/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
V = TypeVar('V')


class RCache(Generic[K, V]):
class RCache(Generic[K, V]): # pylint: disable=too-few-public-methods
"""A redis based cache with in-flight computation detection."""
def __init__(
self,
rt: RedisClientAPI,
Expand All @@ -34,6 +35,26 @@ def __init__(
compute: Callable[[K], V],
value_store: Callable[[V], str],
value_read: Callable[[str], V]) -> None:
"""
Creates a redis based cache.
Args:
rt (RedisClientAPI): The redis client.
prefix (str): The key prefix.
hasher (Callable[[K], str]): Function to create a hash for a given
key.
compute (Callable[[K], V]): Compute the actual value for a given
key.
value_store (Callable[[V], str]): Convert the value into a string
for storing. The string must never be empty.
value_read (Callable[[str], V]): Convert a string into a value for
retrieving.
"""
self._rt = rt
self._prefix = prefix
self._hasher = hasher
Expand All @@ -48,6 +69,26 @@ def _redis_key(self, hash_str: str) -> str:
return f"{prefix}{hash_str}"

def get_value(self, key: K, *, timeout: float = 300.0) -> V:
"""
Retrieves the value of the given key. If the value is not already
cached it is computed. If a value is being computed elsewhere at the
moment the function call blocks until the computation is complete.
During this, if timeout is reached, the computation will start again.
Args:
key (K): The key.
timeout (float, optional): Timeout before starting to compute the
value ourself if another computation was already in-flight
in seconds. Defaults to 300.0.
Raises:
ValueError: If the value string is the empty string after
converting the result.
Returns:
V: The result value.
"""
rt = self._rt
hash_str = self._hasher(key)
rkey = self._redis_key(hash_str)
Expand Down
44 changes: 22 additions & 22 deletions src/redipy/helpers/mavg.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""A moving average implementation."""
from redipy.api import RedisClientAPI
from redipy.backend.backend import ExecFunction
from redipy.symbolic.seq import FnContext
# from redipy.api import RedisClientAPI
# from redipy.backend.backend import ExecFunction
# from redipy.symbolic.seq import FnContext


class RMovingAverage:
def __init__(self, rt: RedisClientAPI) -> None:
self._rt = rt
self._update = self._update_script()
# class RMovingAverage:
# def __init__(self, rt: RedisClientAPI) -> None:
# self._rt = rt
# self._update = self._update_script()

def _update_script(self) -> ExecFunction:
ctx = FnContext()
# def _update_script(self) -> ExecFunction:
# ctx = FnContext()


rsize = RedisVar(ctx.add_key("size"))
base = ctx.add_local(ctx.add_key("frame"))
field = ctx.add_arg("field")
pos = ctx.add_local(ToNum(rsize.get_value(default=0)))
res = ctx.add_local(None)
cur = ctx.add_local(None)
rframe = RedisHash(cur)
# rsize = RedisVar(ctx.add_key("size"))
# base = ctx.add_local(ctx.add_key("frame"))
# field = ctx.add_arg("field")
# pos = ctx.add_local(ToNum(rsize.get_value(default=0)))
# res = ctx.add_local(None)
# cur = ctx.add_local(None)
# rframe = RedisHash(cur)

loop = ctx.while_(res.eq_(None).and_(pos.ge_(0)))
loop.add(cur.assign(Strs(base, ":", ToIntStr(pos))))
loop.add(res.assign(rframe.hget(field)))
loop.add(pos.assign(pos - 1))
# loop = ctx.while_(res.eq_(None).and_(pos.ge_(0)))
# loop.add(cur.assign(Strs(base, ":", ToIntStr(pos))))
# loop.add(res.assign(rframe.hget(field)))
# loop.add(pos.assign(pos - 1))

ctx.set_return_value(res)
return self._rt.register_script(ctx)
# ctx.set_return_value(res)
# return self._rt.register_script(ctx)
6 changes: 6 additions & 0 deletions src/redipy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ def rpop(
def lrange(self, key: str, start: int, stop: int) -> list[str]:
return self._rt.lrange(key, start, stop)

def lset(self, key: str, index: int, value: str) -> None:
self._rt.lset(key, index, value)

def lindex(self, key: str, index: int) -> str | None:
return self._rt.lindex(key, index)

def llen(self, key: str) -> int:
return self._rt.llen(key)

Expand Down
14 changes: 14 additions & 0 deletions src/redipy/memory/rt.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,14 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
with self.lock():
return self._sm.lrange(key, start, stop)

def lset(self, key: str, index: int, value: str) -> None:
with self.lock():
self._sm.lset(key, index, value)

def lindex(self, key: str, index: int) -> str | None:
with self.lock():
return self._sm.lindex(key, index)

def llen(self, key: str) -> int:
with self.lock():
return self._sm.llen(key)
Expand Down Expand Up @@ -675,6 +683,12 @@ def rpop(
def lrange(self, key: str, start: int, stop: int) -> None:
self.add_cmd(lambda: self._sm.lrange(key, start, stop))

def lset(self, key: str, index: int, value: str) -> None:
self.add_cmd(lambda: self._sm.lset(key, index, value))

def lindex(self, key: str, index: int) -> None:
self.add_cmd(lambda: self._sm.lindex(key, index))

def llen(self, key: str) -> None:
self.add_cmd(lambda: self._sm.llen(key))

Expand Down
15 changes: 15 additions & 0 deletions src/redipy/memory/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,21 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
queue.rotate(start)
return res

def lset(self, key: str, index: int, value: str) -> None:
now_mono = self.get_mono()
queue = self._state.get_queue(key, now_mono)
queue[index] = value

def lindex(self, key: str, index: int) -> str | None:
now_mono = self.get_mono()
queue = self._state.readonly_queue(key, now_mono)
if not queue:
return None
try:
return queue[index]
except IndexError:
return None

def llen(self, key: str) -> int:
now_mono = self.get_mono()
return self._state.queue_len(key, now_mono)
Expand Down
16 changes: 16 additions & 0 deletions src/redipy/redis/conn.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,14 @@ def lrange(self, key: str, start: int, stop: int) -> None:
self._pipe.lrange(self.with_prefix(key), start, stop)
self.add_fixup(to_list_str)

def lset(self, key: str, index: int, value: str) -> None:
self._pipe.lset(self.with_prefix(key), index, value)
self.add_fixup(lambda _: None)

def lindex(self, key: str, index: int) -> None:
self._pipe.lindex(self.with_prefix(key), index)
self.add_fixup(to_maybe_str)

def llen(self, key: str) -> None:
self._pipe.llen(self.with_prefix(key))
self.add_fixup(int)
Expand Down Expand Up @@ -1088,6 +1096,14 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
with self.get_connection() as conn:
return to_list_str(conn.lrange(self.with_prefix(key), start, stop))

def lset(self, key: str, index: int, value: str) -> None:
with self.get_connection() as conn:
conn.lset(self.with_prefix(key), index, value)

def lindex(self, key: str, index: int) -> str | None:
with self.get_connection() as conn:
return to_maybe_str(conn.lindex(self.with_prefix(key), index))

def llen(self, key: str) -> int:
with self.get_connection() as conn:
return conn.llen(self.with_prefix(key))
Expand Down

0 comments on commit a4f1a80

Please sign in to comment.