Skip to content

Commit dcb6839

Browse files
lrafeeiTimPansinomergify[bot]
authored
Add support for Redis v6 (#1380)
* Add new redis methods * Cleanup, part 1 * [MegaLinter] Apply linters fixes * Add vectorset commands * Clean up tests * [MegaLinter] Apply linters fixes * Bump tests * Add (most) new methods * [MegaLinter] Apply linters fixes * Add retry in ignored methods * [MegaLinter] Apply linters fixes --------- Co-authored-by: Tim Pansino <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 4764774 commit dcb6839

File tree

7 files changed

+81
-48
lines changed

7 files changed

+81
-48
lines changed

newrelic/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,10 +3342,18 @@ def _process_module_builtin_defaults():
33423342
"redis.commands.bf.commands", "newrelic.hooks.datastore_redis", "instrument_redis_commands_bf_commands"
33433343
)
33443344

3345+
# Redis version <6.0
33453346
_process_module_definition(
33463347
"redis.commands.graph.commands", "newrelic.hooks.datastore_redis", "instrument_redis_commands_graph_commands"
33473348
)
33483349

3350+
# Added in Redis v6.0+
3351+
_process_module_definition(
3352+
"redis.commands.vectorset.commands",
3353+
"newrelic.hooks.datastore_redis",
3354+
"instrument_redis_commands_vectorset_commands",
3355+
)
3356+
33493357
_process_module_definition(
33503358
"valkey.asyncio.client", "newrelic.hooks.datastore_valkey", "instrument_asyncio_valkey_client"
33513359
)

newrelic/hooks/datastore_redis.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@
3939
"hexpire",
4040
"hexpireat",
4141
"hexpiretime",
42+
"hgetdel",
43+
"hgetex",
4244
"hpersist",
4345
"hpexpire",
4446
"hpexpireat",
4547
"hpexpiretime",
4648
"hpttl",
49+
"hsetex",
4750
"httl",
4851
"latency_doctor",
4952
"latency_graph",
@@ -79,6 +82,17 @@
7982
"spop",
8083
"srandmember",
8184
"unwatch",
85+
"vadd",
86+
"vcard",
87+
"vdim",
88+
"vemb",
89+
"vgetattr",
90+
"vinfo",
91+
"vlinks",
92+
"vrandmember",
93+
"vrem",
94+
"vsetattr",
95+
"vsim",
8296
"watch",
8397
"zlexcount",
8498
"zrevrangebyscore",
@@ -694,6 +708,10 @@ def instrument_redis_commands_graph_commands(module):
694708
_instrument_redis_commands_module(module, "GraphCommands")
695709

696710

711+
def instrument_redis_commands_vectorset_commands(module):
712+
_instrument_redis_commands_module(module, "VectorSetCommands")
713+
714+
697715
def instrument_redis_commands_bf_commands(module):
698716
_instrument_redis_commands_module(module, "BFCommands")
699717
_instrument_redis_commands_module(module, "CFCommands")

tests/datastore_redis/test_custom_conn_pool.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class FakeConnectionPool:
3737
def __init__(self, connection):
3838
self.connection = connection
3939

40-
def get_connection(self, name, *keys, **options):
40+
def get_connection(self, name=None, *keys, **options):
4141
return self.connection
4242

4343
def release(self, connection):
@@ -47,6 +47,15 @@ def disconnect(self):
4747
self.connection.disconnect()
4848

4949

50+
# Backwards compatibility between redis-py v4 and v6
51+
52+
# Call to `get_connection` function with the usage of the command
53+
# name as an input argument has been deprecated since v5.3.0.
54+
_get_command_as_arg = [
55+
(pytest.param(None, marks=pytest.mark.skipif(REDIS_PY_VERSION < (5, 3), reason="Deprecated after v5.3"))),
56+
(pytest.param("GET", marks=pytest.mark.skipif(REDIS_PY_VERSION >= (5, 3), reason="Needed until v5.3"))),
57+
]
58+
5059
# Settings
5160

5261
_enable_instance_settings = {"datastore_tracer.instance_reporting.enabled": True}
@@ -102,7 +111,7 @@ def exercise_redis(client):
102111
# Tests
103112

104113

105-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7), reason="Client list command introduced in 2.7")
114+
@pytest.mark.parametrize("command_name", _get_command_as_arg)
106115
@override_application_settings(_enable_instance_settings)
107116
@validate_transaction_metrics(
108117
"test_custom_conn_pool:test_fake_conn_pool_enable_instance",
@@ -111,12 +120,12 @@ def exercise_redis(client):
111120
background_task=True,
112121
)
113122
@background_task()
114-
def test_fake_conn_pool_enable_instance():
123+
def test_fake_conn_pool_enable_instance(command_name):
115124
client = redis.StrictRedis(host=DB_SETTINGS["host"], port=DB_SETTINGS["port"], db=0)
116125

