Skip to content

Commit a4f1a80

Browse files
committed
add LSET and LINDEX
1 parent 70fd06c commit a4f1a80

File tree

8 files changed

+195
-23
lines changed

8 files changed

+195
-23
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [release candidate]
4+
5+
### Added
6+
7+
- `LSET` and `LINDEX` ([#19])
8+
39
## [0.7.0] - 2024-04-21
410

511
### Added
@@ -79,3 +85,4 @@
7985
[#15]: https://github.com/JosuaKrause/redipy/pull/15
8086
[#17]: https://github.com/JosuaKrause/redipy/pull/17
8187
[#18]: https://github.com/JosuaKrause/redipy/pull/18
88+
[#19]: https://github.com/JosuaKrause/redipy/pull/19

src/redipy/api.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,43 @@ def lrange(self, key: str, start: int, stop: int) -> None:
441441
"""
442442
raise NotImplementedError()
443443

444+
def lset(self, key: str, index: int, value: str) -> None:
445+
"""
446+
Sets the value of a list element at the given index. The index can be
447+
negative to index from the back. The function will raise if the index
448+
is out of bounds.
449+
450+
See also the redis documentation: https://redis.io/commands/lset/
451+
452+
The pipeline value is None.
453+
454+
Args:
455+
key (str): The key.
456+
457+
index (int): The index. Can be negative to index from the back.
458+
459+
value (str): The value.
460+
"""
461+
raise NotImplementedError()
462+
463+
def lindex(self, key: str, index: int) -> None:
464+
"""
465+
Retrieves the value of a list element at the given index. The index can
466+
be negative to index from the back. None is returned for an out of
467+
bounds index.
468+
469+
See also the redis documentation: https://redis.io/commands/lindex/
470+
471+
The pipeline value is the value at the index or None if the index was
472+
out of bounds.
473+
474+
Args:
475+
key (str): The key.
476+
477+
index (int): The index. Can be negative to index from the back.
478+
"""
479+
raise NotImplementedError()
480+
444481
def llen(self, key: str) -> None:
445482
"""
446483
Computes the length of the list associated with the key.
@@ -1212,6 +1249,42 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
12121249
"""
12131250
raise NotImplementedError()
12141251

1252+
def lset(self, key: str, index: int, value: str) -> None:
1253+
"""
1254+
Sets the value of a list element at the given index. The index can be
1255+
negative to index from the back. The function will raise if the index
1256+
is out of bounds.
1257+
1258+
See also the redis documentation: https://redis.io/commands/lset/
1259+
1260+
Args:
1261+
key (str): The key.
1262+
1263+
index (int): The index. Can be negative to index from the back.
1264+
1265+
value (str): The value.
1266+
"""
1267+
raise NotImplementedError()
1268+
1269+
def lindex(self, key: str, index: int) -> str | None:
1270+
"""
1271+
Retrieves the value of a list element at the given index. The index can
1272+
be negative to index from the back. None is returned for an out of
1273+
bounds index.
1274+
1275+
See also the redis documentation: https://redis.io/commands/lindex/
1276+
1277+
Args:
1278+
key (str): The key.
1279+
1280+
index (int): The index. Can be negative to index from the back.
1281+
1282+
Returns:
1283+
str | None: The value at the index or None if the index was out of
1284+
bounds.
1285+
"""
1286+
raise NotImplementedError()
1287+
12151288
def llen(self, key: str) -> int:
12161289
"""
12171290
Computes the length of the list associated with the key.

src/redipy/helpers/cache.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
V = TypeVar('V')
2525

2626

27-
class RCache(Generic[K, V]):
27+
class RCache(Generic[K, V]): # pylint: disable=too-few-public-methods
28+
"""A redis based cache with in-flight computation detection."""
2829
def __init__(
2930
self,
3031
rt: RedisClientAPI,
@@ -34,6 +35,26 @@ def __init__(
3435
compute: Callable[[K], V],
3536
value_store: Callable[[V], str],
3637
value_read: Callable[[str], V]) -> None:
38+
"""
39+
Creates a redis based cache.
40+
41+
Args:
42+
rt (RedisClientAPI): The redis client.
43+
44+
prefix (str): The key prefix.
45+
46+
hasher (Callable[[K], str]): Function to create a hash for a given
47+
key.
48+
49+
compute (Callable[[K], V]): Compute the actual value for a given
50+
key.
51+
52+
value_store (Callable[[V], str]): Convert the value into a string
53+
for storing. The string must never be empty.
54+
55+
value_read (Callable[[str], V]): Convert a string into a value for
56+
retrieving.
57+
"""
3758
self._rt = rt
3859
self._prefix = prefix
3960
self._hasher = hasher
@@ -48,6 +69,26 @@ def _redis_key(self, hash_str: str) -> str:
4869
return f"{prefix}{hash_str}"
4970

5071
def get_value(self, key: K, *, timeout: float = 300.0) -> V:
72+
"""
73+
Retrieves the value of the given key. If the value is not already
74+
cached it is computed. If a value is being computed elsewhere at the
75+
moment the function call blocks until the computation is complete.
76+
During this, if timeout is reached, the computation will start again.
77+
78+
Args:
79+
key (K): The key.
80+
81+
timeout (float, optional): Timeout before starting to compute the
82+
value ourself if another computation was already in-flight
83+
in seconds. Defaults to 300.0.
84+
85+
Raises:
86+
ValueError: If the value string is the empty string after
87+
converting the result.
88+
89+
Returns:
90+
V: The result value.
91+
"""
5192
rt = self._rt
5293
hash_str = self._hasher(key)
5394
rkey = self._redis_key(hash_str)

src/redipy/helpers/mavg.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,32 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""A moving average implementation."""
15-
from redipy.api import RedisClientAPI
16-
from redipy.backend.backend import ExecFunction
17-
from redipy.symbolic.seq import FnContext
15+
# from redipy.api import RedisClientAPI
16+
# from redipy.backend.backend import ExecFunction
17+
# from redipy.symbolic.seq import FnContext
1818

1919

20-
class RMovingAverage:
21-
def __init__(self, rt: RedisClientAPI) -> None:
22-
self._rt = rt
23-
self._update = self._update_script()
20+
# class RMovingAverage:
21+
# def __init__(self, rt: RedisClientAPI) -> None:
22+
# self._rt = rt
23+
# self._update = self._update_script()
2424

25-
def _update_script(self) -> ExecFunction:
26-
ctx = FnContext()
25+
# def _update_script(self) -> ExecFunction:
26+
# ctx = FnContext()
2727

2828

29-
rsize = RedisVar(ctx.add_key("size"))
30-
base = ctx.add_local(ctx.add_key("frame"))
31-
field = ctx.add_arg("field")
32-
pos = ctx.add_local(ToNum(rsize.get_value(default=0)))
33-
res = ctx.add_local(None)
34-
cur = ctx.add_local(None)
35-
rframe = RedisHash(cur)
29+
# rsize = RedisVar(ctx.add_key("size"))
30+
# base = ctx.add_local(ctx.add_key("frame"))
31+
# field = ctx.add_arg("field")
32+
# pos = ctx.add_local(ToNum(rsize.get_value(default=0)))
33+
# res = ctx.add_local(None)
34+
# cur = ctx.add_local(None)
35+
# rframe = RedisHash(cur)
3636

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

42-
ctx.set_return_value(res)
43-
return self._rt.register_script(ctx)
42+
# ctx.set_return_value(res)
43+
# return self._rt.register_script(ctx)

src/redipy/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,12 @@ def rpop(
394394
def lrange(self, key: str, start: int, stop: int) -> list[str]:
395395
return self._rt.lrange(key, start, stop)
396396

397+
def lset(self, key: str, index: int, value: str) -> None:
398+
self._rt.lset(key, index, value)
399+
400+
def lindex(self, key: str, index: int) -> str | None:
401+
return self._rt.lindex(key, index)
402+
397403
def llen(self, key: str) -> int:
398404
return self._rt.llen(key)
399405

src/redipy/memory/rt.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,14 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
407407
with self.lock():
408408
return self._sm.lrange(key, start, stop)
409409

410+
def lset(self, key: str, index: int, value: str) -> None:
411+
with self.lock():
412+
self._sm.lset(key, index, value)
413+
414+
def lindex(self, key: str, index: int) -> str | None:
415+
with self.lock():
416+
return self._sm.lindex(key, index)
417+
410418
def llen(self, key: str) -> int:
411419
with self.lock():
412420
return self._sm.llen(key)
@@ -675,6 +683,12 @@ def rpop(
675683
def lrange(self, key: str, start: int, stop: int) -> None:
676684
self.add_cmd(lambda: self._sm.lrange(key, start, stop))
677685

686+
def lset(self, key: str, index: int, value: str) -> None:
687+
self.add_cmd(lambda: self._sm.lset(key, index, value))
688+
689+
def lindex(self, key: str, index: int) -> None:
690+
self.add_cmd(lambda: self._sm.lindex(key, index))
691+
678692
def llen(self, key: str) -> None:
679693
self.add_cmd(lambda: self._sm.llen(key))
680694

src/redipy/memory/state.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,21 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
14351435
queue.rotate(start)
14361436
return res
14371437

1438+
def lset(self, key: str, index: int, value: str) -> None:
1439+
now_mono = self.get_mono()
1440+
queue = self._state.get_queue(key, now_mono)
1441+
queue[index] = value
1442+
1443+
def lindex(self, key: str, index: int) -> str | None:
1444+
now_mono = self.get_mono()
1445+
queue = self._state.readonly_queue(key, now_mono)
1446+
if not queue:
1447+
return None
1448+
try:
1449+
return queue[index]
1450+
except IndexError:
1451+
return None
1452+
14381453
def llen(self, key: str) -> int:
14391454
now_mono = self.get_mono()
14401455
return self._state.queue_len(key, now_mono)

src/redipy/redis/conn.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,14 @@ def lrange(self, key: str, start: int, stop: int) -> None:
454454
self._pipe.lrange(self.with_prefix(key), start, stop)
455455
self.add_fixup(to_list_str)
456456

457+
def lset(self, key: str, index: int, value: str) -> None:
458+
self._pipe.lset(self.with_prefix(key), index, value)
459+
self.add_fixup(lambda _: None)
460+
461+
def lindex(self, key: str, index: int) -> None:
462+
self._pipe.lindex(self.with_prefix(key), index)
463+
self.add_fixup(to_maybe_str)
464+
457465
def llen(self, key: str) -> None:
458466
self._pipe.llen(self.with_prefix(key))
459467
self.add_fixup(int)
@@ -1088,6 +1096,14 @@ def lrange(self, key: str, start: int, stop: int) -> list[str]:
10881096
with self.get_connection() as conn:
10891097
return to_list_str(conn.lrange(self.with_prefix(key), start, stop))
10901098

1099+
def lset(self, key: str, index: int, value: str) -> None:
1100+
with self.get_connection() as conn:
1101+
conn.lset(self.with_prefix(key), index, value)
1102+
1103+
def lindex(self, key: str, index: int) -> str | None:
1104+
with self.get_connection() as conn:
1105+
return to_maybe_str(conn.lindex(self.with_prefix(key), index))
1106+
10911107
def llen(self, key: str) -> int:
10921108
with self.get_connection() as conn:
10931109
return conn.llen(self.with_prefix(key))

0 commit comments

Comments
 (0)