Skip to content

Commit 0ea7eec

Browse files
authored
Merge branch 'master' into ps_add_hybrid_search
2 parents 1af90b9 + bb7ae74 commit 0ea7eec

File tree

11 files changed

+1567
-21
lines changed

11 files changed

+1567
-21
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040

4141
# Initializes the CodeQL tools for scanning.
4242
- name: Initialize CodeQL
43-
uses: github/codeql-action/init@v3
43+
uses: github/codeql-action/init@v4
4444
with:
4545
languages: ${{ matrix.language }}
4646
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -51,7 +51,7 @@ jobs:
5151
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
5252
# If this step fails, then you should remove it and run the build manually (see below)
5353
- name: Autobuild
54-
uses: github/codeql-action/autobuild@v3
54+
uses: github/codeql-action/autobuild@v4
5555

5656
# ℹ️ Command-line programs to run using the OS shell.
5757
# 📚 https://git.io/JvXDl
@@ -65,4 +65,4 @@ jobs:
6565
# make release
6666

6767
- name: Perform CodeQL Analysis
68-
uses: github/codeql-action/analyze@v3
68+
uses: github/codeql-action/analyze@v4

redis/_parsers/helpers.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ def parse_sentinel_state(item):
137137
return result
138138

139139

140-
def parse_sentinel_master(response):
140+
def parse_sentinel_master(response, **options):
141141
return parse_sentinel_state(map(str_if_bytes, response))
142142

143143

144-
def parse_sentinel_state_resp3(response):
144+
def parse_sentinel_state_resp3(response, **options):
145145
result = {}
146146
for key in response:
147147
try:
@@ -154,27 +154,27 @@ def parse_sentinel_state_resp3(response):
154154
return result
155155

156156

157-
def parse_sentinel_masters(response):
157+
def parse_sentinel_masters(response, **options):
158158
result = {}
159159
for item in response:
160160
state = parse_sentinel_state(map(str_if_bytes, item))
161161
result[state["name"]] = state
162162
return result
163163

164164

165-
def parse_sentinel_masters_resp3(response):
166-
return [parse_sentinel_state(master) for master in response]
165+
def parse_sentinel_masters_resp3(response, **options):
166+
return [parse_sentinel_state_resp3(master) for master in response]
167167

168168

169-
def parse_sentinel_slaves_and_sentinels(response):
169+
def parse_sentinel_slaves_and_sentinels(response, **options):
170170
return [parse_sentinel_state(map(str_if_bytes, item)) for item in response]
171171

172172

173-
def parse_sentinel_slaves_and_sentinels_resp3(response):
174-
return [parse_sentinel_state_resp3(item) for item in response]
173+
def parse_sentinel_slaves_and_sentinels_resp3(response, **options):
174+
return [parse_sentinel_state_resp3(item, **options) for item in response]
175175

176176

177-
def parse_sentinel_get_master(response):
177+
def parse_sentinel_get_master(response, **options):
178178
return response and (response[0], int(response[1])) or None
179179

180180

@@ -268,13 +268,16 @@ def sort_return_tuples(response, **options):
268268
return list(zip(*[response[i::n] for i in range(n)]))
269269

270270

271-
def parse_stream_list(response):
271+
def parse_stream_list(response, **options):
272272
if response is None:
273273
return None
274274
data = []
275275
for r in response:
276276
if r is not None:
277-
data.append((r[0], pairs_to_dict(r[1])))
277+
if "claim_min_idle_time" in options:
278+
data.append((r[0], pairs_to_dict(r[1]), *r[2:]))
279+
else:
280+
data.append((r[0], pairs_to_dict(r[1])))
278281
else:
279282
data.append((None, None))
280283
return data
@@ -332,16 +335,18 @@ def parse_xinfo_stream(response, **options):
332335
return data
333336

334337

335-
def parse_xread(response):
338+
def parse_xread(response, **options):
336339
if response is None:
337340
return []
338-
return [[r[0], parse_stream_list(r[1])] for r in response]
341+
return [[r[0], parse_stream_list(r[1], **options)] for r in response]
339342

340343

341-
def parse_xread_resp3(response):
344+
def parse_xread_resp3(response, **options):
342345
if response is None:
343346
return {}
344-
return {key: [parse_stream_list(value)] for key, value in response.items()}
347+
return {
348+
key: [parse_stream_list(value, **options)] for key, value in response.items()
349+
}
345350

346351

347352
def parse_xpending(response, **options):

redis/cluster.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@ def inner(*args, **kwargs):
23582358
"MGET NONATOMIC",
23592359
"MOVE",
23602360
"MSET",
2361+
"MSETEX",
23612362
"MSET NONATOMIC",
23622363
"MSETNX",
23632364
"PFCOUNT",

redis/commands/core.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,16 @@ def execute(self) -> ResponseT:
15591559
return self.client.execute_command(*command)
15601560

15611561

1562+
class DataPersistOptions(Enum):
1563+
# set the value for each provided key to each
1564+
# provided value only if all do not already exist.
1565+
NX = "NX"
1566+
1567+
# set the value for each provided key to each
1568+
# provided value only if all already exist.
1569+
XX = "XX"
1570+
1571+
15621572
class BasicKeyCommands(CommandsProtocol):
15631573
"""
15641574
Redis basic key-based commands
@@ -1987,6 +1997,10 @@ def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
19871997
"""
19881998
Returns a list of values ordered identically to ``keys``
19891999
2000+
** Important ** When this method is used with Cluster clients, all keys
2001+
must be in the same hash slot, otherwise a RedisClusterException
2002+
will be raised.
2003+
19902004
For more information, see https://redis.io/commands/mget
19912005
"""
19922006
from redis.client import EMPTY_RESPONSE
@@ -2004,20 +2018,94 @@ def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
20042018
key/value pairs. Both keys and values should be strings or types that
20052019
can be cast to a string via str().
20062020
2021+
** Important ** When this method is used with Cluster clients, all keys
2022+
must be in the same hash slot, otherwise a RedisClusterException
2023+
will be raised.
2024+
20072025
For more information, see https://redis.io/commands/mset
20082026
"""
20092027
items = []
20102028
for pair in mapping.items():
20112029
items.extend(pair)
20122030
return self.execute_command("MSET", *items)
20132031