117126
# Get a real connection
118127

119-
conn = client.connection_pool.get_connection("GET")
128+
conn = client.connection_pool.get_connection(command_name)
120129

121130
# Replace the original connection pool with one that doesn't
122131
# have the `connection_kwargs` attribute.
@@ -128,7 +137,7 @@ def test_fake_conn_pool_enable_instance():
128137
exercise_redis(client)
129138

130139

131-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 7), reason="Client list command introduced in 2.7")
140+
@pytest.mark.parametrize("command_name", _get_command_as_arg)
132141
@override_application_settings(_disable_instance_settings)
133142
@validate_transaction_metrics(
134143
"test_custom_conn_pool:test_fake_conn_pool_disable_instance",
@@ -137,12 +146,12 @@ def test_fake_conn_pool_enable_instance():
137146
background_task=True,
138147
)
139148
@background_task()
140-
def test_fake_conn_pool_disable_instance():
149+
def test_fake_conn_pool_disable_instance(command_name):
141150
client = redis.StrictRedis(host=DB_SETTINGS["host"], port=DB_SETTINGS["port"], db=0)
142151

143152
# Get a real connection
144153

145-
conn = client.connection_pool.get_connection("GET")
154+
conn = client.connection_pool.get_connection(command_name)
146155

147156
# Replace the original connection pool with one that doesn't
148157
# have the `connection_kwargs` attribute.

tests/datastore_redis/test_instance_info.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020

2121
REDIS_PY_VERSION = get_package_version_tuple("redis")
2222

23+
# Call to `get_connection` function with the usage of the command
24+
# name as an input argument has been deprecated since v5.3.0.
25+
_select_command_as_arg = [
26+
(pytest.param(None, marks=pytest.mark.skipif(REDIS_PY_VERSION < (5, 3), reason="Deprecated after v5.3"))),
27+
(pytest.param("SELECT", marks=pytest.mark.skipif(REDIS_PY_VERSION >= (5, 3), reason="Needed until v5.3"))),
28+
]
29+
2330
_instance_info_tests = [
2431
((), {}, ("localhost", "6379", "0")),
2532
((), {"host": None}, ("localhost", "6379", "0")),
@@ -71,11 +78,12 @@ def test_redis_connection_instance_info(args, kwargs, expected):
7178
r.connection_pool.release(connection)
7279

7380

81+
@pytest.mark.parametrize("command_name", _select_command_as_arg)
7482
@pytest.mark.parametrize("args,kwargs,expected", _instance_info_tests)
75-
def test_strict_redis_connection_instance_info(args, kwargs, expected):
83+
def test_strict_redis_connection_instance_info(args, kwargs, expected, command_name):
7684
r = redis.StrictRedis(*args, **kwargs)
7785
r.connection_pool.connection_class = DisabledConnection
78-
connection = r.connection_pool.get_connection("SELECT")
86+
connection = r.connection_pool.get_connection(command_name)
7987
try:
8088
conn_kwargs = _conn_attrs_to_dict(connection)
8189
assert _instance_info(conn_kwargs) == expected
@@ -93,49 +101,35 @@ def test_strict_redis_connection_instance_info(args, kwargs, expected):
93101
(("redis://:1234/",), {}, ("localhost", "1234", "0")),
94102
(("redis://@:1234/",), {}, ("localhost", "1234", "0")),
95103
(("redis://localhost:1234/garbage",), {}, ("localhost", "1234", "0")),
104+
(("redis://localhost:6379/2/",), {}, ("localhost", "6379", "2")),
105+
(("redis://localhost:6379",), {"host": "someotherhost"}, ("localhost", "6379", "0")),
106+
(("redis://localhost:6379/2",), {"db": 3}, ("localhost", "6379", "2")),
107+
(("redis://localhost:6379/2/?db=111",), {}, ("localhost", "6379", "111")),
108+
(("redis://localhost:6379?db=2",), {}, ("localhost", "6379", "2")),
109+
(("redis://localhost:6379/2?db=111",), {}, ("localhost", "6379", "111")),
110+
(("unix:///path/to/socket.sock",), {}, ("localhost", "/path/to/socket.sock", "0")),
111+
(("unix:///path/to/socket.sock?db=2",), {}, ("localhost", "/path/to/socket.sock", "2")),
112+
(("unix:///path/to/socket.sock",), {"db": 2}, ("localhost", "/path/to/socket.sock", "2")),
96113
]
97114

