diff --git a/redis/cache.py b/redis/cache.py index cb29ffe785..dc88f204ec 100644 --- a/redis/cache.py +++ b/redis/cache.py @@ -17,8 +17,21 @@ class EvictionPolicyType(Enum): @dataclass(frozen=True) class CacheKey: + """ + Represents a unique key for a cache entry. + + Attributes: + command (str): The Redis command being cached. + redis_keys (tuple): The Redis keys involved in the command. + redis_args (tuple): Additional arguments for the Redis command. + This field is included in the cache key to ensure uniqueness + when commands have the same keys but different arguments. + Changing this field will affect cache key uniqueness. + """ + command: str redis_keys: tuple + redis_args: tuple = () # Additional arguments for the Redis command; affects cache key uniqueness. class CacheEntry: diff --git a/redis/connection.py b/redis/connection.py index 389529a1a7..41ddde02c6 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -1423,7 +1423,9 @@ def send_command(self, *args, **kwargs): with self._cache_lock: # Command is write command or not allowed # to be cached. - if not self._cache.is_cachable(CacheKey(command=args[0], redis_keys=())): + if not self._cache.is_cachable( + CacheKey(command=args[0], redis_keys=(), redis_args=()) + ): self._current_command_cache_key = None self._conn.send_command(*args, **kwargs) return @@ -1433,7 +1435,7 @@ def send_command(self, *args, **kwargs): # Creates cache key. self._current_command_cache_key = CacheKey( - command=args[0], redis_keys=tuple(kwargs.get("keys")) + command=args[0], redis_keys=tuple(kwargs.get("keys")), redis_args=args ) with self._cache_lock: diff --git a/tests/test_cache.py b/tests/test_cache.py index 1f3193c49d..9f18f1c090 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -71,7 +71,9 @@ def test_get_from_given_cache(self, r, r2): # get key from redis and save in local cache assert r.get("foo") in [b"bar", "bar"] # get key from local cache - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -80,10 +82,94 @@ def test_get_from_given_cache(self, r, r2): # Retrieves a new value from server and cache it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ + b"barbar", + "barbar", + ] + + @pytest.mark.parametrize( + "r", + [ + { + "cache": DefaultCache(CacheConfig(max_size=5)), + "single_connection_client": True, + }, + { + "cache": DefaultCache(CacheConfig(max_size=5)), + "single_connection_client": False, + }, + { + "cache": DefaultCache(CacheConfig(max_size=5)), + "single_connection_client": False, + "decode_responses": True, + }, + ], + ids=["single", "pool", "decoded"], + indirect=True, + ) + @pytest.mark.onlynoncluster + def test_hash_get_from_given_cache(self, r, r2): + cache = r.get_cache() + hash_key = "hash_foo_key" + field_1 = "bar" + field_2 = "bar2" + + # add hash key to redis + r.hset(hash_key, field_1, "baz") + r.hset(hash_key, field_2, "baz2") + # get keys from redis and save them in local cache + assert r.hget(hash_key, field_1) in [b"baz", "baz"] + assert r.hget(hash_key, field_2) in [b"baz2", "baz2"] + # get key from local cache + assert cache.get( + CacheKey( + command="HGET", + redis_keys=(hash_key,), + redis_args=("HGET", hash_key, field_1), + ) + ).cache_value in [ + b"baz", + "baz", + ] + assert cache.get( + CacheKey( + command="HGET", + redis_keys=(hash_key,), + redis_args=("HGET", hash_key, field_2), + ) + ).cache_value in [ + b"baz2", + "baz2", + ] + # change key in redis (cause invalidation) + r2.hset(hash_key, field_1, "barbar") + # Retrieves a new value from server and cache it + assert r.hget(hash_key, field_1) in [b"barbar", "barbar"] + # Make sure that new value was cached + assert cache.get( + CacheKey( + command="HGET", + redis_keys=(hash_key,), + redis_args=("HGET", hash_key, field_1), + ) + ).cache_value in [ b"barbar", "barbar", ] + # The other field is also reset, because the invalidation message contains only the hash key. + assert ( + cache.get( + CacheKey( + command="HGET", + redis_keys=(hash_key,), + redis_args=("HGET", hash_key, field_2), + ) + ) + is None + ) + assert r.hget(hash_key, field_2) in [b"baz2", "baz2"] @pytest.mark.parametrize( "r", @@ -116,7 +202,9 @@ def test_get_from_default_cache(self, r, r2): # get key from redis and save in local cache assert r.get("foo") in [b"bar", "bar"] # get key from local cache - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -129,7 +217,9 @@ def test_get_from_default_cache(self, r, r2): # Retrieves a new value from server and cache it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -158,7 +248,9 @@ def test_cache_clears_on_disconnect(self, r, cache): assert r.get("foo") == b"bar" # get key from local cache assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"bar" ) # Force disconnection @@ -194,22 +286,37 @@ def test_cache_lru_eviction(self, r, cache): assert r.get("foo3") == b"bar3" # get the 3 keys from local cache assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"bar" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo2",))).cache_value + cache.get( + CacheKey( + command="GET", redis_keys=("foo2",), redis_args=("GET", "foo2") + ) + ).cache_value == b"bar2" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo3",))).cache_value + cache.get( + CacheKey( + command="GET", redis_keys=("foo3",), redis_args=("GET", "foo3") + ) + ).cache_value == b"bar3" ) # add 1 more key to redis (exceed the max size) r.set("foo4", "bar4") assert r.get("foo4") == b"bar4" # the first key is not in the local cache anymore - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))) is None + assert ( + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ) + is None + ) assert cache.size == 3 @pytest.mark.parametrize( @@ -234,7 +341,16 @@ def test_cache_ignore_not_allowed_command(self, r): assert r.hset("foo", "bar", "baz") # get random field assert r.hrandfield("foo") == b"bar" - assert cache.get(CacheKey(command="HRANDFIELD", redis_keys=("foo",))) is None + assert ( + cache.get( + CacheKey( + command="HRANDFIELD", + redis_keys=("foo",), + redis_args=("HRANDFIELD", "foo"), + ) + ) + is None + ) @pytest.mark.parametrize( "r", @@ -262,7 +378,13 @@ def test_cache_invalidate_all_related_responses(self, r): # Make sure that replies was cached assert res == [b"bar", b"foo"] assert ( - cache.get(CacheKey(command="MGET", redis_keys=("foo", "bar"))).cache_value + cache.get( + CacheKey( + command="MGET", + redis_keys=("foo", "bar"), + redis_args=("MGET", "foo", "bar"), + ) + ).cache_value == res ) @@ -275,9 +397,20 @@ def test_cache_invalidate_all_related_responses(self, r): # all associated cached entries was removed assert r.set("foo", "baz") assert r.get("foo") == b"baz" - assert cache.get(CacheKey(command="MGET", redis_keys=("foo", "bar"))) is None assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey( + command="MGET", + redis_keys=("foo", "bar"), + redis_args=("MGET", "foo", "bar"), + ) + ) + is None + ) + assert ( + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"baz" ) @@ -309,15 +442,21 @@ def test_cache_flushed_on_server_flush(self, r): assert r.get("bar") == b"foo" assert r.get("baz") == b"bar" assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"bar" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("bar",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("bar",), redis_args=("GET", "bar")) + ).cache_value == b"foo" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("baz",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("baz",), redis_args=("GET", "baz")) + ).cache_value == b"bar" ) @@ -352,7 +491,9 @@ def test_get_from_cache(self, r): # get key from redis and save in local cache assert r.get("foo") in [b"bar", "bar"] # get key from local cache - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -361,7 +502,9 @@ def test_get_from_cache(self, r): # Retrieves a new value from server and cache it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -393,7 +536,9 @@ def test_get_from_custom_cache(self, r, r2): # get key from redis and save in local cache assert r.get("foo") in [b"bar", "bar"] # get key from local cache - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -402,7 +547,9 @@ def test_get_from_custom_cache(self, r, r2): # Retrieves a new value from server and cache it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -425,7 +572,9 @@ def test_cache_clears_on_disconnect(self, r, r2): assert r.get("foo") == b"bar" # get key from local cache assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"bar" ) # Force disconnection @@ -457,22 +606,49 @@ def test_cache_lru_eviction(self, r): assert r.get("foo3{slot}") == b"bar3" # get the 3 keys from local cache assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("foo{slot}",), + redis_args=("GET", "foo{slot}"), + ) + ).cache_value == b"bar" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo2{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("foo2{slot}",), + redis_args=("GET", "foo2{slot}"), + ) + ).cache_value == b"bar2" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo3{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("foo3{slot}",), + redis_args=("GET", "foo3{slot}"), + ) + ).cache_value == b"bar3" ) # add 1 more key to redis (exceed the max size) r.set("foo4{slot}", "bar4") assert r.get("foo4{slot}") == b"bar4" # the first key is not in the local cache_data anymore - assert cache.get(CacheKey(command="GET", redis_keys=("foo{slot}",))) is None + assert ( + cache.get( + CacheKey( + command="GET", + redis_keys=("foo{slot}",), + redis_args=("GET", "foo{slot}"), + ) + ) + is None + ) @pytest.mark.parametrize( "r", @@ -490,7 +666,16 @@ def test_cache_ignore_not_allowed_command(self, r): assert r.hset("foo", "bar", "baz") # get random field assert r.hrandfield("foo") == b"bar" - assert cache.get(CacheKey(command="HRANDFIELD", redis_keys=("foo",))) is None + assert ( + cache.get( + CacheKey( + command="HRANDFIELD", + redis_keys=("foo",), + redis_args=("HRANDFIELD", "foo"), + ) + ) + is None + ) @pytest.mark.parametrize( "r", @@ -511,7 +696,15 @@ def test_cache_invalidate_all_related_responses(self, r, cache): # Make sure that replies was cached assert r.mget("foo{slot}", "bar{slot}") == [b"bar", b"foo"] assert cache.get( - CacheKey(command="MGET", redis_keys=("foo{slot}", "bar{slot}")), + CacheKey( + command="MGET", + redis_keys=("foo{slot}", "bar{slot}"), + redis_args=( + "MGET", + "foo{slot}", + "bar{slot}", + ), + ), ).cache_value == [b"bar", b"foo"] # Invalidate one of the keys and make sure @@ -520,12 +713,22 @@ def test_cache_invalidate_all_related_responses(self, r, cache): assert r.get("foo{slot}") == b"baz" assert ( cache.get( - CacheKey(command="MGET", redis_keys=("foo{slot}", "bar{slot}")), + CacheKey( + command="MGET", + redis_keys=("foo{slot}", "bar{slot}"), + redis_args=("MGET", "foo{slot}", "bar{slot}"), + ), ) is None ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("foo{slot}",), + redis_args=("GET", "foo{slot}"), + ) + ).cache_value == b"baz" ) @@ -551,15 +754,33 @@ def test_cache_flushed_on_server_flush(self, r, cache): assert r.get("bar{slot}") == b"foo" assert r.get("baz{slot}") == b"bar" assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("foo{slot}",), + redis_args=("GET", "foo{slot}"), + ) + ).cache_value == b"bar" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("bar{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("bar{slot}",), + redis_args=("GET", "bar{slot}"), + ) + ).cache_value == b"foo" ) assert ( - cache.get(CacheKey(command="GET", redis_keys=("baz{slot}",))).cache_value + cache.get( + CacheKey( + command="GET", + redis_keys=("baz{slot}",), + redis_args=("GET", "baz{slot}"), + ) + ).cache_value == b"bar" ) @@ -595,7 +816,9 @@ def test_get_from_cache(self, master): # get key from redis and save in local cache_data assert master.get("foo") in [b"bar", "bar"] # get key from local cache_data - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -604,7 +827,9 @@ def test_get_from_cache(self, master): # get key from redis assert master.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -631,7 +856,9 @@ def test_get_from_default_cache(self, r, r2): # get key from redis and save in local cache_data assert r.get("foo") in [b"bar", "bar"] # get key from local cache_data - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -641,7 +868,9 @@ def test_get_from_default_cache(self, r, r2): # Retrieves a new value from server and cache_data it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -665,7 +894,9 @@ def test_cache_clears_on_disconnect(self, master, cache): assert master.get("foo") == b"bar" # get key from local cache_data assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"bar" ) # Force disconnection @@ -701,7 +932,9 @@ def test_get_from_cache(self, r, r2, cache): # get key from redis and save in local cache_data assert r.get("foo") in [b"bar", "bar"] # get key from local cache_data - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -713,7 +946,9 @@ def test_get_from_cache(self, r, r2, cache): # Retrieves a new value from server and cache_data it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -742,7 +977,9 @@ def test_get_from_custom_cache(self, r, r2): # get key from redis and save in local cache_data assert r.get("foo") in [b"bar", "bar"] # get key from local cache_data - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"bar", "bar", ] @@ -754,7 +991,9 @@ def test_get_from_custom_cache(self, r, r2): # Retrieves a new value from server and cache_data it assert r.get("foo") in [b"barbar", "barbar"] # Make sure that new value was cached - assert cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value in [ + assert cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value in [ b"barbar", "barbar", ] @@ -779,7 +1018,11 @@ def test_cache_invalidate_all_related_responses(self, r): # Make sure that replies was cached assert r.mget("foo", "bar") == [b"bar", b"foo"] assert cache.get( - CacheKey(command="MGET", redis_keys=("foo", "bar")) + CacheKey( + command="MGET", + redis_keys=("foo", "bar"), + redis_args=("MGET", "foo", "bar"), + ) ).cache_value == [b"bar", b"foo"] # Invalidate one of the keys and make sure @@ -789,9 +1032,20 @@ def test_cache_invalidate_all_related_responses(self, r): # between data appears in socket buffer time.sleep(0.1) assert r.get("foo") == b"baz" - assert cache.get(CacheKey(command="MGET", redis_keys=("foo", "bar"))) is None assert ( - cache.get(CacheKey(command="GET", redis_keys=("foo",))).cache_value + cache.get( + CacheKey( + command="MGET", + redis_keys=("foo", "bar"), + redis_args=("MGET", "foo", "bar"), + ) + ) + is None + ) + assert ( + cache.get( + CacheKey(command="GET", redis_keys=("foo",), redis_args=("GET", "foo")) + ).cache_value == b"baz" ) @@ -868,9 +1122,15 @@ def test_set_does_not_store_not_allowed_key(self, cache_key, mock_connection): def test_set_evict_lru_cache_key_on_reaching_max_size(self, mock_connection): cache = DefaultCache(CacheConfig(max_size=3)) - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("foo1",)) - cache_key3 = CacheKey(command="GET", redis_keys=("foo2",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("foo1",), redis_args=("GET", "foo1") + ) + cache_key3 = CacheKey( + command="GET", redis_keys=("foo2",), redis_args=("GET", "foo2") + ) # Set 3 different keys assert cache.set( @@ -904,7 +1164,9 @@ def test_set_evict_lru_cache_key_on_reaching_max_size(self, mock_connection): assert cache.get(cache_key3).cache_value == b"bar2" assert cache.get(cache_key1).cache_value == b"bar" - cache_key4 = CacheKey(command="GET", redis_keys=("foo3",)) + cache_key4 = CacheKey( + command="GET", redis_keys=("foo3",), redis_args=("GET", "foo3") + ) assert cache.set( CacheEntry( cache_key=cache_key4, @@ -934,7 +1196,9 @@ def test_get_return_correct_value(self, cache_key, mock_connection): ) assert cache.get(cache_key).cache_value == b"val" - wrong_key = CacheKey(command="HGET", redis_keys=("foo",)) + wrong_key = CacheKey( + command="HGET", redis_keys=("foo",), redis_args=("HGET", "foo", "bar") + ) assert cache.get(wrong_key) is None result = cache.get(cache_key) @@ -953,10 +1217,18 @@ def test_get_return_correct_value(self, cache_key, mock_connection): def test_delete_by_cache_keys_removes_associated_entries(self, mock_connection): cache = DefaultCache(CacheConfig(max_size=5)) - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("foo1",)) - cache_key3 = CacheKey(command="GET", redis_keys=("foo2",)) - cache_key4 = CacheKey(command="GET", redis_keys=("foo3",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("foo1",), redis_args=("GET", "foo1") + ) + cache_key3 = CacheKey( + command="GET", redis_keys=("foo2",), redis_args=("GET", "foo2") + ) + cache_key4 = CacheKey( + command="GET", redis_keys=("foo3",), redis_args=("GET", "foo3") + ) # Set 3 different keys assert cache.set( @@ -995,10 +1267,22 @@ def test_delete_by_cache_keys_removes_associated_entries(self, mock_connection): def test_delete_by_redis_keys_removes_associated_entries(self, mock_connection): cache = DefaultCache(CacheConfig(max_size=5)) - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("foo1",)) - cache_key3 = CacheKey(command="MGET", redis_keys=("foo", "foo3")) - cache_key4 = CacheKey(command="MGET", redis_keys=("foo2", "foo3")) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("foo1",), redis_args=("GET", "foo1") + ) + cache_key3 = CacheKey( + command="MGET", + redis_keys=("foo", "foo3"), + redis_args=("MGET", "foo", "foo3"), + ) + cache_key4 = CacheKey( + command="MGET", + redis_keys=("foo2", "foo3"), + redis_args=("MGET", "foo2", "foo3"), + ) # Set 3 different keys assert cache.set( @@ -1041,9 +1325,15 @@ def test_delete_by_redis_keys_removes_associated_entries(self, mock_connection): def test_flush(self, mock_connection): cache = DefaultCache(CacheConfig(max_size=5)) - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("foo1",)) - cache_key3 = CacheKey(command="GET", redis_keys=("foo2",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("foo1",), redis_args=("GET", "foo1") + ) + cache_key3 = CacheKey( + command="GET", redis_keys=("foo2",), redis_args=("GET", "foo2") + ) # Set 3 different keys assert cache.set( @@ -1086,8 +1376,12 @@ def test_evict_next(self, mock_connection): ) policy = cache.eviction_policy - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("bar",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("bar",), redis_args=("GET", "bar") + ) assert cache.set( CacheEntry( @@ -1114,9 +1408,15 @@ def test_evict_many(self, mock_connection): CacheConfig(max_size=5, eviction_policy=EvictionPolicy.LRU) ) policy = cache.eviction_policy - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("bar",)) - cache_key3 = CacheKey(command="GET", redis_keys=("baz",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("bar",), redis_args=("GET", "bar") + ) + cache_key3 = CacheKey( + command="GET", redis_keys=("baz",), redis_args=("GET", "baz") + ) assert cache.set( CacheEntry( @@ -1156,8 +1456,12 @@ def test_touch(self, mock_connection): ) policy = cache.eviction_policy - cache_key1 = CacheKey(command="GET", redis_keys=("foo",)) - cache_key2 = CacheKey(command="GET", redis_keys=("bar",)) + cache_key1 = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + cache_key2 = CacheKey( + command="GET", redis_keys=("bar",), redis_args=("GET", "bar") + ) cache.set( CacheEntry( diff --git a/tests/test_connection.py b/tests/test_connection.py index 89ea04df75..441528ca6b 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -348,12 +348,17 @@ def test_format_error_message(conn, error, expected_message): def test_network_connection_failure(): - exp_err = f"Error {ECONNREFUSED} connecting to localhost:9999. Connection refused." + # Match only the stable part of the error message across OS + exp_err = rf"Error {ECONNREFUSED} connecting to localhost:9999\." with pytest.raises(ConnectionError, match=exp_err): redis = Redis(port=9999) redis.set("a", "b") +@pytest.mark.skipif( + not hasattr(socket, "AF_UNIX"), + reason="Unix domain sockets not supported on this platform", +) def test_unix_socket_connection_failure(): exp_err = "Error 2 connecting to unix:///tmp/a.sock. No such file or directory." with pytest.raises(ConnectionError, match=exp_err): @@ -423,7 +428,9 @@ def test_make_connection_proxy_connection_on_given_cache(self): class TestUnitCacheProxyConnection: def test_clears_cache_on_disconnect(self, mock_connection, cache_conf): cache = DefaultCache(CacheConfig(max_size=10)) - cache_key = CacheKey(command="GET", redis_keys=("foo",)) + cache_key = CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) cache.set( CacheEntry( @@ -463,25 +470,33 @@ def test_read_response_returns_cached_reply(self, mock_cache, mock_connection): None, None, CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ), cache_value=CacheProxyConnection.DUMMY_CACHE_VALUE, status=CacheEntryStatus.IN_PROGRESS, connection_ref=mock_connection, ), CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ), cache_value=b"bar", status=CacheEntryStatus.VALID, connection_ref=mock_connection, ), CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ), cache_value=b"bar", status=CacheEntryStatus.VALID, connection_ref=mock_connection, ), CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ), cache_value=b"bar", status=CacheEntryStatus.VALID, connection_ref=mock_connection, @@ -503,7 +518,11 @@ def test_read_response_returns_cached_reply(self, mock_cache, mock_connection): [ call( CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", + redis_keys=("foo",), + redis_args=("GET", "foo"), + ), cache_value=CacheProxyConnection.DUMMY_CACHE_VALUE, status=CacheEntryStatus.IN_PROGRESS, connection_ref=mock_connection, @@ -511,7 +530,11 @@ def test_read_response_returns_cached_reply(self, mock_cache, mock_connection): ), call( CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", + redis_keys=("foo",), + redis_args=("GET", "foo"), + ), cache_value=b"bar", status=CacheEntryStatus.VALID, connection_ref=mock_connection, @@ -522,9 +545,21 @@ def test_read_response_returns_cached_reply(self, mock_cache, mock_connection): mock_cache.get.assert_has_calls( [ - call(CacheKey(command="GET", redis_keys=("foo",))), - call(CacheKey(command="GET", redis_keys=("foo",))), - call(CacheKey(command="GET", redis_keys=("foo",))), + call( + CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + ), + call( + CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + ), + call( + CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ) + ), ] ) @@ -544,7 +579,9 @@ def test_triggers_invalidation_processing_on_another_connection( another_conn.can_read.side_effect = [True, False] another_conn.read_response.return_value = None cache_entry = CacheEntry( - cache_key=CacheKey(command="GET", redis_keys=("foo",)), + cache_key=CacheKey( + command="GET", redis_keys=("foo",), redis_args=("GET", "foo") + ), cache_value=b"bar", status=CacheEntryStatus.VALID, connection_ref=another_conn,