Skip to content

Commit 976ae66

Browse files
committed
Merge remote-tracking branch 'origin/master' into ck_py312
2 parents 35efa8e + 0be67bf commit 976ae66

File tree

12 files changed

+163
-141
lines changed

12 files changed

+163
-141
lines changed

.github/workflows/integration.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ permissions:
2525

2626
env:
2727
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
28-
REDIS_IMAGE: redis/redis-stack-server:7.4.0-rc1
29-
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
28+
REDIS_IMAGE: redis:7.4-rc2
29+
REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc2
3030

3131
jobs:
3232
dependency-audit:

redis/asyncio/connection.py

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
)
2828
from urllib.parse import ParseResult, parse_qs, unquote, urlparse
2929

30+
from ..utils import format_error_message
31+
3032
# the functionality is available in 3.11.x but has a major issue before
3133
# 3.11.3. See https://github.com/redis/redis-py/issues/2633
3234
if sys.version_info >= (3, 11, 3):
@@ -345,9 +347,8 @@ async def _connect(self):
345347
def _host_error(self) -> str:
346348
pass
347349

348-
@abstractmethod
349350
def _error_message(self, exception: BaseException) -> str:
350-
pass
351+
return format_error_message(self._host_error(), exception)
351352

352353
async def on_connect(self) -> None:
353354
"""Initialize the connection, authenticate and select a database"""
@@ -799,27 +800,6 @@ async def _connect(self):
799800
def _host_error(self) -> str:
800801
return f"{self.host}:{self.port}"
801802

802-
def _error_message(self, exception: BaseException) -> str:
803-
# args for socket.error can either be (errno, "message")
804-
# or just "message"
805-
806-
host_error = self._host_error()
807-
808-
if not exception.args:
809-
# asyncio has a bug where on Connection reset by peer, the
810-
# exception is not instanciated, so args is empty. This is the
811-
# workaround.
812-
# See: https://github.com/redis/redis-py/issues/2237
813-
# See: https://github.com/python/cpython/issues/94061
814-
return f"Error connecting to {host_error}. Connection reset by peer"
815-
elif len(exception.args) == 1:
816-
return f"Error connecting to {host_error}. {exception.args[0]}."
817-
else:
818-
return (
819-
f"Error {exception.args[0]} connecting to {host_error}. "
820-
f"{exception}."
821-
)
822-
823803