98-
# Behavior to default port to 6379 was added in v2.7.5
99-
# (https://github.com/redis/redis-py/commit/cfe1041bbb8a8887531810429879bffbe705b03a)
100-
# and removed in v4.0.0b1 (https://github.com/redis/redis-py/blame/v4.0.0b1/redis/connection.py#L997)
101-
if (3, 5, 3) >= REDIS_PY_VERSION >= (2, 7, 5):
102-
_instance_info_from_url_tests.append((("redis://127.0.0.1",), {}, ("127.0.0.1", "6379", "0")))
103-
104-
if REDIS_PY_VERSION >= (2, 10):
105-
_instance_info_from_url_tests.extend(
106-
[
107-
(("rediss://localhost:6379/2/",), {}, ("localhost", "6379", "2")),
108-
(("redis://localhost:6379",), {"host": "someotherhost"}, ("localhost", "6379", "0")),
109-
(("redis://localhost:6379/2",), {"db": 3}, ("localhost", "6379", "2")),
110-
(("redis://localhost:6379/2/?db=111",), {}, ("localhost", "6379", "111")),
111-
(("redis://localhost:6379?db=2",), {}, ("localhost", "6379", "2")),
112-
(("redis://localhost:6379/2?db=111",), {}, ("localhost", "6379", "111")),
113-
(("unix:///path/to/socket.sock",), {}, ("localhost", "/path/to/socket.sock", "0")),
114-
(("unix:///path/to/socket.sock?db=2",), {}, ("localhost", "/path/to/socket.sock", "2")),
115-
(("unix:///path/to/socket.sock",), {"db": 2}, ("localhost", "/path/to/socket.sock", "2")),
116-
]
117-
)
118-
119-
120-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 6), reason="from_url not yet implemented in this redis-py version")
115+
121116
@pytest.mark.parametrize("args,kwargs,expected", _instance_info_from_url_tests)
122117
def test_redis_client_from_url(args, kwargs, expected):
123118
r = redis.Redis.from_url(*args, **kwargs)
124119
conn_kwargs = r.connection_pool.connection_kwargs
125120
assert _instance_info(conn_kwargs) == expected
126121

127122

128-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 6), reason="from_url not yet implemented in this redis-py version")
129123
@pytest.mark.parametrize("args,kwargs,expected", _instance_info_from_url_tests)
130124
def test_strict_redis_client_from_url(args, kwargs, expected):
131125
r = redis.StrictRedis.from_url(*args, **kwargs)
132126
conn_kwargs = r.connection_pool.connection_kwargs
133127
assert _instance_info(conn_kwargs) == expected
134128

135129

136-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 6), reason="from_url not yet implemented in this redis-py version")
130+
@pytest.mark.parametrize("command_name", _select_command_as_arg)
137131
@pytest.mark.parametrize("args,kwargs,expected", _instance_info_from_url_tests)
138-
def test_redis_connection_from_url(args, kwargs, expected):
132+
def test_redis_connection_from_url(args, kwargs, expected, command_name):
139133
r = redis.Redis.from_url(*args, **kwargs)
140134
if r.connection_pool.connection_class is redis.Connection:
141135
r.connection_pool.connection_class = DisabledConnection
@@ -145,17 +139,17 @@ def test_redis_connection_from_url(args, kwargs, expected):
145139
r.connection_pool.connection_class = DisabledSSLConnection
146140
else:
147141
raise AssertionError(r.connection_pool.connection_class)
148-
connection = r.connection_pool.get_connection("SELECT")
142+
connection = r.connection_pool.get_connection(command_name)
149143
try:
150144
conn_kwargs = _conn_attrs_to_dict(connection)
151145
assert _instance_info(conn_kwargs) == expected
152146
finally:
153147
r.connection_pool.release(connection)
154148

155149

156-
@pytest.mark.skipif(REDIS_PY_VERSION < (2, 6), reason="from_url not yet implemented in this redis-py version")
150+
@pytest.mark.parametrize("command_name", _select_command_as_arg)
157151
@pytest.mark.parametrize("args,kwargs,expected", _instance_info_from_url_tests)
158-
def test_strict_redis_connection_from_url(args, kwargs, expected):
152+
def test_strict_redis_connection_from_url(args, kwargs, expected, command_name):
159153
r = redis.StrictRedis.from_url(*args, **kwargs)
160154
if r.connection_pool.connection_class is redis.Connection:
161155
r.connection_pool.connection_class = DisabledConnection
@@ -165,7 +159,7 @@ def test_strict_redis_connection_from_url(args, kwargs, expected):
165159
r.connection_pool.connection_class = DisabledSSLConnection
166160
else:
167161
raise AssertionError(r.connection_pool.connection_class)
168-
connection = r.connection_pool.get_connection("SELECT")
162+
connection = r.connection_pool.get_connection(command_name)
169163
try:
170164
conn_kwargs = _conn_attrs_to_dict(connection)
171165
assert _instance_info(conn_kwargs) == expected