2032+
def msetex(
2033+
self,
2034+
mapping: Mapping[AnyKeyT, EncodableT],
2035+
data_persist_option: Optional[DataPersistOptions] = None,
2036+
ex: Optional[ExpiryT] = None,
2037+
px: Optional[ExpiryT] = None,
2038+
exat: Optional[AbsExpiryT] = None,
2039+
pxat: Optional[AbsExpiryT] = None,
2040+
keepttl: bool = False,
2041+
) -> Union[Awaitable[int], int]:
2042+
"""
2043+
Sets key/values based on the provided ``mapping`` items.
2044+
2045+
** Important ** When this method is used with Cluster clients, all keys
2046+
must be in the same hash slot, otherwise a RedisClusterException
2047+
will be raised.
2048+
2049+
``mapping`` accepts a dict of key/value pairs that will be added to the database.
2050+
2051+
``data_persist_option`` can be set to ``NX`` or ``XX`` to control the
2052+
behavior of the command.
2053+
``NX`` will set the value for each provided key to each
2054+
provided value only if all do not already exist.
2055+
``XX`` will set the value for each provided key to each
2056+
provided value only if all already exist.
2057+
2058+
``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds.
2059+
2060+
``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds.
2061+
2062+
``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds,
2063+
specified in unix time.
2064+
2065+
``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds,
2066+
specified in unix time.
2067+
2068+
``keepttl`` if True, retain the time to live associated with the keys.
2069+
2070+
Returns the number of fields that were added.
2071+
2072+
Available since Redis 8.4
2073+
For more information, see https://redis.io/commands/msetex
2074+
"""
2075+
opset = {ex, px, exat, pxat}
2076+
if len(opset) > 2 or len(opset) > 1 and keepttl:
2077+
raise DataError(
2078+
"``ex``, ``px``, ``exat``, ``pxat``, "
2079+
"and ``keepttl`` are mutually exclusive."
2080+
)
2081+
2082+
exp_options: list[EncodableT] = []
2083+
if data_persist_option:
2084+
exp_options.append(data_persist_option.value)
2085+
2086+
exp_options.extend(extract_expire_flags(ex, px, exat, pxat))
2087+
2088+
if keepttl:
2089+
exp_options.append("KEEPTTL")
2090+
2091+
pieces = ["MSETEX", len(mapping)]
2092+
2093+
for pair in mapping.items():
2094+
pieces.extend(pair)
2095+
2096+
return self.execute_command(*pieces, *exp_options)
2097+
20142098
def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
20152099
"""
20162100
Sets key/values based on a mapping if none of the keys are already set.
20172101
Mapping is a dictionary of key/value pairs. Both keys and values
20182102
should be strings or types that can be cast to a string via str().
20192103
Returns a boolean indicating if the operation was successful.
20202104
2105+
** Important ** When this method is used with Cluster clients, all keys
2106+
must be in the same hash slot, otherwise a RedisClusterException
2107+
will be raised.
2108+
20212109
For more information, see https://redis.io/commands/msetnx
20222110
"""
20232111
items = []
@@ -4013,6 +4101,7 @@ def xreadgroup(
40134101
count: Optional[int] = None,
40144102
block: Optional[int] = None,
40154103
noack: bool = False,
4104+
claim_min_idle_time: Optional[int] = None,
40164105
) -> ResponseT:
40174106
"""
40184107
Read from a stream via a consumer group.
@@ -4030,8 +4119,12 @@ def xreadgroup(
40304119
block: number of milliseconds to wait, if nothing already present.
40314120
noack: do not add messages to the PEL
40324121
4122+
claim_min_idle_time: accepts an integer type and represents a
4123+
time interval in milliseconds
4124+
40334125
For more information, see https://redis.io/commands/xreadgroup
40344126
"""
4127+
options = {}
40354128
pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
40364129
if count is not None:
40374130
if not isinstance(count, int) or count < 1:
@@ -4045,12 +4138,20 @@ def xreadgroup(
40454138
pieces.append(str(block))
40464139
if noack:
40474140
pieces.append(b"NOACK")
4141+
if claim_min_idle_time is not None:
4142+
if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0:
4143+
raise DataError(
4144+
"XREADGROUP claim_min_idle_time must be a non-negative integer"
4145+
)
4146+
pieces.append(b"CLAIM")
4147+
pieces.append(claim_min_idle_time)
4148+
options["claim_min_idle_time"] = claim_min_idle_time
40484149
if not isinstance(streams, dict) or len(streams) == 0:
40494150
raise DataError("XREADGROUP streams must be a non empty dict")
40504151
pieces.append(b"STREAMS")
40514152
pieces.extend(streams.keys())
40524153
pieces.extend(streams.values())
4053-
return self.execute_command("XREADGROUP", *pieces)
4154+
return self.execute_command("XREADGROUP", *pieces, **options)
40544155

40554156
def xrevrange(
40564157
self,

0 commit comments

Comments
 (0)