824804
class SSLConnection(Connection):
825805
"""Manages SSL connections to and from the Redis server(s).
@@ -971,20 +951,6 @@ async def _connect(self):
971951
def _host_error(self) -> str:
972952
return self.path
973953

974-
def _error_message(self, exception: BaseException) -> str:
975-
# args for socket.error can either be (errno, "message")
976-
# or just "message"
977-
host_error = self._host_error()
978-
if len(exception.args) == 1:
979-
return (
980-
f"Error connecting to unix socket: {host_error}. {exception.args[0]}."
981-
)
982-
else:
983-
return (
984-
f"Error {exception.args[0]} connecting to unix socket: "
985-
f"{host_error}. {exception.args[1]}."
986-
)
987-
988954

989955
FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")
990956

redis/commands/core.py

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5126,9 +5126,8 @@ def hexpire(
51265126
lt: Set expiry only when the new expiry is less than the current one.
51275127
51285128
Returns:
5129-
If the key does not exist, returns an empty list. If the key exists, returns
5130-
a list which contains for each field in the request:
5131-
- `-2` if the field does not exist.
5129+
Returns a list which contains for each field in the request:
5130+
- `-2` if the field does not exist, or if the key does not exist.
51325131
- `0` if the specified NX | XX | GT | LT condition was not met.
51335132
- `1` if the expiration time was set or updated.
51345133
- `2` if the field was deleted because the specified expiration time is
@@ -5187,9 +5186,8 @@ def hpexpire(
51875186
lt: Set expiry only when the new expiry is less than the current one.
51885187
51895188
Returns:
5190-
If the key does not exist, returns an empty list. If the key exists, returns
5191-
a list which contains for each field in the request:
5192-
- `-2` if the field does not exist.
5189+
Returns a list which contains for each field in the request:
5190+
- `-2` if the field does not exist, or if the key does not exist.
51935191
- `0` if the specified NX | XX | GT | LT condition was not met.
51945192
- `1` if the expiration time was set or updated.
51955193
- `2` if the field was deleted because the specified expiration time is
@@ -5248,9 +5246,8 @@ def hexpireat(
52485246
lt: Set expiry only when the new expiry is less than the current one.
52495247
52505248
Returns:
5251-
If the key does not exist, returns an empty list. If the key exists, returns
5252-
a list which contains for each field in the request:
5253-
- `-2` if the field does not exist.
5249+
Returns a list which contains for each field in the request:
5250+
- `-2` if the field does not exist, or if the key does not exist.
52545251
- `0` if the specified NX | XX | GT | LT condition was not met.
52555252
- `1` if the expiration time was set or updated.
52565253
- `2` if the field was deleted because the specified expiration time is
@@ -5315,9 +5312,8 @@ def hpexpireat(
53155312
lt: Set expiry only when the new expiry is less than the current one.
53165313
53175314
Returns:
5318-
If the key does not exist, returns an empty list. If the key exists, returns
5319-
a list which contains for each field in the request:
5320-
- `-2` if the field does not exist.
5315+
Returns a list which contains for each field in the request:
5316+
- `-2` if the field does not exist, or if the key does not exist.
53215317
- `0` if the specified NX | XX | GT | LT condition was not met.
53225318
- `1` if the expiration time was set or updated.
53235319
- `2` if the field was deleted because the specified expiration time is
@@ -5362,9 +5358,8 @@ def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
53625358
expiration time.
53635359
53645360
Returns:
5365-
If the key does not exist, returns an empty list. If the key exists, returns
5366-
a list which contains for each field in the request:
5367-
- `-2` if the field does not exist.
5361+
Returns a list which contains for each field in the request:
5362+
- `-2` if the field does not exist, or if the key does not exist.
53685363
- `-1` if the field exists but has no associated expiration time.
53695364
- `1` if the expiration time was successfully removed from the field.
53705365
"""
@@ -5382,9 +5377,8 @@ def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
53825377
time.
53835378
53845379
Returns:
5385-
If the key does not exist, returns an empty list. If the key exists, returns
5386-
a list which contains for each field in the request:
5387-
- `-2` if the field does not exist.
5380+
Returns a list which contains for each field in the request:
5381+
- `-2` if the field does not exist, or if the key does not exist.
53885382
- `-1` if the field exists but has no associated expire time.
53895383
- A positive integer representing the expiration Unix timestamp in
53905384
seconds, if the field has an associated expiration time.
@@ -5405,9 +5399,8 @@ def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
54055399
time.
54065400
54075401
Returns:
5408-
If the key does not exist, returns an empty list. If the key exists, returns
5409-
a list which contains for each field in the request:
5410-
- `-2` if the field does not exist.
5402+
Returns a list which contains for each field in the request:
5403+
- `-2` if the field does not exist, or if the key does not exist.
54115404
- `-1` if the field exists but has no associated expire time.
54125405
- A positive integer representing the expiration Unix timestamp in
54135406
milliseconds, if the field has an associated expiration time.
@@ -5428,9 +5421,8 @@ def httl(self, key: KeyT, *fields: str) -> ResponseT:
54285421
fields: A list of fields within the hash for which to get the TTL.
54295422
54305423
Returns:
5431-
If the key does not exist, returns an empty list. If the key exists, returns
5432-
a list which contains for each field in the request:
5433-
- `-2` if the field does not exist.
5424+
Returns a list which contains for each field in the request:
5425+
- `-2` if the field does not exist, or if the key does not exist.
54345426
- `-1` if the field exists but has no associated expire time.
54355427
- A positive integer representing the TTL in seconds if the field has
54365428
an associated expiration time.
@@ -5451,9 +5443,8 @@ def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
54515443
fields: A list of fields within the hash for which to get the TTL.
54525444
54535445
Returns:
5454-
If the key does not exist, returns an empty list. If the key exists, returns
5455-
a list which contains for each field in the request:
5456-
- `-2` if the field does not exist.
5446+
Returns a list which contains for each field in the request:
5447+
- `-2` if the field does not exist, or if the key does not exist.
54575448
- `-1` if the field exists but has no associated expire time.
54585449
- A positive integer representing the TTL in milliseconds if the field
54595450
has an associated expiration time.

redis/connection.py

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
HIREDIS_AVAILABLE,
4040
HIREDIS_PACK_AVAILABLE,
4141
SSL_AVAILABLE,
42+
format_error_message,
4243
get_lib_version,
4344
str_if_bytes,
4445
)
@@ -338,9 +339,8 @@ def _connect(self):
338339
def _host_error(self):
339340
pass
340341

341-
@abstractmethod
342342
def _error_message(self, exception):
343-
pass
343+
return format_error_message(self._host_error(), exception)
344344

345345
def on_connect(self):
346346
"Initialize the connection, authenticate and select a database"
@@ -733,27 +733,6 @@ def _connect(self):
733733
def _host_error(self):
734734
return f"{self.host}:{self.port}"
735735

736-
def _error_message(self, exception):
737-
# args for socket.error can either be (errno, "message")
738-
# or just "message"
739-
740-
host_error = self._host_error()
741-
742-
if len(exception.args) == 1:
743-
try:
744-
return f"Error connecting to {host_error}. \
745-
{exception.args[0]}."
746-
except AttributeError:
747-
return f"Connection Error: {exception.args[0]}"
748-
else:
749-
try:
750-
return (
751-
f"Error {exception.args[0]} connecting to "
752-
f"{host_error}. {exception.args[1]}."
753-
)
754-
except AttributeError:
755-
return f"Connection Error: {exception.args[0]}"
756-
757736

