From 130cf4e874c2caf03eefa26a77b7a4e80539a942 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 14 May 2023 16:39:52 +0300 Subject: [PATCH 1/8] support JSON.MERGE Command --- redis/commands/json/__init__.py | 1 + redis/commands/json/commands.py | 22 ++++++++++++++++++++++ tests/test_json.py | 16 ++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/redis/commands/json/__init__.py b/redis/commands/json/__init__.py index 7d55023e1e..77fb21c39f 100644 --- a/redis/commands/json/__init__.py +++ b/redis/commands/json/__init__.py @@ -38,6 +38,7 @@ def __init__( "JSON.GET": self._decode, "JSON.MGET": bulk_of_jsons(self._decode), "JSON.SET": lambda r: r and nativestr(r) == "OK", + "JSON.MERGE": lambda r: r and nativestr(r) == "OK", "JSON.NUMINCRBY": self._decode, "JSON.NUMMULTBY": self._decode, "JSON.TOGGLE": self._decode, diff --git a/redis/commands/json/commands.py b/redis/commands/json/commands.py index c02c47ad86..5da9245a95 100644 --- a/redis/commands/json/commands.py +++ b/redis/commands/json/commands.py @@ -253,6 +253,28 @@ def set( pieces.append("XX") return self.execute_command("JSON.SET", *pieces) + def merge( + self, + name: str, + path: str, + obj: JsonType, + decode_keys: Optional[bool] = False, + ) -> Optional[str]: + """ + Sets or updates the JSON value at a path.. + + ``decode_keys`` If set to True, the keys of ``obj`` will be decoded + with utf-8. + + For more information see `JSON.MERGE `_. + """ + if decode_keys: + obj = decode_dict_keys(obj) + + pieces = [name, str(path), self._encode(obj)] + + return self.execute_command("JSON.MERGE", *pieces) + def set_file( self, name: str, diff --git a/tests/test_json.py b/tests/test_json.py index 8e8da05609..9d85c1e585 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -47,6 +47,22 @@ def test_json_get_jset(client): assert client.exists("foo") == 0 +@pytest.mark.redismod +@skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release +def test_json_merge(client): + assert client.json().set("test_merge", Path.root_path(), '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}') + assert client.json().merge("test_merge", Path.root_path(), '{"person":{"age":30}}') + assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + + # Test with root path path $.a.b + assert client.json().merge("test_merge", Path("person", "address"), '{"work":"Redis office"}') + assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' + + # Test with null value to delete a value + assert client.json().merge("test_merge", Path("person", "address"), '{"work":null}') + assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + + @pytest.mark.redismod def test_nonascii_setgetdelete(client): assert client.json().set("notascii", Path.root_path(), "hyvää-élève") From 7901b1f1d5e65ecafdad781453a75a9f46960410 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 14 May 2023 17:16:33 +0300 Subject: [PATCH 2/8] linters --- tests/test_json.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/test_json.py b/tests/test_json.py index 9d85c1e585..fff18fc7ab 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -50,17 +50,32 @@ def test_json_get_jset(client): @pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release def test_json_merge(client): - assert client.json().set("test_merge", Path.root_path(), '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}') + assert client.json().set( + "test_merge", + Path.root_path(), + '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}', + ) assert client.json().merge("test_merge", Path.root_path(), '{"person":{"age":30}}') - assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + assert ( + client.json().get("test_merge") + == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + ) # Test with root path path $.a.b - assert client.json().merge("test_merge", Path("person", "address"), '{"work":"Redis office"}') - assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' + assert client.json().merge( + "test_merge", Path("person", "address"), '{"work":"Redis office"}' + ) + assert ( + client.json().get("test_merge") + == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' + ) # Test with null value to delete a value assert client.json().merge("test_merge", Path("person", "address"), '{"work":null}') - assert client.json().get("test_merge") == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + assert ( + client.json().get("test_merge") + == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + ) @pytest.mark.redismod From 99fd14400728c9231ec8460cecdc3e2ac1898e89 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 11:25:59 +0300 Subject: [PATCH 3/8] try with abc instead person --- tests/test_json.py | 57 +++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/tests/test_json.py b/tests/test_json.py index fff18fc7ab..580470a9cd 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -50,32 +50,53 @@ def test_json_get_jset(client): @pytest.mark.redismod @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release def test_json_merge(client): + # Test with root path $ assert client.json().set( "test_merge", Path.root_path(), - '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}', - ) - assert client.json().merge("test_merge", Path.root_path(), '{"person":{"age":30}}') - assert ( - client.json().get("test_merge") - == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' + '{"a":{"b":{"c":"d"}}}', ) + assert client.json().merge("test_merge", Path.root_path(), '{"a":{"b":{"e":"f"}}}') + assert client.json().get("test_merge") == '{"a":{"b":{"c":"d","e":"f"}}}' # Test with root path path $.a.b - assert client.json().merge( - "test_merge", Path("person", "address"), '{"work":"Redis office"}' - ) - assert ( - client.json().get("test_merge") - == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' - ) + assert client.json().merge("test_merge", "$.a.b", '{"h":"i"}') + assert client.json().get("test_merge") == '{"a":{"b":{"c":"d","e":"f","h":"i"}}}' # Test with null value to delete a value - assert client.json().merge("test_merge", Path("person", "address"), '{"work":null}') - assert ( - client.json().get("test_merge") - == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' - ) + assert client.json().merge("test_merge", "$.a.b", '{"c":null}') + assert client.json().get("test_merge") == '{"a":{"b":{"h":"i","e":"f"}}}' + + +# @pytest.mark.redismod +# @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release +# def test_json_merge(client): +# assert client.json().set( +# "test_merge", +# Path.root_path(), +# '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}', +# ) +# assert client.json().merge("test_merge", Path.root_path(), '{"person":{"age":30}}') +# assert ( +# client.json().get("test_merge") +# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' +# ) +# +# # Test with root path path $.a.b +# assert client.json().merge( +# "test_merge", Path("person", "address"), '{"work":"Redis office"}' +# ) +# assert ( +# client.json().get("test_merge") +# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' +# ) +# +# # Test with null value to delete a value +# assert client.json().merge("test_merge", Path("person", "address"), '{"work":null}') +# assert ( +# client.json().get("test_merge") +# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' +# ) @pytest.mark.redismod From 5c645645fbc359231a285c831669a8a7a197325b Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 12:07:03 +0300 Subject: [PATCH 4/8] change @skip_ifmodversion_lt to latest ReJSON 2.4.7 --- tests/test_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_json.py b/tests/test_json.py index 580470a9cd..bcec6c3157 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -48,7 +48,7 @@ def test_json_get_jset(client): @pytest.mark.redismod -@skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release +@skip_ifmodversion_lt("2.4.7", "ReJSON") # todo: update after the release def test_json_merge(client): # Test with root path $ assert client.json().set( From 86d98f764c69af256c7ff1eb90c482fd659561ea Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 12:50:27 +0300 Subject: [PATCH 5/8] change version --- tests/test_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_json.py b/tests/test_json.py index bcec6c3157..6986972dda 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -48,7 +48,7 @@ def test_json_get_jset(client): @pytest.mark.redismod -@skip_ifmodversion_lt("2.4.7", "ReJSON") # todo: update after the release +@skip_ifmodversion_lt("2.6.0", "ReJSON") # todo: update after the release def test_json_merge(client): # Test with root path $ assert client.json().set( From c58ed30fe0f3e1eca8dec9b35b5a133b3db80736 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 14:32:39 +0300 Subject: [PATCH 6/8] fix test --- tests/test_json.py | 51 +++++++++------------------------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/tests/test_json.py b/tests/test_json.py index 6986972dda..992aacdf4a 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -52,51 +52,20 @@ def test_json_get_jset(client): def test_json_merge(client): # Test with root path $ assert client.json().set( - "test_merge", - Path.root_path(), - '{"a":{"b":{"c":"d"}}}', + "person_data", + "$", + {"person1":{"personal_data":{"name":"John"}}}, ) - assert client.json().merge("test_merge", Path.root_path(), '{"a":{"b":{"e":"f"}}}') - assert client.json().get("test_merge") == '{"a":{"b":{"c":"d","e":"f"}}}' + assert client.json().merge("person_data", "$", {"person1":{"personal_data":{"hobbies":"reading"}}}) + assert client.json().get("person_data") == {"person1":{"personal_data":{"name":"John","hobbies":"reading"}}} - # Test with root path path $.a.b - assert client.json().merge("test_merge", "$.a.b", '{"h":"i"}') - assert client.json().get("test_merge") == '{"a":{"b":{"c":"d","e":"f","h":"i"}}}' + # Test with root path path $.person1.personal_data + assert client.json().merge("person_data", "$.person1.personal_data", {"country":"Israel"}) + assert client.json().get("person_data") == {"person1":{"personal_data":{"name":"John","hobbies":"reading","country":"Israel"}}} # Test with null value to delete a value - assert client.json().merge("test_merge", "$.a.b", '{"c":null}') - assert client.json().get("test_merge") == '{"a":{"b":{"h":"i","e":"f"}}}' - - -# @pytest.mark.redismod -# @skip_ifmodversion_lt("99.99.99", "ReJSON") # todo: update after the release -# def test_json_merge(client): -# assert client.json().set( -# "test_merge", -# Path.root_path(), -# '{"person":{"name":"John Doe","age":25,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}', -# ) -# assert client.json().merge("test_merge", Path.root_path(), '{"person":{"age":30}}') -# assert ( -# client.json().get("test_merge") -# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' -# ) -# -# # Test with root path path $.a.b -# assert client.json().merge( -# "test_merge", Path("person", "address"), '{"work":"Redis office"}' -# ) -# assert ( -# client.json().get("test_merge") -# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street","work":"Redis office"},"phone":"123-456-7890"}}' -# ) -# -# # Test with null value to delete a value -# assert client.json().merge("test_merge", Path("person", "address"), '{"work":null}') -# assert ( -# client.json().get("test_merge") -# == '{"person":{"name":"John Doe","age":30,"address":{"home":"123 Main Street"},"phone":"123-456-7890"}}' -# ) + assert client.json().merge("person_data", "$.person1.personal_data", {"name":None}) + assert client.json().get("person_data") == {"person1":{"personal_data":{"country":"Israel","hobbies":"reading"}}} @pytest.mark.redismod From a167e5c0c0d311a75e028c2a5986b6fc97a7e825 Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 14:35:17 +0300 Subject: [PATCH 7/8] linters --- tests/test_json.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/test_json.py b/tests/test_json.py index 992aacdf4a..0a85998205 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -54,18 +54,30 @@ def test_json_merge(client): assert client.json().set( "person_data", "$", - {"person1":{"personal_data":{"name":"John"}}}, + {"person1": {"personal_data": {"name": "John"}}}, ) - assert client.json().merge("person_data", "$", {"person1":{"personal_data":{"hobbies":"reading"}}}) - assert client.json().get("person_data") == {"person1":{"personal_data":{"name":"John","hobbies":"reading"}}} + assert client.json().merge( + "person_data", "$", {"person1": {"personal_data": {"hobbies": "reading"}}} + ) + assert client.json().get("person_data") == { + "person1": {"personal_data": {"name": "John", "hobbies": "reading"}} + } # Test with root path path $.person1.personal_data - assert client.json().merge("person_data", "$.person1.personal_data", {"country":"Israel"}) - assert client.json().get("person_data") == {"person1":{"personal_data":{"name":"John","hobbies":"reading","country":"Israel"}}} + assert client.json().merge( + "person_data", "$.person1.personal_data", {"country": "Israel"} + ) + assert client.json().get("person_data") == { + "person1": { + "personal_data": {"name": "John", "hobbies": "reading", "country": "Israel"} + } + } # Test with null value to delete a value - assert client.json().merge("person_data", "$.person1.personal_data", {"name":None}) - assert client.json().get("person_data") == {"person1":{"personal_data":{"country":"Israel","hobbies":"reading"}}} + assert client.json().merge("person_data", "$.person1.personal_data", {"name": None}) + assert client.json().get("person_data") == { + "person1": {"personal_data": {"country": "Israel", "hobbies": "reading"}} + } @pytest.mark.redismod From 5dc0be3040e6ffe9a0505c9f9d1bb721f152a85c Mon Sep 17 00:00:00 2001 From: shacharPash Date: Mon, 15 May 2023 14:58:16 +0300 Subject: [PATCH 8/8] add async test --- tests/test_asyncio/test_json.py | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_asyncio/test_json.py b/tests/test_asyncio/test_json.py index fc530c63c1..7334399df4 100644 --- a/tests/test_asyncio/test_json.py +++ b/tests/test_asyncio/test_json.py @@ -39,6 +39,41 @@ async def test_json_get_jset(modclient: redis.Redis): assert await modclient.exists("foo") == 0 +@pytest.mark.redismod +@skip_ifmodversion_lt("2.6.0", "ReJSON") # todo: update after the release +async def test_json_merge(modclient: redis.Redis): + # Test with root path $ + assert await modclient.json().set( + "person_data", + "$", + {"person1": {"personal_data": {"name": "John"}}}, + ) + assert await modclient.json().merge( + "person_data", "$", {"person1": {"personal_data": {"hobbies": "reading"}}} + ) + assert await modclient.json().get("person_data") == { + "person1": {"personal_data": {"name": "John", "hobbies": "reading"}} + } + + # Test with root path path $.person1.personal_data + assert await modclient.json().merge( + "person_data", "$.person1.personal_data", {"country": "Israel"} + ) + assert await modclient.json().get("person_data") == { + "person1": { + "personal_data": {"name": "John", "hobbies": "reading", "country": "Israel"} + } + } + + # Test with null value to delete a value + assert await modclient.json().merge( + "person_data", "$.person1.personal_data", {"name": None} + ) + assert await modclient.json().get("person_data") == { + "person1": {"personal_data": {"country": "Israel", "hobbies": "reading"}} + } + + @pytest.mark.redismod async def test_nonascii_setgetdelete(modclient: redis.Redis): assert await modclient.json().set("notascii", Path.root_path(), "hyvää-élève")