tests/datastore_redis/test_uninstrumented_methods.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@
8686
"set_path",
8787
"set_response_callback",
8888
"set_retry",
89+
"single_connection_lock", # this is just the threading lock object
8990
"transaction",
9091
"version",
9192
}
9293

93-
REDIS_MODULES = {"bf", "cf", "cms", "ft", "graph", "json", "tdigest", "topk", "ts"}
94+
REDIS_MODULES = {"bf", "cf", "cms", "ft", "graph", "json", "tdigest", "topk", "ts", "vset"}
9495

9596
IGNORED_METHODS |= REDIS_MODULES
9697

tests/datastore_rediscluster/test_uninstrumented_rediscluster_methods.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@
9999
"cluster_addslotsrange",
100100
"cluster_bumpepoch",
101101
"cluster_delslotsrange",
102-
"cluster_error_retry_attempts",
102+
"cluster_error_retry_attempts", # Deprecated in redis-py v5.3.0
103+
"retry", # Use instead of cluster_error_retry_attempts after v5.3.0
103104
"cluster_flushslots",
104105
"cluster_links",
105106
"cluster_myid",
@@ -129,7 +130,8 @@
129130
"nodes_manager",
130131
"on_connect",
131132
"pubsub",
132-
"read_from_replicas",
133+
"read_from_replicas", # Deprecated in redis-py v5.3.0
134+
"load_balancing_strategy", # Use instead of read_from_replicas after v5.3.0
133135
"reinitialize_counter",
134136
"reinitialize_steps",
135137
"replace_default_node",
@@ -138,7 +140,7 @@
138140
"user_on_connect_func",
139141
}
140142

141-
REDIS_MODULES = {"bf", "cf", "cms", "ft", "graph", "json", "tdigest", "topk", "ts"}
143+
REDIS_MODULES = {"bf", "cf", "cms", "ft", "graph", "json", "tdigest", "topk", "ts", "vset"}
142144

143145
IGNORED_METHODS |= REDIS_MODULES
144146

tox.ini

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ envlist =
168168
rabbitmq-messagebroker_pika-{py37,py38,py39,py310,py311,py312,py313,pypy310}-pikalatest,
169169
rabbitmq-messagebroker_kombu-{py38,py39,py310,py311,py312,py313,pypy310}-kombulatest,
170170
rabbitmq-messagebroker_kombu-{py38,py39,py310,pypy310}-kombu050204,
171-
redis-datastore_redis-{py37,py311,pypy310}-redis04,
172-
redis-datastore_redis-{py37,py38,py39,py310,py311,py312,py313,pypy310}-redislatest,
171+
redis-datastore_redis-{py37,py38,py39,py310,py311,pypy310}-redis04,
172+
redis-datastore_redis-{py38,py39,py310,py311,py312,pypy310}-redis05,
173+
redis-datastore_redis-{py38,py39,py310,py311,py312,py313,pypy310}-redislatest,
173174
rediscluster-datastore_rediscluster-{py37,py312,py313,pypy310}-redislatest,
174175
valkey-datastore_valkey-{py38,py39,py310,py311,py312,py313,pypy310}-valkeylatest,
175176
solr-datastore_pysolr-{py37,py38,py39,py310,py311,py312,py313,pypy310},
@@ -292,9 +293,9 @@ deps =
292293
datastore_pymysql: cryptography
293294
datastore_pysolr: pysolr<4.0
294295
datastore_redis-redis04: redis<5
295-
; Pinning redis tests until official redis v6 support is added
296-
datastore_redis-redislatest: redis<5.3.0
297-
datastore_rediscluster-redislatest: redis<5.3.0
296+
datastore_redis-redis05: redis<6
297+
datastore_redis-redislatest: redis
298+
datastore_rediscluster-redislatest: redis
298299
datastore_valkey-valkeylatest: valkey
299300
external_aiobotocore-aiobotocorelatest: aiobotocore[awscli]
300301
external_aiobotocore-aiobotocorelatest: flask

0 commit comments

Comments
 (0)