Skip to content

Prepare for Redis 7.4 RC2 #3303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
@@ -25,8 +25,8 @@ permissions:

env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
REDIS_IMAGE: redis/redis-stack-server:7.4.0-rc1
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
REDIS_IMAGE: redis:7.4-rc2
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc2

jobs:
dependency-audit:
45 changes: 18 additions & 27 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
@@ -5126,9 +5126,8 @@ def hexpire(
lt: Set expiry only when the new expiry is less than the current one.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
@@ -5187,9 +5186,8 @@ def hpexpire(
lt: Set expiry only when the new expiry is less than the current one.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
@@ -5248,9 +5246,8 @@ def hexpireat(
lt: Set expiry only when the new expiry is less than the current one.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
@@ -5315,9 +5312,8 @@ def hpexpireat(
lt: Set expiry only when the new expiry is less than the current one.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `0` if the specified NX | XX | GT | LT condition was not met.
- `1` if the expiration time was set or updated.
- `2` if the field was deleted because the specified expiration time is
@@ -5362,9 +5358,8 @@ def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
expiration time.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expiration time.
- `1` if the expiration time was successfully removed from the field.
"""
@@ -5382,9 +5377,8 @@ def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
time.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
seconds, if the field has an associated expiration time.
@@ -5405,9 +5399,8 @@ def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
time.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the expiration Unix timestamp in
milliseconds, if the field has an associated expiration time.
@@ -5428,9 +5421,8 @@ def httl(self, key: KeyT, *fields: str) -> ResponseT:
fields: A list of fields within the hash for which to get the TTL.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in seconds if the field has
an associated expiration time.
@@ -5451,9 +5443,8 @@ def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
fields: A list of fields within the hash for which to get the TTL.
Returns:
If the key does not exist, returns an empty list. If the key exists, returns
a list which contains for each field in the request:
- `-2` if the field does not exist.
Returns a list which contains for each field in the request:
- `-2` if the field does not exist, or if the key does not exist.
- `-1` if the field exists but has no associated expire time.
- A positive integer representing the TTL in milliseconds if the field
has an associated expiration time.
8 changes: 4 additions & 4 deletions tests/test_asyncio/test_hash.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ async def test_hexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
async def test_hexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hexpire("test:hash", 1, "field1") == []
assert await r.hexpire("test:hash", 1, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]

@@ -105,7 +105,7 @@ async def test_hpexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hpexpire("test:hash", 500, "field1") == []
assert await r.hpexpire("test:hash", 500, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]

@@ -163,7 +163,7 @@ async def test_hexpireat_conditions(r):
async def test_hexpireat_nonexistent_key_or_field(r):
await r.delete("test:hash")
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", future_exp_time, "field1") == []
assert await r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

@@ -228,7 +228,7 @@ async def test_hpexpireat_nonexistent_key_or_field(r):
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == []
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

2 changes: 1 addition & 1 deletion tests/test_asyncio/test_json.py
Original file line number Diff line number Diff line change
@@ -131,7 +131,7 @@ async def test_mset(decoded_r: redis.Redis):
async def test_clear(decoded_r: redis.Redis):
await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
assert 1 == await decoded_r.json().clear("arr", Path.root_path())
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [[[]]])
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [])


@pytest.mark.redismod
18 changes: 9 additions & 9 deletions tests/test_hash.py
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ def test_hexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hexpire_nonexistent_key_or_field(r):
r.delete("test:hash")
assert r.hexpire("test:hash", 1, "field1") == []
assert r.hexpire("test:hash", 1, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hexpire("test:hash", 1, "nonexistent_field") == [-2]

@@ -115,7 +115,7 @@ def test_hpexpire_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpexpire_nonexistent_key_or_field(r):
r.delete("test:hash")
assert r.hpexpire("test:hash", 500, "field1") == []
assert r.hpexpire("test:hash", 500, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]

@@ -182,7 +182,7 @@ def test_hexpireat_conditions(r):
def test_hexpireat_nonexistent_key_or_field(r):
r.delete("test:hash")
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert r.hexpireat("test:hash", future_exp_time, "field1") == []
assert r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

@@ -257,7 +257,7 @@ def test_hpexpireat_nonexistent_key_or_field(r):
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
assert r.hpexpireat("test:hash", future_exp_time, "field1") == []
assert r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
r.hset("test:hash", "field1", "value1")
assert r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]

@@ -298,7 +298,7 @@ def test_hpersist_multiple_fields(r):
@skip_if_server_version_lt("7.3.240")
def test_hpersist_nonexistent_key(r):
r.delete("test:hash")
assert r.hpersist("test:hash", "field1", "field2", "field3") == []
assert r.hpersist("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
@@ -315,7 +315,7 @@ def test_hexpiretime_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hexpiretime_nonexistent_key(r):
r.delete("test:hash")
assert r.hexpiretime("test:hash", "field1", "field2", "field3") == []
assert r.hexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
@@ -332,7 +332,7 @@ def test_hpexpiretime_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpexpiretime_nonexistent_key(r):
r.delete("test:hash")
assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == []
assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
@@ -349,7 +349,7 @@ def test_httl_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_httl_nonexistent_key(r):
r.delete("test:hash")
assert r.httl("test:hash", "field1", "field2", "field3") == []
assert r.httl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]


@skip_if_server_version_lt("7.3.240")
@@ -366,4 +366,4 @@ def test_hpttl_multiple_fields_mixed_conditions(r):
@skip_if_server_version_lt("7.3.240")
def test_hpttl_nonexistent_key(r):
r.delete("test:hash")
assert r.hpttl("test:hash", "field1", "field2", "field3") == []
assert r.hpttl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
2 changes: 1 addition & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
@@ -130,7 +130,7 @@ def test_mset(client):
def test_clear(client):
client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
assert 1 == client.json().clear("arr", Path.root_path())
assert_resp_response(client, client.json().get("arr"), [], [[[]]])
assert_resp_response(client, client.json().get("arr"), [], [])


@pytest.mark.redismod
33 changes: 17 additions & 16 deletions tests/test_search.py
Original file line number Diff line number Diff line change
@@ -2369,27 +2369,27 @@ def test_search_missing_fields(client):

with pytest.raises(redis.exceptions.ResponseError) as e:
client.ft().search(
Query("ismissing(@title)").dialect(5).return_field("id").no_content()
Query("ismissing(@title)").dialect(2).return_field("id").no_content()
)
assert "to be defined with 'INDEXMISSING'" in e.value.args[0]

res = client.ft().search(
Query("ismissing(@features)").dialect(5).return_field("id").no_content()
Query("ismissing(@features)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:2"])

res = client.ft().search(
Query("-ismissing(@features)").dialect(5).return_field("id").no_content()
Query("-ismissing(@features)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:3"])

res = client.ft().search(
Query("ismissing(@description)").dialect(5).return_field("id").no_content()
Query("ismissing(@description)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:3"])

res = client.ft().search(
Query("-ismissing(@description)").dialect(5).return_field("id").no_content()
Query("-ismissing(@description)").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:2"])

@@ -2438,27 +2438,29 @@ def test_search_empty_fields(client):

with pytest.raises(redis.exceptions.ResponseError) as e:
client.ft().search(
Query("@title:''").dialect(5).return_field("id").no_content()
Query("@title:''").dialect(2).return_field("id").no_content()
)
assert "to be defined with `INDEXEMPTY`" in e.value.args[0]
assert "Use `INDEXEMPTY` in field creation" in e.value.args[0]

res = client.ft().search(
Query("@features:{ }").dialect(5).return_field("id").no_content()
Query("@features:{$empty}").dialect(2).return_field("id").no_content(),
query_params={"empty": ""},
)
_assert_search_result(client, res, ["property:2"])

res = client.ft().search(
Query("-@features:{ }").dialect(5).return_field("id").no_content()
Query("-@features:{$empty}").dialect(2).return_field("id").no_content(),
query_params={"empty": ""},
)
_assert_search_result(client, res, ["property:1", "property:3"])

res = client.ft().search(
Query("@description:''").dialect(5).return_field("id").no_content()
Query("@description:''").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:3"])

res = client.ft().search(
Query("-@description:''").dialect(5).return_field("id").no_content()
Query("-@description:''").dialect(2).return_field("id").no_content()
)
_assert_search_result(client, res, ["property:1", "property:2"])

@@ -2503,22 +2505,21 @@ def test_special_characters_in_fields(client):
)
_assert_search_result(client, res, ["resource:1"])

# with dialect 5 no need to escape the - even without params
# with double quotes exact match no need to escape the - even without params
res = client.ft().search(
Query("@uuid:{123e4567-e89b-12d3-a456-426614174000}").dialect(5)
Query('@uuid:{"123e4567-e89b-12d3-a456-426614174000"}').dialect(2)
)
_assert_search_result(client, res, ["resource:1"])

# also no need to escape ' with dialect 5
res = client.ft().search(Query("@tags:{new-year's-resolutions}").dialect(5))
res = client.ft().search(Query('@tags:{"new-year\'s-resolutions"}').dialect(2))
_assert_search_result(client, res, ["resource:2"])

# possible to search numeric fields by single value
res = client.ft().search(Query("@rating:[4]").dialect(2))
_assert_search_result(client, res, ["resource:2"])

# some chars still need escaping
res = client.ft().search(Query(r"@tags:{\$btc}").dialect(5))
res = client.ft().search(Query(r"@tags:{\$btc}").dialect(2))
_assert_search_result(client, res, ["resource:1"])