From 68aedd9c5d3560a506301ad89ef21b23632237eb Mon Sep 17 00:00:00 2001 From: Brad MacPhee Date: Tue, 23 May 2023 16:40:34 -0300 Subject: [PATCH 1/6] expose OutOfMemoryError as explicit exception type - handle "OOM" error code string by raising explicit exception type instance - enables callers to avoid string matching after catching ResponseError --- redis/__init__.py | 2 ++ redis/asyncio/__init__.py | 2 ++ redis/asyncio/connection.py | 2 ++ redis/connection.py | 2 ++ redis/exceptions.py | 4 ++++ tests/test_asyncio/test_connection_pool.py | 10 +++++++++- tests/test_connection_pool.py | 9 ++++++++- 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/redis/__init__.py b/redis/__init__.py index b8850add15..d7b74edf41 100644 --- a/redis/__init__.py +++ b/redis/__init__.py @@ -19,6 +19,7 @@ ConnectionError, DataError, InvalidResponse, + OutOfMemoryError, PubSubError, ReadOnlyError, RedisError, @@ -72,6 +73,7 @@ def int_or_str(value): "from_url", "default_backoff", "InvalidResponse", + "OutOfMemoryError", "PubSubError", "ReadOnlyError", "Redis", diff --git a/redis/asyncio/__init__.py b/redis/asyncio/__init__.py index bf90dde555..2a82df251e 100644 --- a/redis/asyncio/__init__.py +++ b/redis/asyncio/__init__.py @@ -24,6 +24,7 @@ ConnectionError, DataError, InvalidResponse, + OutOfMemoryError, PubSubError, ReadOnlyError, RedisError, @@ -47,6 +48,7 @@ "default_backoff", "InvalidResponse", "PubSubError", + "OutOfMemoryError", "ReadOnlyError", "Redis", "RedisCluster", diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 462673f2ed..2e8d98c13b 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -49,6 +49,7 @@ ModuleError, NoPermissionError, NoScriptError, + OutOfMemoryError, ReadOnlyError, RedisError, ResponseError, @@ -174,6 +175,7 @@ class BaseParser: "READONLY": ReadOnlyError, "NOAUTH": AuthenticationError, "NOPERM": NoPermissionError, + "OOM": OutOfMemoryError, } def __init__(self, socket_read_size: int): diff --git a/redis/connection.py b/redis/connection.py index 5af8928a5d..bf0d6dea80 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -28,6 +28,7 @@ ModuleError, NoPermissionError, NoScriptError, + OutOfMemoryError, ReadOnlyError, RedisError, ResponseError, @@ -149,6 +150,7 @@ class BaseParser: MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError, **NO_AUTH_SET_ERROR, }, + "OOM": OutOfMemoryError, "WRONGPASS": AuthenticationError, "EXECABORT": ExecAbortError, "LOADING": BusyLoadingError, diff --git a/redis/exceptions.py b/redis/exceptions.py index 8a8bf423eb..c8418dcd38 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -49,6 +49,10 @@ class NoScriptError(ResponseError): pass +class OutOfMemoryError(ResponseError): + pass + + class ExecAbortError(ResponseError): pass diff --git a/tests/test_asyncio/test_connection_pool.py b/tests/test_asyncio/test_connection_pool.py index 92499e2c4a..24d9902142 100644 --- a/tests/test_asyncio/test_connection_pool.py +++ b/tests/test_asyncio/test_connection_pool.py @@ -606,10 +606,18 @@ async def test_busy_loading_from_pipeline(self, r): @skip_if_server_version_lt("2.8.8") @skip_if_redis_enterprise() async def test_read_only_error(self, r): - """READONLY errors get turned in ReadOnlyError exceptions""" + """READONLY errors get turned into ReadOnlyError exceptions""" with pytest.raises(redis.ReadOnlyError): await r.execute_command("DEBUG", "ERROR", "READONLY blah blah") + @skip_if_redis_enterprise() + async def test_oom_error(self, r): + """OOM errors get turned into OutOfMemoryError exceptions""" + with pytest.raises(redis.OutOfMemoryError): + # note: don't use the DEBUG OOM command since it's not the same + # as the db being full + await r.execute_command("DEBUG", "ERROR", "OOM blah blah") + def test_connect_from_url_tcp(self): connection = redis.Redis.from_url("redis://localhost") pool = connection.connection_pool diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py index e8a42692a1..155bffe56a 100644 --- a/tests/test_connection_pool.py +++ b/tests/test_connection_pool.py @@ -528,10 +528,17 @@ def test_busy_loading_from_pipeline(self, r): @skip_if_server_version_lt("2.8.8") @skip_if_redis_enterprise() def test_read_only_error(self, r): - "READONLY errors get turned in ReadOnlyError exceptions" + "READONLY errors get turned into ReadOnlyError exceptions" with pytest.raises(redis.ReadOnlyError): r.execute_command("DEBUG", "ERROR", "READONLY blah blah") + def test_oom_error(self, r): + "OOM errors get turned into OutOfMemoryError exceptions" + with pytest.raises(redis.OutOfMemoryError): + # note: don't use the DEBUG OOM command since it's not the same + # as the db being full + r.execute_command("DEBUG", "ERROR", "OOM blah blah") + def test_connect_from_url_tcp(self): connection = redis.Redis.from_url("redis://localhost") pool = connection.connection_pool From 9c5399a2bdf24290b0a0d9c2913684bc233ec03e Mon Sep 17 00:00:00 2001 From: Brad MacPhee Date: Mon, 5 Jun 2023 18:34:12 -0300 Subject: [PATCH 2/6] add OutOfMemoryError exception class docstring --- redis/exceptions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/redis/exceptions.py b/redis/exceptions.py index c8418dcd38..e280c41fdb 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -50,6 +50,11 @@ class NoScriptError(ResponseError): class OutOfMemoryError(ResponseError): + """ + Indicates the database is full. Can only occur when either: + * Redis maxmemory-policy=noeviction + * Redis maxmemory-policy=volatile* and there are no evictable keys + """ pass From cc6cfe1f4138e10f1b72785e9220c02a7638d8a7 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 15 Jun 2023 11:28:30 +0200 Subject: [PATCH 3/6] Provide more info in the exception docstring --- redis/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/redis/exceptions.py b/redis/exceptions.py index e280c41fdb..c87720da84 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -54,6 +54,8 @@ class OutOfMemoryError(ResponseError): Indicates the database is full. Can only occur when either: * Redis maxmemory-policy=noeviction * Redis maxmemory-policy=volatile* and there are no evictable keys + + For more information see `Memory optimization in Redis `_. # noqa """ pass From 3801fa76ad1407d8b0f67aef9e4151d1076911f0 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 15 Jun 2023 11:32:05 +0200 Subject: [PATCH 4/6] Fix formatting --- redis/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/redis/exceptions.py b/redis/exceptions.py index c87720da84..0bf67242ad 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -57,6 +57,7 @@ class OutOfMemoryError(ResponseError): For more information see `Memory optimization in Redis `_. # noqa """ + pass From 9e1e36210a3da2f4d09f15930e7f9978e03a87b5 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 15 Jun 2023 11:34:28 +0200 Subject: [PATCH 5/6] Again --- redis/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/exceptions.py b/redis/exceptions.py index 0bf67242ad..02bdafbc70 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -57,7 +57,7 @@ class OutOfMemoryError(ResponseError): For more information see `Memory optimization in Redis `_. # noqa """ - + pass From a927853824baec5f70f433c4e1836e7d512e1209 Mon Sep 17 00:00:00 2001 From: dvora-h <67596500+dvora-h@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:36:48 +0300 Subject: [PATCH 6/6] linters --- redis/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/exceptions.py b/redis/exceptions.py index 02bdafbc70..8bab093eb6 100644 --- a/redis/exceptions.py +++ b/redis/exceptions.py @@ -55,7 +55,7 @@ class OutOfMemoryError(ResponseError): * Redis maxmemory-policy=noeviction * Redis maxmemory-policy=volatile* and there are no evictable keys - For more information see `Memory optimization in Redis `_. # noqa + For more information see `Memory optimization in Redis `_. # noqa """ pass