758737
class SSLConnection(Connection):
759738
"""Manages SSL connections to and from the Redis server(s).
@@ -948,20 +927,6 @@ def _connect(self):
948927
def _host_error(self):
949928
return self.path
950929

951-
def _error_message(self, exception):
952-
# args for socket.error can either be (errno, "message")
953-
# or just "message"
954-
host_error = self._host_error()
955-
if len(exception.args) == 1:
956-
return (
957-
f"Error connecting to unix socket: {host_error}. {exception.args[0]}."
958-
)
959-
else:
960-
return (
961-
f"Error {exception.args[0]} connecting to unix socket: "
962-
f"{host_error}. {exception.args[1]}."
963-
)
964-
965930

966931
FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")
967932

redis/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,15 @@ def get_lib_version():
141141
except metadata.PackageNotFoundError:
142142
libver = "99.99.99"
143143
return libver
144+
145+
146+
def format_error_message(host_error: str, exception: BaseException) -> str:
147+
if not exception.args:
148+
return f"Error connecting to {host_error}."
149+
elif len(exception.args) == 1:
150+
return f"Error {exception.args[0]} connecting to {host_error}."
151+
else:
152+
return (
153+
f"Error {exception.args[0]} connecting to {host_error}. "
154+
f"{exception.args[1]}."
155+
)

tests/test_asyncio/test_connection.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
_AsyncRESPBase,
1313
)
1414
from redis.asyncio import ConnectionPool, Redis
15-
from redis.asyncio.connection import Connection, UnixDomainSocketConnection, parse_url
15+
from redis.asyncio.connection import (
16+
Connection,
17+
SSLConnection,
18+
UnixDomainSocketConnection,
19+
parse_url,
20+
)
1621
from redis.asyncio.retry import Retry
1722
from redis.backoff import NoBackoff
1823
from redis.exceptions import ConnectionError, InvalidResponse, TimeoutError
@@ -494,18 +499,50 @@ async def test_connection_garbage_collection(request):
494499

495500

496501
@pytest.mark.parametrize(
497-
"error, expected_message",
502+
"conn, error, expected_message",
498503
[
499-
(OSError(), "Error connecting to localhost:6379. Connection reset by peer"),
500-
(OSError(12), "Error connecting to localhost:6379. 12."),
504+
(SSLConnection(), OSError(), "Error connecting to localhost:6379."),
505+
(SSLConnection(), OSError(12), "Error 12 connecting to localhost:6379."),
501506
(
507+
SSLConnection(),
502508
OSError(12, "Some Error"),
503-
"Error 12 connecting to localhost:6379. [Errno 12] Some Error.",
509+
"Error 12 connecting to localhost:6379. Some Error.",
510+
),
511+
(
512+
UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
513+
OSError(),
514+
"Error connecting to unix:///tmp/redis.sock.",
515+
),
516+
(
517+
UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
518+
OSError(12),
519+
"Error 12 connecting to unix:///tmp/redis.sock.",
520+
),
521+
(
522+
UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
523+
OSError(12, "Some Error"),
524+
"Error 12 connecting to unix:///tmp/redis.sock. Some Error.",
504525
),
505526
],
506527
)
507-
async def test_connect_error_message(error, expected_message):
528+
async def test_format_error_message(conn, error, expected_message):
508529
"""Test that the _error_message function formats errors correctly"""
509-
conn = Connection()
510530
error_message = conn._error_message(error)
511531
assert error_message == expected_message
532+
533+
534+
async def test_network_connection_failure():
535+
with pytest.raises(ConnectionError) as e:
536+
redis = Redis(host="127.0.0.1", port=9999)
537+
await redis.set("a", "b")
538+
assert str(e.value).startswith("Error 111 connecting to 127.0.0.1:9999. Connect")
539+
540+
541+
async def test_unix_socket_connection_failure():
542+
with pytest.raises(ConnectionError) as e:
543+
redis = Redis(unix_socket_path="unix:///tmp/a.sock")
544+
await redis.set("a", "b")
545+
assert (
546+
str(e.value)
547+
== "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
548+
)

tests/test_asyncio/test_hash.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ async def test_hexpire_conditions(r):
4545
@skip_if_server_version_lt("7.3.240")
4646
async def test_hexpire_nonexistent_key_or_field(r):
4747
await r.delete("test:hash")
48-
assert await r.hexpire("test:hash", 1, "field1") == []
48+
assert await r.hexpire("test:hash", 1, "field1") == [-2]
4949
await r.hset("test:hash", "field1", "value1")
5050
assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]
5151

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

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

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

tests/test_asyncio/test_json.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async def test_mset(decoded_r: redis.Redis):
131131
async def test_clear(decoded_r: redis.Redis):
132132
await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
133133
assert 1 == await decoded_r.json().clear("arr", Path.root_path())
134-
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [[[]]])
134+
assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [])
135135

136136

137137
@pytest.mark.redismod

0 commit comments

Comments
 (0)