From b8948d4a3049d393ad1c07bfb6054486e94ae2b9 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Sun, 31 Oct 2021 11:46:30 +0200 Subject: [PATCH 1/9] multi path tests for rejson --- tests/test_json.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/test_json.py b/tests/test_json.py index 83fbf28669..fb36fc043c 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -233,3 +233,73 @@ def test_objlenshouldsucceed(client): # assert [True, "bar", 1] == p.execute() # assert client.keys() == [] # assert client.get("foo") is None + +@pytest.mark.redismod +def test_jsondollardelete(client): + doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} + assert client.json().set("doc1", "$", doc1) + assert client.json().delete("doc1", "$..a") == 2 + r = client.json().get("doc1", "$") + assert r == [{"nested": {"b": 3}}] + + + doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], + "nested": {"b":[True, "a","b"]}} + assert client.json().set('doc2', '$', doc2) + assert client.json().delete("doc2", "$..a") == 1 + res = client.json().get("doc2", "$") + assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] + + doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, + {"ciao":[2,"a"]}, + {"ciaoc":[3,"non","ciao"]}, + {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] + assert client.json().set('doc3', '$', doc3) + assert client.json().delete('doc3', '$.[0]["nested"]..ciao') == 3 + + doc3val = [[{"ciao":["non ancora"], + "nested":[{},{}, + {"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]] + res = client.json().get('doc3', '$') + assert res == doc3val + + # Test default path + assert client.json().delete("doc3") == 1 + assert client.json().get("doc3", "$") is None + + client.json().delete("not_a_document", "..a") + +@pytest.mark.redismod +def test_jsondollarforget(client): + doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} + assert client.json().set("doc1", "$", doc1) + assert client.json().forget("doc1", "$..a") == 2 + r = client.json().get("doc1", "$") + assert r == [{"nested": {"b": 3}}] + + + doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], + "nested": {"b":[True, "a","b"]}} + assert client.json().set('doc2', '$', doc2) + assert client.json().forget("doc2", "$..a") == 1 + res = client.json().get("doc2", "$") + assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] + + doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, + {"ciao":[2,"a"]}, + {"ciaoc":[3,"non","ciao"]}, + {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] + assert client.json().set('doc3', '$', doc3) + assert client.json().forget('doc3', '$.[0]["nested"]..ciao') == 3 + + doc3val = [[{"ciao":["non ancora"], + "nested":[{},{}, + {"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]] + res = client.json().get('doc3', '$') + assert res == doc3val + + # Test default path + assert client.json().forget("doc3") == 1 + assert client.json().get("doc3", "$") is None + + client.json().forget("not_a_document", "..a") \ No newline at end of file From ade01832cdc9fb711023a60849181981f2b4ee81 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Sun, 31 Oct 2021 15:46:32 +0200 Subject: [PATCH 2/9] json test conversion continues --- tests/test_json.py | 511 ++++++++++++++++++++++++++- tests/testdata/jsontestdata.py | 617 +++++++++++++++++++++++++++++++++ 2 files changed, 1125 insertions(+), 3 deletions(-) create mode 100644 tests/testdata/jsontestdata.py diff --git a/tests/test_json.py b/tests/test_json.py index fb36fc043c..3c89ae3458 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1,7 +1,9 @@ import pytest import redis from redis.commands.json.path import Path +from redis import exceptions from .conftest import skip_ifmodversion_lt +from .testdata.jsontestdata import nested_large_key @pytest.fixture @@ -234,8 +236,11 @@ def test_objlenshouldsucceed(client): # assert client.keys() == [] # assert client.get("foo") is None + +######### DUAL + @pytest.mark.redismod -def test_jsondollardelete(client): +def test_json_delete_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert client.json().set("doc1", "$", doc1) assert client.json().delete("doc1", "$..a") == 2 @@ -270,7 +275,7 @@ def test_jsondollardelete(client): client.json().delete("not_a_document", "..a") @pytest.mark.redismod -def test_jsondollarforget(client): +def test_json_forget_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} assert client.json().set("doc1", "$", doc1) assert client.json().forget("doc1", "$..a") == 2 @@ -302,4 +307,504 @@ def test_jsondollarforget(client): assert client.json().forget("doc3") == 1 assert client.json().get("doc3", "$") is None - client.json().forget("not_a_document", "..a") \ No newline at end of file + client.json().forget("not_a_document", "..a") + +@pytest.mark.redismod +def test_set_and_get_with_dollar(client): + # Test set and get on large nested key + assert client.json().set("doc1", "$", nested_large_key, "XX") is None + assert client.json().set("doc1", "$", nested_large_key, "NX") + assert client.json().get('doc1', '$') == [nested_large_key] + assert client.json().set("doc1", "$", nested_large_key, "NX") is None + + # Test single path + assert client.json().get('doc1', '$..tm') == [[46,876.85],[134.761,"jcoels",None]] + + # Test multi get and set + assert client.json().get('doc1', '$..foobar') == [3.141592,1.61803398875] + + # Set multi existing values + client.json().set('doc1', '$..foobar', '"new_val"') + assert client.json().get('doc1', '$..foobar') == ["new_val","new_val"] + + # Test multi set and get on small nested key + nested_simple_key = {"a":1,"nested":{"a":2,"b":3}} + client.json().set('doc2', '$', nested_simple_key) + assert client.json().get('doc2', '$') == [nested_simple_key] + # Set multi existing values + client.json().set('doc2', '$..a', '4.2') + assert client.json().get('doc2', '$') == \ + [{"a":4.2,"nested":{"a":4.2,"b":3}}] + + + # Test multi paths + assert client.json().get('doc1', '$..tm', '$..nu') == \ + [[[46,876.85],[134.761,"jcoels",None]],[[377,"qda",True]]] + # Test multi paths - if one path is none-legacy - result format is not legacy + assert client.json().get('doc1', '..tm', '$..nu') == \ + [[[46,876.85],[134.761,"jcoels",None]],[[377,"qda",True]]] + + # Test missing key + assert client.json().get('docX', '..tm', '$..nu') is None + # Test missing path + assert client.json().get('doc1', '..tm', '$..back_in_nov') == \ + [[[46,876.85],[134.761,"jcoels",None]],[]] + assert client.json().get('doc2', '..a', '..b', '$.back_in_nov') == \ + [[4.2,4.2],[3],[]] + + # Test legacy multi path (all paths are legacy) + client.json().get('doc1', '..nu', '..tm') == \ + {"..nu":[377,"qda",True],"..tm":[46,876.85]} + # Test legacy single path + client.json().get('doc1', '..tm') == '[46,876.85]' + + # Test missing legacy path (should return an error for a missing path) + client.json().set('doc2', '$.nested.b', None) + + with pytest.raises(exceptions.DataError): + client.json.get('doc2', '.a', '.nested.b', '.back_in_nov', '.ttyl') + client.json.get('JSON.GET', 'doc2', '.back_in_nov') + +@pytest.mark.redismod +def test_json_mget_dollar(client): + # Test mget with multi paths + client.json().set('doc1', '$', {"a":1, "b": 2, "nested": {"a": 3}, "c": None, "nested2": {"a": None}}) + client.json().set('doc2', '$', {"a":4, "b": 5, "nested": {"a": 6}, "c": None, "nested2": {"a": [None]}}) + # Compare also to single JSON.GET + assert client.json().get('doc1', '$..a') == [1,3,None] + assert client.json().get('doc2', '$..a') == [4,6,[None]] + + # Test mget with single path + client.json().mget('doc1', '$..a') == [1,3,None] + # Test mget with multi path + client.json().mget('doc1', 'doc2', '$..a') == [[1,3,None], [4,6,[None]]] + + # Test missing key + client.json().mget('doc1', 'missing_doc', '$..a') == [[1,3,None], None] + res = client.json().mget('missing_doc1', 'missing_doc2', '$..a') + assert res == [None, None] + +@pytest.mark.redismod +def test_numby_commands_dollar(env): + + r = env + + # Test NUMINCRBY + client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + # Test multi + assert client.json().numincrby('doc1', '$..a', '2') == [None, 4, 7.0, None] + + assert client.json().numincrby('doc1', '$..a', '2.5') == [None, 6.5, 9.5, None] + # Test single + assert client.json().numincrby('doc1', '$.b[1].a', '2') == [11.5] + + assert client.json().numincrby('doc1', '$.b[2].a', '2') == [None] + assert client.json().numincrby('doc1', '$.b[1].a', '3.5') == [15.0] + + # Test NUMMULTBY + client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + + assert client.json().nummultby('doc1', '$..a', '2') == [None, 4, 10, None] + assert client.json().nummultby('doc1', '$..a', '2.5') == [None,10.0,25.0,None] + # Test single + assert client.json().nummultby('doc1', '$.b[1].a', '2') == [50.0] + assert client.json().nummultby('doc1', '$.b[2].a', '2') == [None] + assert client.json().nummultby('doc1', '$.b[1].a', '3') == [150.0] + + # Test NUMPOWBY + client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + # Test multi + assert client.json().numpowby('doc1', '$..a', '2') == [None, 4, 25, None] + # Avoid json.loads to verify the underlying type (integer/float) + assert client.json().numpowby('doc1', '$..a', '2') == [None,16,625.0,None] + + # Test single + assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[1].a', '2') == [390625.0] + assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[2].a', '2') == [None] + assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[1].a', '3') == [5.960464477539062e16] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().numincrby('non_existing_doc', '$..a', '2') + client.json().nummultby('non_existing_doc', '$..a', '2') + + # TODO fixme + r.expect('JSON.NUMPOWBY', 'non_existing_doc', '$..a', '2') + + # Test legacy NUMINCRBY + client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + client.json().numincrby('doc1', '.b[0].a', '3') == 5 + + # Test legacy NUMMULTBY + client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + client.json().nummultby('doc1', '.b[0].a', '3') == 6 + +@pytest.mark.redismod +def test_strappend_dollar(client): + + client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) + # Test multi + client.json().strappend('doc1', '$..a', '"bar"') == [6, 8, None] + + + client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobar"},"nested2":{"a":31}}] + # Test single + client.json().strappend('doc1', '$.nested1.a', '"baz"') == [11] + + client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobarbaz"},"nested2":{"a":31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().strappend('non_existing_doc', '$..a', '"err"') + + # Test multi + client.json().strappend('doc1', '.*.a', '"bar"') == 8 + client.json.get('doc1', '$') == [{"a":"foo","nested1":{"a":"hellobar"},"nested2":{"a":31}}] + + # Test missing path + with pytest.raises(exceptions.DataError): + client.json().strappend('doc1', '"piu"') + +@pytest.mark.redismod +def test_strlen_dollar(client): + + # Test multi + client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) + res1 = client.json().strlen('doc1', '$..a') [3, 5, None] + + res2 = client.json().strappend('doc1', '$..a', '"bar"') == [6, 8, None] + res1 = client.json().strlen('doc1', '$..a') + assert res1 == res2 + + # Test single + client.json().strlen('doc1', '$.nested1.a') == [8] + client.json().strlen('doc1', '$.nested2.a') == [None] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().strlen('non_existing_doc', '$..a') + +@pytest.mark.redismod +def test_arrappend_dollar(client): + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi + client.json().arrappend('doc1', '$..a', '"bar"', '"racuda"') == [3, 5, None] + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] + + # Test single + assert client.json().arrappend('doc1', '$.nested1.a', '"baz"') == [6] + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json.arrappend('non_existing_doc', '$..a') + + # Test legacy + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi (all paths are updated, but return result of last path) + assert client.json().arrappend('doc1', '..a', '"bar"', '"racuda"') == 5 + + assert client.json.get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] + # Test single + assert client.json().arrappend('doc1', '.nested1.a', '"baz"') == 6 + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json.arrappend('non_existing_doc', '$..a') + +@pytest.mark.redismod +def test_arrinsert_dollar(client): + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi + assert client.json().arrinsert('doc1', '$..a', '1', '"bar"', '"racuda"') == [3, 5, None] + + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, "nested2": {"a": 31}}] + # Test single + assert client.json().arrinsert('doc1', '$.nested1.a', -2, '"baz"') == [6] + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json.arrappend('non_existing_doc', '$..a') + + # Test legacy + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + assert client.json().arrappend('doc1', '..a', '1', '"bar"', '"racuda"') == 5 + + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, "nested2": {"a": 31}}] + # Test single + assert client.json().arrinsert('doc1', '.nested1.a', -2, '"baz"') == 6 + assert client.json().get('doc1', '$') == \ + [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json.arrinsert('non_existing_doc', '$..a') + +@pytest.mark.redismod +def test_arrlen_dollar(client): + + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + + # Test multi + assert client.json().arrlen('doc1', '$..a') == [1, 3, None] + assert client.json().arrappend('doc1', '$..a', '"non"', '"abba"', '"stanza"') == \ + [4, 6, None] + + client.json().clear('doc1', '$.a') + assert client.json.arrlen('doc1', '$..a') == [0, 6, None] + # Test single + assert client.json().arrlen('doc1', '$.nested1.a') == [6] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json.arrappend('non_existing_doc', '$..a') + + # Test legacy + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi (return result of last path) + assert client.json().arrlen('doc1', '$..a') == [1, 3, None] + assert client.json().arrappend('doc1', '..a', '"non"', '"abba"', '"stanza"') == 6 + + # Test single + assert client.json().arrlen('doc1', '.nested1.a') = 6 + + # Test missing key + assert client.json().arrlen('non_existing_doc', '..a') is None + +@pytest.mark.redismod +def test_arrpop_dollar(client): + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi + assert client.json().arrpop('doc1', '$..a', '1') == ['"foo"', 'null', None] + + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] + + assert client.json().arrpop('doc1', '$..a', '-1') == [None, '"world"', None] + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["hello"]}, "nested2": {"a": 31}}] + + # Test single + assert client.json().arrpop('doc1', '$.nested1.a', -2) == ['"hello"'] + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().arrpop('non_existing_doc', '..a') + + # Test legacy + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi (all paths are updated, but return result of last path) + client.json().arrpop('doc1', '..a', '1') is None + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] + + # Test single + assert client.json().arrpop('doc1', '.nested1.a', -2, '"baz"') == '"hello"' + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().arrpop('non_existing_doc', '..a') + +@pytest.mark.redismod +def test_arrtrim_dollar(client): + + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + # Test multi + assert client.json().arrtrim('doc1', '$..a', '1', -1) == [0, 2, None] + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}}] + + assert client.json().arrtrim('doc1', '$..a', '1', '1') == [0, 1, None] + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] + # Test single + assert client.json().arrtrim('doc1', '$.nested1.a', 1, 0) == [0] + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().arrtrim('non_existing_doc', '..a', '0') + + # Test legacy + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + + # Test multi (all paths are updated, but return result of last path) + assert client.json().arrtrim('doc1', '..a', '1', '-1') == 2 + res = r.execute_command('JSON.GET', 'doc1', '$') == \ + [{"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}}] + # Test single + assert client.json().arrtrim('doc1', '.nested1.a', '1', '1') == 1 + assert client.json().get('doc1', '$') == \ + [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().arrtrim('non_existing_doc', '..a') + +@pytest.mark.redismod +def test_objkeys_dollar(client): + client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) + + # Test multi + assert client.json().objkeys('doc1', '$..a') == [["foo", "bar"], None, ["baz"]] + + # Test single + assert client.json().object('doc1', '$.nested1.a') == [["foo", "bar"]] + + # Test legacy + assert client.json().objkeys('doc1', '.*.a') == ["foo", "bar"] + # Test single + assert client.json().objkeys('doc1', '.nested2.a') == ["baz"] + + # Test missing key + assert client.json().objkeys('non_existing_doc', '..a') is None + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().objkeys('doc1', '$.nowhere') + +@pytest.mark.redismod +def test_objlen_dollar(client): + client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) + # Test multi + assert client.json().objlen('doc1', '$..a') == [2, None, 1] + # Test single + assert client.json().objlen('doc1', '$.nested1.a') == [2] + + # Test missing key + assert client.json().objlen('non_existing_doc', '$..a') is None + + # Test missing path + with pytest.raises(exceptions.DataError): + client.json().objlen('doc1', '$.nowhere') + + + # Test legacy + assert client.json().objlen('doc1', '.*.a') == 2 + + # Test single + assert client.json().objlen('doc1', '.nested2.a') == 1 + + # Test missing key + assert client.json.objlen('non_existing_doc', '..a') is None + + # Test missing path + with pytest.raises(exceptions.DataError): + client.json().objlen('doc1', '.nowhere') + +@pytest.mark.redismod +def load_types_data(nested_key_name): + types_data = { + 'object': {}, + 'array': [], + 'string': 'str', + 'integer': 42, + 'number': 1.2, + 'boolean': False, + 'null': None, + + } + jdata = {} + types = [] + for i, (k, v) in zip(range(1, len(types_data) + 1), iter(types_data.items())): + jdata["nested" + str(i)] = {nested_key_name: v} + types.append(k) + + return jdata, types + +@pytest.mark.redismod +def test_type_dollar(client): + jdata, jtypes = load_types_data('a') + client.json().set('doc1', '$', jdata) + # Test multi + assert client.json().type('JSON.TYPE', 'doc1', '$..a') == jtypes + + # Test single + assert client.json().type('doc1', '$.nested2.a') == [jtypes[1]] + + # Test legacy + assert client.json().type('doc1', '..a') == jtypes[0] + # Test missing path (defaults to root) + assert client.json().type('doc1') == 'object' + + # Test missing key + assert client.json().type('non_existing_doc', '..a') is None + +@pytest.mark.redismod +def test_clear_dollar(client): + + client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz":50}}}) + # Test multi + assert client.json().clear('doc1', '$..a') == 3 + + assert client.json().get('doc1', '$') == \ + [{"nested1": {"a": {}}, "a": [], "nested2": {"a": "claro"}, "nested3": {"a": {}}}] + + # Test single + client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz":50}}}) + assert client.json().clear('doc1', '$.nested1.a') == 1 + assert client.json().get('doc1', '$') == \ + [{"nested1": {"a": {}}, "a": ["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz": 50}}}] + + # Test missing path (defaults to root) + assert client.json().clear('doc1') == 1 + assert client.json().get('doc1', '$') == [{}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().clear('non_existing_doc', '$..a') + +@pytest.mark.redismod +def test_toggle_dollar(client): + client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": False}, "nested2": {"a": 31}, "nested3": {"a": True}}) + # Test multi + assert client.json().toggle('doc1', '$..a') == [None, 1, None, 0] + assert client.json().get('doc1', '$') == \ + [{"a": ["foo"], "nested1": {"a": True}, "nested2": {"a": 31}, "nested3": {"a": False}}] + + # Test single + assert client.json().toggle('doc1', '$.nested1.a') == [0] + assert client.json().get('JSON.GET', 'doc1', '$') == \ + [{"a": ["foo"], "nested1": {"a": False}, "nested2": {"a": 31}, "nested3": {"a": False}}] + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().toggle('non_existing_doc', '$..a') + +@pytest.mark.redismod +def test_debug_dollar(client): + + jdata, jtypes = load_types_data('a') + + client.json().set('doc1', '$', jdata) + + # Test multi + assert client.json().debug('MEMORY', 'doc1', '$..a') == \ + [72, 24, 24, 16, 16, 1, 0] + + # Test single + assert client.json().debug('MEMORY', 'doc1', '$.nested2.a') == [24] + + # Test legacy + assert client.json().debug('MEMORY', 'doc1', '..a') == 72 + + # Test missing path (defaults to root) + assert client.json().debug('MEMORY', 'doc1') == 72 + + # Test missing key + with pytest.raises(exceptions.DataError): + client.json().debug('non_existing_doc', '$..a') \ No newline at end of file diff --git a/tests/testdata/jsontestdata.py b/tests/testdata/jsontestdata.py new file mode 100644 index 0000000000..744fe21535 --- /dev/null +++ b/tests/testdata/jsontestdata.py @@ -0,0 +1,617 @@ +nested_large_key = r""" +{ + "jkra": [ + 154, + 4472, + [ + 8567, + false, + 363.84, + 5276, + "ha", + "rizkzs", + 93 + ], + false + ], + "hh": 20.77, + "mr": 973.217, + "ihbe": [ + 68, + [ + true, + { + "lqe": [ + 486.363, + [ + true, + { + "mp": { + "ory": "rj", + "qnl": "tyfrju", + "hf": null + }, + "uooc": 7418, + "xela": 20, + "bt": 7014, + "ia": 547, + "szec": 68.73 + }, + null + ], + 3622, + "iwk", + null + ], + "fepi": 19.954, + "ivu": { + "rmnd": 65.539, + "bk": 98, + "nc": "bdg", + "dlb": { + "hw": { + "upzz": [ + true, + { + "nwb": [ + 4259.47 + ], + "nbt": "yl" + }, + false, + false, + 65, + [ + [ + [], + 629.149, + "lvynqh", + "hsk", + [], + 2011.932, + true, + [] + ], + null, + "ymbc", + null + ], + "aj", + 97.425, + "hc", + 58 + ] + }, + "jq": true, + "bi": 3333, + "hmf": "pl", + "mrbj": [ + true, + false + ] + } + }, + "hfj": "lwk", + "utdl": "aku", + "alqb": [ + 74, + 534.389, + 7235, + [ + null, + false, + null + ] + ] + }, + null, + { + "lbrx": { + "vm": "ubdrbb" + }, + "tie": "iok", + "br": "ojro" + }, + 70.558, + [ + { + "mmo": null, + "dryu": null + } + ] + ], + true, + null, + false, + { + "jqun": 98, + "ivhq": [ + [ + [ + 675.936, + [ + 520.15, + 1587.4, + false + ], + "jt", + true, + { + "bn": null, + "ygn": "cve", + "zhh": true, + "aak": 9165, + "skx": true, + "qqsk": 662.28 + }, + { + "eio": 9933.6, + "agl": null, + "pf": false, + "kv": 5099.631, + "no": null, + "shly": 58 + }, + [ + null, + [ + "uiundu", + 726.652, + false, + 94.92, + 259.62, + { + "ntqu": null, + "frv": null, + "rvop": "upefj", + "jvdp": { + "nhx": [], + "bxnu": {}, + "gs": null, + "mqho": null, + "xp": 65, + "ujj": {} + }, + "ts": false, + "kyuk": [ + false, + 58, + {}, + "khqqif" + ] + }, + 167, + true, + "bhlej", + 53 + ], + 64, + { + "eans": "wgzfo", + "zfgb": 431.67, + "udy": [ + { + "gnt": [], + "zeve": {} + }, + { + "pg": {}, + "vsuc": {}, + "dw": 19, + "ffo": "uwsh", + "spk": "pjdyam", + "mc": [], + "wunb": {}, + "qcze": 2271.15, + "mcqx": null + }, + "qob" + ], + "wo": "zy" + }, + { + "dok": null, + "ygk": null, + "afdw": [ + 7848, + "ah", + null + ], + "foobar": 3.141592, + "wnuo": { + "zpvi": { + "stw": true, + "bq": {}, + "zord": true, + "omne": 3061.73, + "bnwm": "wuuyy", + "tuv": 7053, + "lepv": null, + "xap": 94.26 + }, + "nuv": false, + "hhza": 539.615, + "rqw": { + "dk": 2305, + "wibo": 7512.9, + "ytbc": 153, + "pokp": null, + "whzd": null, + "judg": [], + "zh": null + }, + "bcnu": "ji", + "yhqu": null, + "gwc": true, + "smp": { + "fxpl": 75, + "gc": [], + "vx": 9352.895, + "fbzf": 4138.27, + "tiaq": 354.306, + "kmfb": {}, + "fxhy": [], + "af": 94.46, + "wg": {}, + "fb": null + } + }, + "zvym": 2921, + "hhlh": [ + 45, + 214.345 + ], + "vv": "gqjoz" + }, + [ + "uxlu", + null, + "utl", + 64, + [ + 2695 + ], + [ + false, + null, + [ + "cfcrl", + [], + [], + 562, + 1654.9, + {}, + null, + "sqzud", + 934.6 + ], + { + "hk": true, + "ed": "lodube", + "ye": "ziwddj", + "ps": null, + "ir": {}, + "heh": false + }, + true, + 719, + 50.56, + [ + 99, + 6409, + null, + 4886, + "esdtkt", + {}, + null + ], + [ + false, + "bkzqw" + ] + ], + null, + 6357 + ], + { + "asvv": 22.873, + "vqm": { + "drmv": 68.12, + "tmf": 140.495, + "le": null, + "sanf": [ + true, + [], + "vyawd", + false, + 76.496, + [], + "sdfpr", + 33.16, + "nrxy", + "antje" + ], + "yrkh": 662.426, + "vxj": true, + "sn": 314.382, + "eorg": null + }, + "bavq": [ + 21.18, + 8742.66, + { + "eq": "urnd" + }, + 56.63, + "fw", + [ + {}, + "pjtr", + null, + "apyemk", + [], + [], + false, + {} + ], + { + "ho": null, + "ir": 124, + "oevp": 159, + "xdrv": 6705, + "ff": [], + "sx": false + }, + true, + null, + true + ], + "zw": "qjqaap", + "hr": { + "xz": 32, + "mj": 8235.32, + "yrtv": null, + "jcz": "vnemxe", + "ywai": [ + null, + 564, + false, + "vbr", + 54.741 + ], + "vw": 82, + "wn": true, + "pav": true + }, + "vxa": 881 + }, + "bgt", + "vuzk", + 857 + ] + ] + ], + null, + null, + { + "xyzl": "nvfff" + }, + true, + 13 + ], + "npd": null, + "ha": [ + [ + "du", + [ + 980, + { + "zdhd": [ + 129.986, + [ + "liehns", + 453, + { + "fuq": false, + "dxpn": {}, + "hmpx": 49, + "zb": "gbpt", + "vdqc": null, + "ysjg": false, + "gug": 7990.66 + }, + "evek", + [ + {} + ], + "dfywcu", + 9686, + null + ] + ], + "gpi": { + "gt": { + "qe": 7460, + "nh": "nrn", + "czj": 66.609, + "jwd": true, + "rb": "azwwe", + "fj": { + "csn": true, + "foobar": 1.61803398875, + "hm": "efsgw", + "zn": "vbpizt", + "tjo": 138.15, + "teo": {}, + "hecf": [], + "ls": false + } + }, + "xlc": 7916, + "jqst": 48.166, + "zj": "ivctu" + }, + "jl": 369.27, + "mxkx": null, + "sh": [ + true, + 373, + false, + "sdis", + 6217, + { + "ernm": null, + "srbo": 90.798, + "py": 677, + "jgrq": null, + "zujl": null, + "odsm": { + "pfrd": null, + "kwz": "kfvjzb", + "ptkp": false, + "pu": null, + "xty": null, + "ntx": [], + "nq": 48.19, + "lpyx": [] + }, + "ff": null, + "rvi": [ + "ych", + {}, + 72, + 9379, + 7897.383, + true, + {}, + 999.751, + false + ] + }, + true + ], + "ghe": [ + 24, + { + "lpr": true, + "qrs": true + }, + true, + false, + 7951.94, + true, + 2690.54, + [ + 93, + null, + null, + "rlz", + true, + "ky", + true + ] + ], + "vet": false, + "olle": null + }, + "jzm", + true + ], + null, + null, + 19.17, + 7145, + "ipsmk" + ], + false, + { + "du": 6550.959, + "sps": 8783.62, + "nblr": { + "dko": 9856.616, + "lz": { + "phng": "dj" + }, + "zeu": 766, + "tn": "dkr" + }, + "xa": "trdw", + "gn": 9875.687, + "dl": null, + "vuql": null + }, + { + "qpjo": null, + "das": { + "or": { + "xfy": null, + "xwvs": 4181.86, + "yj": 206.325, + "bsr": [ + "qrtsh" + ], + "wndm": { + "ve": 56, + "jyqa": true, + "ca": null + }, + "rpd": 9906, + "ea": "dvzcyt" + }, + "xwnn": 9272, + "rpx": "zpr", + "srzg": { + "beo": 325.6, + "sq": null, + "yf": null, + "nu": [ + 377, + "qda", + true + ], + "sfz": "zjk" + }, + "kh": "xnpj", + "rk": null, + "hzhn": [ + null + ], + "uio": 6249.12, + "nxrv": 1931.635, + "pd": null + }, + "pxlc": true, + "mjer": false, + "hdev": "msr", + "er": null + }, + "ug", + null, + "yrfoix", + 503.89, + 563 + ], + "tcy": 300, + "me": 459.17, + "tm": [ + 134.761, + "jcoels", + null + ], + "iig": 945.57, + "ad": "be" + }, + "ltpdm", + null, + 14.53 + ], + "xi": "gxzzs", + "zfpw": 1564.87, + "ow": null, + "tm": [ + 46, + 876.85 + ], + "xejv": null +} +""" \ No newline at end of file From 3741313e0ea90edf56239330c4aa8f02e351db44 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Mon, 1 Nov 2021 13:50:00 +0200 Subject: [PATCH 3/9] fixing more json tests --- redis/commands/json/__init__.py | 16 ++- redis/commands/json/commands.py | 6 +- redis/commands/json/decoders.py | 12 ++ tests/test_json.py | 207 +++++++++++++++++++++++++------- tests/testdata/jsontestdata.py | 154 ++++++++++++------------ 5 files changed, 265 insertions(+), 130 deletions(-) create mode 100644 redis/commands/json/decoders.py diff --git a/redis/commands/json/__init__.py b/redis/commands/json/__init__.py index 978370553a..ead0e7731d 100644 --- a/redis/commands/json/__init__.py +++ b/redis/commands/json/__init__.py @@ -1,6 +1,10 @@ from json import JSONDecoder, JSONEncoder from .helpers import bulk_of_jsons +from .decoders import ( + decode_list_or_int, + decode_toggle +) from ..helpers import nativestr, delist from .commands import JSONCommands @@ -42,9 +46,9 @@ def __init__( "JSON.SET": lambda r: r and nativestr(r) == "OK", "JSON.NUMINCRBY": self._decode, "JSON.NUMMULTBY": self._decode, - "JSON.TOGGLE": lambda b: b == b"true", + "JSON.TOGGLE": decode_toggle, "JSON.STRAPPEND": int, - "JSON.STRLEN": int, + "JSON.STRLEN": decode_list_or_int, "JSON.ARRAPPEND": int, "JSON.ARRINDEX": int, "JSON.ARRINSERT": int, @@ -54,7 +58,6 @@ def __init__( "JSON.OBJLEN": int, "JSON.OBJKEYS": delist, # "JSON.RESP": delist, - "JSON.DEBUG": int, } self.client = client @@ -73,9 +76,14 @@ def _decode(self, obj): return obj try: - return self.__decoder__.decode(obj) + x = self.__decoder__.decode(obj) + if x is None: + raise TypeError except TypeError: return self.__decoder__.decode(obj.decode()) + finally: + import json + return json.loads(obj.decode()) def _encode(self, obj): """Get the encoder.""" diff --git a/redis/commands/json/commands.py b/redis/commands/json/commands.py index 2f8039f8bf..cccb55e541 100644 --- a/redis/commands/json/commands.py +++ b/redis/commands/json/commands.py @@ -83,7 +83,7 @@ def numincrby(self, name, path, number): ``path`` at key ``name`` by the provided ``number``. """ return self.execute_command( - "JSON.NUMINCRBY", name, str_path(path), self._encode(number) + "JSON.NUMINCRBY", name, str_path(path), str(number) ) def nummultby(self, name, path, number): @@ -91,7 +91,7 @@ def nummultby(self, name, path, number): ``path`` at key ``name`` with the provided ``number``. """ return self.execute_command( - "JSON.NUMMULTBY", name, str_path(path), self._encode(number) + "JSON.NUMMULTBY", name, str_path(path), str(number) ) def clear(self, name, path=Path.rootPath()): @@ -189,7 +189,7 @@ def strappend(self, name, string, path=Path.rootPath()): "JSON.STRAPPEND", name, str_path(path), self._encode(string) ) - def debug(self, name, path=Path.rootPath()): + def debug(self, name, path=Path.rootPath()): """Return the memory usage in bytes of a value under ``path`` from key ``name``. """ diff --git a/redis/commands/json/decoders.py b/redis/commands/json/decoders.py new file mode 100644 index 0000000000..d08b620cda --- /dev/null +++ b/redis/commands/json/decoders.py @@ -0,0 +1,12 @@ +from ..helpers import delist + + +def decode_toggle(b): + if isinstance(b, list): + return b + return b == b"true" + +def decode_list_or_int(b): + if isinstance(b, list): + return b + return int(b) \ No newline at end of file diff --git a/tests/test_json.py b/tests/test_json.py index 3c89ae3458..48bbf8e096 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -312,8 +312,8 @@ def test_json_forget_with_dollar(client): @pytest.mark.redismod def test_set_and_get_with_dollar(client): # Test set and get on large nested key - assert client.json().set("doc1", "$", nested_large_key, "XX") is None - assert client.json().set("doc1", "$", nested_large_key, "NX") + client.json().set("doc1", "$", nested_large_key, "XX") + client.json().set("doc1", "$", nested_large_key, "NX") assert client.json().get('doc1', '$') == [nested_large_key] assert client.json().set("doc1", "$", nested_large_key, "NX") is None @@ -385,59 +385,42 @@ def test_json_mget_dollar(client): assert res == [None, None] @pytest.mark.redismod -def test_numby_commands_dollar(env): - - r = env +def test_numby_commands_dollar(client): # Test NUMINCRBY client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) # Test multi - assert client.json().numincrby('doc1', '$..a', '2') == [None, 4, 7.0, None] + assert client.json().numincrby('doc1', '$..a', 2) == [None, 4, 7.0, None] - assert client.json().numincrby('doc1', '$..a', '2.5') == [None, 6.5, 9.5, None] + assert client.json().numincrby('doc1', '$..a', 2.5) == [None, 6.5, 9.5, None] # Test single - assert client.json().numincrby('doc1', '$.b[1].a', '2') == [11.5] + assert client.json().numincrby('doc1', '$.b[1].a', 2) == [11.5] - assert client.json().numincrby('doc1', '$.b[2].a', '2') == [None] - assert client.json().numincrby('doc1', '$.b[1].a', '3.5') == [15.0] + assert client.json().numincrby('doc1', '$.b[2].a', 2) == [None] + assert client.json().numincrby('doc1', '$.b[1].a', 3.5) == [15.0] # Test NUMMULTBY client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - assert client.json().nummultby('doc1', '$..a', '2') == [None, 4, 10, None] - assert client.json().nummultby('doc1', '$..a', '2.5') == [None,10.0,25.0,None] + assert client.json().nummultby('doc1', '$..a', 2) == [None, 4, 10, None] + assert client.json().nummultby('doc1', '$..a', 2.5) == [None,10.0,25.0,None] # Test single - assert client.json().nummultby('doc1', '$.b[1].a', '2') == [50.0] - assert client.json().nummultby('doc1', '$.b[2].a', '2') == [None] - assert client.json().nummultby('doc1', '$.b[1].a', '3') == [150.0] + assert client.json().nummultby('doc1', '$.b[1].a', 2) == [50.0] + assert client.json().nummultby('doc1', '$.b[2].a', 2) == [None] + assert client.json().nummultby('doc1', '$.b[1].a', 3) == [150.0] - # Test NUMPOWBY - client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - # Test multi - assert client.json().numpowby('doc1', '$..a', '2') == [None, 4, 25, None] - # Avoid json.loads to verify the underlying type (integer/float) - assert client.json().numpowby('doc1', '$..a', '2') == [None,16,625.0,None] - - # Test single - assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[1].a', '2') == [390625.0] - assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[2].a', '2') == [None] - assert client.json().numpowby('JSON.NUMPOWBY', 'doc1', '$.b[1].a', '3') == [5.960464477539062e16] - - # Test missing key - with pytest.raises(exceptions.DataError): - client.json().numincrby('non_existing_doc', '$..a', '2') - client.json().nummultby('non_existing_doc', '$..a', '2') - - # TODO fixme - r.expect('JSON.NUMPOWBY', 'non_existing_doc', '$..a', '2') + # test missing keys + with pytest.raises(exceptions.ResponseError): + client.json().numincrby('non_existing_doc', '$..a', 2) + client.json().nummultby('non_existing_doc', '$..a', 2) # Test legacy NUMINCRBY client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - client.json().numincrby('doc1', '.b[0].a', '3') == 5 + client.json().numincrby('doc1', '.b[0].a', 3) == 5 # Test legacy NUMMULTBY client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - client.json().nummultby('doc1', '.b[0].a', '3') == 6 + client.json().nummultby('doc1', '.b[0].a', 3) == 6 @pytest.mark.redismod def test_strappend_dollar(client): @@ -470,7 +453,7 @@ def test_strlen_dollar(client): # Test multi client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) - res1 = client.json().strlen('doc1', '$..a') [3, 5, None] + assert client.json().strlen('doc1', '$..a') == [3, 5, None] res2 = client.json().strappend('doc1', '$..a', '"bar"') == [6, 8, None] res1 = client.json().strlen('doc1', '$..a') @@ -481,7 +464,7 @@ def test_strlen_dollar(client): client.json().strlen('doc1', '$.nested2.a') == [None] # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().strlen('non_existing_doc', '$..a') @pytest.mark.redismod @@ -575,7 +558,7 @@ def test_arrlen_dollar(client): assert client.json().arrappend('doc1', '..a', '"non"', '"abba"', '"stanza"') == 6 # Test single - assert client.json().arrlen('doc1', '.nested1.a') = 6 + assert client.json().arrlen('doc1', '.nested1.a') == 6 # Test missing key assert client.json().arrlen('non_existing_doc', '..a') is None @@ -584,7 +567,7 @@ def test_arrlen_dollar(client): def test_arrpop_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi - assert client.json().arrpop('doc1', '$..a', '1') == ['"foo"', 'null', None] + assert client.json().arrpop('doc1', '$..a', '1') == ['"foo"', 'None', None] assert client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] @@ -731,7 +714,7 @@ def test_type_dollar(client): jdata, jtypes = load_types_data('a') client.json().set('doc1', '$', jdata) # Test multi - assert client.json().type('JSON.TYPE', 'doc1', '$..a') == jtypes + assert client.json().type('doc1', '$..a') == jtypes # Test single assert client.json().type('doc1', '$.nested2.a') == [jtypes[1]] @@ -793,18 +776,150 @@ def test_debug_dollar(client): client.json().set('doc1', '$', jdata) # Test multi - assert client.json().debug('MEMORY', 'doc1', '$..a') == \ + assert client.json().debug('doc1', '$..a') == \ [72, 24, 24, 16, 16, 1, 0] # Test single - assert client.json().debug('MEMORY', 'doc1', '$.nested2.a') == [24] + assert client.json().debug('doc1', '$.nested2.a') == [24] # Test legacy - assert client.json().debug('MEMORY', 'doc1', '..a') == 72 + assert client.json().debug('doc1', '..a') == 72 # Test missing path (defaults to root) - assert client.json().debug('MEMORY', 'doc1') == 72 + assert client.json().debug('doc1') == 72 # Test missing key + assert client.json().debug('non_existing_doc', '$..a') == [] + +def test_resp_dollar(client): + + data = { + 'L1': { + 'a': { + 'A1_B1': 10, + 'A1_B2': False, + 'A1_B3': { + 'A1_B3_C1': None, + 'A1_B3_C2': [ 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', -19.5, 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', { + 'A1_B3_C2_D1_6_E1': True + } + ], + 'A1_B3_C3': [1] + }, + 'A1_B4': { + 'A1_B4_C1': "foo", + } + }, + }, + 'L2': { + 'a': { + 'A2_B1': 20, + 'A2_B2': False, + 'A2_B3': { + 'A2_B3_C1': None, + 'A2_B3_C2': [ 'A2_B3_C2_D1_1', 'A2_B3_C2_D1_2', -37.5, 'A2_B3_C2_D1_4', 'A2_B3_C2_D1_5', { + 'A2_B3_C2_D1_6_E1': False + } + ], + 'A2_B3_C3': [2] + }, + 'A2_B4': { + 'A2_B4_C1': "bar", + } + }, + }, + } + client.json().set('doc1', '$', data) + # Test multi + res = client.json().resp('doc1', '$..a') + assert res == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']], ['{', 'A2_B1', 20, 'A2_B2', 'false', 'A2_B3', ['{', 'A2_B3_C1', None, 'A2_B3_C2', ['[', 'A2_B3_C2_D1_1', 'A2_B3_C2_D1_2', '-37.5', 'A2_B3_C2_D1_4', 'A2_B3_C2_D1_5', ['{', 'A2_B3_C2_D1_6_E1', 'false']], 'A2_B3_C3', ['[', 2]], 'A2_B4', ['{', 'A2_B4_C1', 'bar']]] + + # Test single + resSingle = client.json().resp('doc1', '$.L1.a') + assert resSingle == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']]] + + # Test missing path + with pytest.raises(exceptions.DataError): + client.json().resp('doc1', '$.nowhere') + + # Test missing key + assert client.json().resp('non_existing_doc', '$..a') is None + + # Test legacy + assert client.json().resp('doc1', '.L1.a') == resSingle + +def test_arrindex_dollar(client): + + client.json().set( + 'store', + '$', + {"store":{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95,"size":[10,20,30,40]},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99,"size":[50,60,70,80]},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99,"size":[5,10,20,30]},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99,"size":[5,6,7,8]}],"bicycle":{"color":"red","price":19.95}}}) + + assert client.json().get( + 'store', + '$.store.book[?(@.price<10)].size') == [[10,20,30,40],[5,10,20,30]] + assert client.json().arrindex( + 'store', + '$.store.book[?(@.price<10)].size', + '20') == [1, 2] + + # Test index of int scalar in multi values + client.json().set('test_num', + '.', + [{"arr":[0,1,3.0,3,2,1,0,3]},{"nested1_found":{"arr":[5,4,3,2,1,0,1,2,3.0,2,4,5]}},{"nested2_not_found":{"arr":[2,4,6]}},{"nested3_scalar":{"arr":"3"}},[{"nested41_not_arr":{"arr_renamed":[1,2,3]}},{"nested42_empty_arr":{"arr":[]}}]]) + + assert client.json().get('test_num', '$..arr') == \ + [[0,1,3.0,3,2,1,0,3],[5,4,3,2,1,0,1,2,3.0,2,4,5],[2,4,6],"3",[]] + + assert client.json().arrindex('test_num', '$..arr', 3) == [3, 2, -1, None, -1] + + # Test index of double scalar in multi values + assert client.json().arrindex('test_num', '$..arr', 3.0) == [2, 8, -1, None, -1] + + # Test index of string scalar in multi values + client.json().set('test_string', + '.', + [{"arr":["bazzz","bar",2,"baz",2,"ba","baz",3]},{"nested1_found":{"arr":[None,"baz2","buzz",2,1,0,1,"2","baz",2,4,5]}},{"nested2_not_found":{"arr":["baz2",4,6]}},{"nested3_scalar":{"arr":"3"}},[{"nested41_arr":{"arr_renamed":[1,"baz",3]}},{"nested42_empty_arr":{"arr":[]}}]]) + assert client.json().get('test_string', '$..arr') == \ + [["bazzz","bar",2,"baz",2,"ba","baz",3],[None,"baz2","buzz",2,1,0,1,"2","baz",2,4,5],["baz2",4,6],"3",[]] + + assert client.json().arrindex('test_string', '$..arr', '"baz"') == [3, 8, -1, None, -1] + + assert client.json().arrindex('test_string', '$..arr', '"baz"', 2) == [3, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '"baz"', 4) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '"baz"', -5) == [3, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, 7) == [6, -1, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, -1) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, 0) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '5', 7, -1) == [-1, -1, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '5', 7, 0) == [-1, 11, -1, None, -1] + + # Test index of None scalar in multi values + client.json().set('test_None', + '.', + [{"arr":["bazzz","None",2,None,2,"ba","baz",3]},{"nested1_found":{"arr":["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5]}},{"nested2_not_found":{"arr":["None",4,6]}},{"nested3_scalar":{"arr":None}},[{"nested41_arr":{"arr_renamed":[1,None,3]}},{"nested42_empty_arr":{"arr":[]}}]]) + assert client.json.get('test_None', '$..arr') == \ + [["bazzz","None",2,None,2,"ba","baz",3],["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5],["None",4,6],None,[]] + + assert client.json().arrindex('test_None', '$..arr', 'None') == [3, 8, -1, None, -1] + + # Fail with none-scalar value + with pytest.raises(exceptions.DataError): + client.json().arrindex('test_None', '$..nested42_empty_arr.arr', {"arr":[]}) + + # Do not fail with none-scalar value in legacy mode + assert client.json().arrindex('test_None', '.[4][1].nested42_empty_arr.arr', '{"arr":[]}') == -1 + + # Test legacy (path begins with dot) + # Test index of int scalar in single value + assert client.json().arrindex('test_num', '.[0].arr', 3) == 3 + assert client.json().arrindex('test_num', '.[0].arr', 9) == -1 + with pytest.raises(exceptions.DataError): - client.json().debug('non_existing_doc', '$..a') \ No newline at end of file + client.json().arrindex('test_num', '.[0].arr_not', 3) + # Test index of string scalar in single value + assert client.json().arrindex('test_string', '.[0].arr', '"baz"') == 3 + assert client.json().arrindex('test_string', '.[0].arr', '"faz"') == -1 + # Test index of None scalar in single value + assert client.json().arrindex('test_None', '.[0].arr', 'None') == 3 + assert client.json().arrindex('test_None', '..nested2_not_found.arr', 'None') == -1 diff --git a/tests/testdata/jsontestdata.py b/tests/testdata/jsontestdata.py index 744fe21535..32ca6affe4 100644 --- a/tests/testdata/jsontestdata.py +++ b/tests/testdata/jsontestdata.py @@ -29,7 +29,7 @@ "mp": { "ory": "rj", "qnl": "tyfrju", - "hf": null + "hf": None }, "uooc": 7418, "xela": 20, @@ -37,11 +37,11 @@ "ia": 547, "szec": 68.73 }, - null + None ], 3622, "iwk", - null + None ], "fepi": 19.954, "ivu": { @@ -72,9 +72,9 @@ true, [] ], - null, + None, "ymbc", - null + None ], "aj", 97.425, @@ -98,13 +98,13 @@ 534.389, 7235, [ - null, + None, false, - null + None ] ] }, - null, + None, { "lbrx": { "vm": "ubdrbb" @@ -115,13 +115,13 @@ 70.558, [ { - "mmo": null, - "dryu": null + "mmo": None, + "dryu": None } ] ], true, - null, + None, false, { "jqun": 98, @@ -137,7 +137,7 @@ "jt", true, { - "bn": null, + "bn": None, "ygn": "cve", "zhh": true, "aak": 9165, @@ -146,14 +146,14 @@ }, { "eio": 9933.6, - "agl": null, + "agl": None, "pf": false, "kv": 5099.631, - "no": null, + "no": None, "shly": 58 }, [ - null, + None, [ "uiundu", 726.652, @@ -161,14 +161,14 @@ 94.92, 259.62, { - "ntqu": null, - "frv": null, + "ntqu": None, + "frv": None, "rvop": "upefj", "jvdp": { "nhx": [], "bxnu": {}, - "gs": null, - "mqho": null, + "gs": None, + "mqho": None, "xp": 65, "ujj": {} }, @@ -203,19 +203,19 @@ "mc": [], "wunb": {}, "qcze": 2271.15, - "mcqx": null + "mcqx": None }, "qob" ], "wo": "zy" }, { - "dok": null, - "ygk": null, + "dok": None, + "ygk": None, "afdw": [ 7848, "ah", - null + None ], "foobar": 3.141592, "wnuo": { @@ -226,7 +226,7 @@ "omne": 3061.73, "bnwm": "wuuyy", "tuv": 7053, - "lepv": null, + "lepv": None, "xap": 94.26 }, "nuv": false, @@ -235,13 +235,13 @@ "dk": 2305, "wibo": 7512.9, "ytbc": 153, - "pokp": null, - "whzd": null, + "pokp": None, + "whzd": None, "judg": [], - "zh": null + "zh": None }, "bcnu": "ji", - "yhqu": null, + "yhqu": None, "gwc": true, "smp": { "fxpl": 75, @@ -253,7 +253,7 @@ "fxhy": [], "af": 94.46, "wg": {}, - "fb": null + "fb": None } }, "zvym": 2921, @@ -265,7 +265,7 @@ }, [ "uxlu", - null, + None, "utl", 64, [ @@ -273,7 +273,7 @@ ], [ false, - null, + None, [ "cfcrl", [], @@ -281,7 +281,7 @@ 562, 1654.9, {}, - null, + None, "sqzud", 934.6 ], @@ -289,7 +289,7 @@ "hk": true, "ed": "lodube", "ye": "ziwddj", - "ps": null, + "ps": None, "ir": {}, "heh": false }, @@ -299,18 +299,18 @@ [ 99, 6409, - null, + None, 4886, "esdtkt", {}, - null + None ], [ false, "bkzqw" ] ], - null, + None, 6357 ], { @@ -318,7 +318,7 @@ "vqm": { "drmv": 68.12, "tmf": 140.495, - "le": null, + "le": None, "sanf": [ true, [], @@ -334,7 +334,7 @@ "yrkh": 662.426, "vxj": true, "sn": 314.382, - "eorg": null + "eorg": None }, "bavq": [ 21.18, @@ -347,7 +347,7 @@ [ {}, "pjtr", - null, + None, "apyemk", [], [], @@ -355,7 +355,7 @@ {} ], { - "ho": null, + "ho": None, "ir": 124, "oevp": 159, "xdrv": 6705, @@ -363,17 +363,17 @@ "sx": false }, true, - null, + None, true ], "zw": "qjqaap", "hr": { "xz": 32, "mj": 8235.32, - "yrtv": null, + "yrtv": None, "jcz": "vnemxe", "ywai": [ - null, + None, 564, false, "vbr", @@ -391,15 +391,15 @@ ] ] ], - null, - null, + None, + None, { "xyzl": "nvfff" }, true, 13 ], - "npd": null, + "npd": None, "ha": [ [ "du", @@ -416,7 +416,7 @@ "dxpn": {}, "hmpx": 49, "zb": "gbpt", - "vdqc": null, + "vdqc": None, "ysjg": false, "gug": 7990.66 }, @@ -426,7 +426,7 @@ ], "dfywcu", 9686, - null + None ] ], "gpi": { @@ -452,7 +452,7 @@ "zj": "ivctu" }, "jl": 369.27, - "mxkx": null, + "mxkx": None, "sh": [ true, 373, @@ -460,22 +460,22 @@ "sdis", 6217, { - "ernm": null, + "ernm": None, "srbo": 90.798, "py": 677, - "jgrq": null, - "zujl": null, + "jgrq": None, + "zujl": None, "odsm": { - "pfrd": null, + "pfrd": None, "kwz": "kfvjzb", "ptkp": false, - "pu": null, - "xty": null, + "pu": None, + "xty": None, "ntx": [], "nq": 48.19, "lpyx": [] }, - "ff": null, + "ff": None, "rvi": [ "ych", {}, @@ -503,8 +503,8 @@ 2690.54, [ 93, - null, - null, + None, + None, "rlz", true, "ky", @@ -512,13 +512,13 @@ ] ], "vet": false, - "olle": null + "olle": None }, "jzm", true ], - null, - null, + None, + None, 19.17, 7145, "ipsmk" @@ -537,14 +537,14 @@ }, "xa": "trdw", "gn": 9875.687, - "dl": null, - "vuql": null + "dl": None, + "vuql": None }, { - "qpjo": null, + "qpjo": None, "das": { "or": { - "xfy": null, + "xfy": None, "xwvs": 4181.86, "yj": 206.325, "bsr": [ @@ -553,7 +553,7 @@ "wndm": { "ve": 56, "jyqa": true, - "ca": null + "ca": None }, "rpd": 9906, "ea": "dvzcyt" @@ -562,8 +562,8 @@ "rpx": "zpr", "srzg": { "beo": 325.6, - "sq": null, - "yf": null, + "sq": None, + "yf": None, "nu": [ 377, "qda", @@ -572,21 +572,21 @@ "sfz": "zjk" }, "kh": "xnpj", - "rk": null, + "rk": None, "hzhn": [ - null + None ], "uio": 6249.12, "nxrv": 1931.635, - "pd": null + "pd": None }, "pxlc": true, "mjer": false, "hdev": "msr", - "er": null + "er": None }, "ug", - null, + None, "yrfoix", 503.89, 563 @@ -596,22 +596,22 @@ "tm": [ 134.761, "jcoels", - null + None ], "iig": 945.57, "ad": "be" }, "ltpdm", - null, + None, 14.53 ], "xi": "gxzzs", "zfpw": 1564.87, - "ow": null, + "ow": None, "tm": [ 46, 876.85 ], - "xejv": null + "xejv": None } """ \ No newline at end of file From 2bf3fe98843a961849c33c4a5ed8af22f959dc61 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Tue, 2 Nov 2021 14:29:59 +0200 Subject: [PATCH 4/9] test fixes --- redis/commands/json/__init__.py | 21 +++++++++---------- redis/commands/json/decoders.py | 2 ++ tests/test_json.py | 36 ++++++++++++++++----------------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/redis/commands/json/__init__.py b/redis/commands/json/__init__.py index 17273838f4..6bc592f388 100644 --- a/redis/commands/json/__init__.py +++ b/redis/commands/json/__init__.py @@ -1,14 +1,11 @@ from json import JSONDecoder, JSONEncoder -from .decoders import ( - int_or_list, - int_or_none -) -from .helpers import bulk_of_jsons from .decoders import ( decode_list_or_int, - decode_toggle + decode_toggle, + int_or_none, ) +from .helpers import bulk_of_jsons from ..helpers import nativestr, delist from .commands import JSONCommands @@ -51,15 +48,15 @@ def __init__( "JSON.NUMINCRBY": self._decode, "JSON.NUMMULTBY": self._decode, "JSON.TOGGLE": decode_toggle, - "JSON.STRAPPEND": int, + "JSON.STRAPPEND": decode_list_or_int, "JSON.STRLEN": decode_list_or_int, - "JSON.ARRAPPEND": int, - "JSON.ARRINDEX": int, - "JSON.ARRINSERT": int, + "JSON.ARRAPPEND": decode_list_or_int, + "JSON.ARRINDEX": decode_list_or_int, + "JSON.ARRINSERT": decode_list_or_int, "JSON.ARRLEN": int_or_none, "JSON.ARRPOP": self._decode, - "JSON.ARRTRIM": int, - "JSON.OBJLEN": int, + "JSON.ARRTRIM": decode_list_or_int, + "JSON.OBJLEN": decode_list_or_int, "JSON.OBJKEYS": delist, # "JSON.RESP": delist, "JSON.DEBUG": decode_list_or_int, diff --git a/redis/commands/json/decoders.py b/redis/commands/json/decoders.py index fa85253d1b..4f46f0eaed 100644 --- a/redis/commands/json/decoders.py +++ b/redis/commands/json/decoders.py @@ -9,6 +9,8 @@ def decode_toggle(b): def decode_list_or_int(b): if isinstance(b, list): return b + if b is None: + return None return int(b) diff --git a/tests/test_json.py b/tests/test_json.py index 31516dcb92..1a39f06be6 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -430,11 +430,11 @@ def test_json_mget_dollar(client): # Test mget with single path client.json().mget('doc1', '$..a') == [1,3,None] # Test mget with multi path - client.json().mget('doc1', 'doc2', '$..a') == [[1,3,None], [4,6,[None]]] + client.json().mget(['doc1', 'doc2'], '$..a') == [[1,3,None], [4,6,[None]]] # Test missing key - client.json().mget('doc1', 'missing_doc', '$..a') == [[1,3,None], None] - res = client.json().mget('missing_doc1', 'missing_doc2', '$..a') + client.json().mget(['doc1', 'missing_doc'], '$..a') == [[1,3,None], None] + res = client.json().mget(['missing_doc1', 'missing_doc2'], '$..a') assert res == [None, None] @pytest.mark.redismod @@ -480,25 +480,25 @@ def test_strappend_dollar(client): client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) # Test multi - client.json().strappend('doc1', '$..a', '"bar"') == [6, 8, None] + client.json().strappend('doc1', '"bar"', '$..a') == [6, 8, None] client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobar"},"nested2":{"a":31}}] # Test single - client.json().strappend('doc1', '$.nested1.a', '"baz"') == [11] + client.json().strappend('doc1', '"baz"', '$.nested1.a') == [11] client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobarbaz"},"nested2":{"a":31}}] # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().strappend('non_existing_doc', '$..a', '"err"') # Test multi - client.json().strappend('doc1', '.*.a', '"bar"') == 8 - client.json.get('doc1', '$') == [{"a":"foo","nested1":{"a":"hellobar"},"nested2":{"a":31}}] + client.json().strappend('doc1', '"bar"', '.*.a') == 8 + client.json().get('doc1', '$') == [{"a":"foo","nested1":{"a":"hellobar"},"nested2":{"a":31}}] # Test missing path - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().strappend('doc1', '"piu"') @pytest.mark.redismod @@ -508,7 +508,7 @@ def test_strlen_dollar(client): client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) assert client.json().strlen('doc1', '$..a') == [3, 5, None] - res2 = client.json().strappend('doc1', '$..a', '"bar"') == [6, 8, None] + res2 = client.json().strappend('doc1', 'bar', '$..a') res1 = client.json().strlen('doc1', '$..a') assert res1 == res2 @@ -672,15 +672,15 @@ def test_arrtrim_dollar(client): [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): - client.json().arrtrim('non_existing_doc', '..a', '0') + with pytest.raises(exceptions.ResponseError): + client.json().arrtrim('non_existing_doc', '..a', '0', 1) # Test legacy client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi (all paths are updated, but return result of last path) assert client.json().arrtrim('doc1', '..a', '1', '-1') == 2 - res = r.execute_command('JSON.GET', 'doc1', '$') == \ + res = client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}}] # Test single assert client.json().arrtrim('doc1', '.nested1.a', '1', '1') == 1 @@ -688,7 +688,7 @@ def test_arrtrim_dollar(client): [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().arrtrim('non_existing_doc', '..a') @pytest.mark.redismod @@ -725,7 +725,7 @@ def test_objlen_dollar(client): assert client.json().objlen('non_existing_doc', '$..a') is None # Test missing path - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().objlen('doc1', '$.nowhere') @@ -736,10 +736,10 @@ def test_objlen_dollar(client): assert client.json().objlen('doc1', '.nested2.a') == 1 # Test missing key - assert client.json.objlen('non_existing_doc', '..a') is None + assert client.json().objlen('non_existing_doc', '..a') is None # Test missing path - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().objlen('doc1', '.nowhere') @pytest.mark.redismod @@ -801,7 +801,7 @@ def test_clear_dollar(client): assert client.json().get('doc1', '$') == [{}] # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().clear('non_existing_doc', '$..a') @pytest.mark.redismod From 235f0a45df3b61e5f77539413d463f36db894727 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Tue, 2 Nov 2021 14:31:22 +0200 Subject: [PATCH 5/9] fix arrtrim_dollar test --- 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 1a39f06be6..5cea4ab355 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -689,7 +689,7 @@ def test_arrtrim_dollar(client): # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrtrim('non_existing_doc', '..a') + client.json().arrtrim('non_existing_doc', '..a', 1, 1) @pytest.mark.redismod def test_objkeys_dollar(client): From e4d61be9fdc95f2c6a2a60f0c3c0ecd538027200 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Tue, 2 Nov 2021 14:59:08 +0200 Subject: [PATCH 6/9] reduced decoders to one --- redis/commands/json/__init__.py | 8 ++-- redis/commands/json/commands.py | 2 +- redis/commands/json/decoders.py | 19 ++------- tests/test_json.py | 71 ++++++++++++--------------------- 4 files changed, 34 insertions(+), 66 deletions(-) diff --git a/redis/commands/json/__init__.py b/redis/commands/json/__init__.py index 6bc592f388..7303e5d397 100644 --- a/redis/commands/json/__init__.py +++ b/redis/commands/json/__init__.py @@ -2,8 +2,6 @@ from .decoders import ( decode_list_or_int, - decode_toggle, - int_or_none, ) from .helpers import bulk_of_jsons from ..helpers import nativestr, delist @@ -47,14 +45,14 @@ def __init__( "JSON.SET": lambda r: r and nativestr(r) == "OK", "JSON.NUMINCRBY": self._decode, "JSON.NUMMULTBY": self._decode, - "JSON.TOGGLE": decode_toggle, + "JSON.TOGGLE": decode_list_or_int, "JSON.STRAPPEND": decode_list_or_int, "JSON.STRLEN": decode_list_or_int, "JSON.ARRAPPEND": decode_list_or_int, "JSON.ARRINDEX": decode_list_or_int, "JSON.ARRINSERT": decode_list_or_int, - "JSON.ARRLEN": int_or_none, - "JSON.ARRPOP": self._decode, + "JSON.ARRLEN": decode_list_or_int, + "JSON.ARRPOP": decode_list_or_int, "JSON.ARRTRIM": decode_list_or_int, "JSON.OBJLEN": decode_list_or_int, "JSON.OBJKEYS": delist, diff --git a/redis/commands/json/commands.py b/redis/commands/json/commands.py index fb00e220aa..a601f5ff86 100644 --- a/redis/commands/json/commands.py +++ b/redis/commands/json/commands.py @@ -192,7 +192,7 @@ def strappend(self, name, value, path=Path.rootPath()): the key name, the path is determined to be the first. If a single option is passed, then the rootpath (i.e Path.rootPath()) is used. """ - pieces = [name, str(path), value] + pieces = [name, str(path), self._encode(value)] return self.execute_command( "JSON.STRAPPEND", *pieces ) diff --git a/redis/commands/json/decoders.py b/redis/commands/json/decoders.py index 4f46f0eaed..5dcf50c53f 100644 --- a/redis/commands/json/decoders.py +++ b/redis/commands/json/decoders.py @@ -1,21 +1,10 @@ -from ..helpers import delist - - -def decode_toggle(b): - if isinstance(b, list): - return b - return b == b"true" - def decode_list_or_int(b): if isinstance(b, list): return b if b is None: return None + elif b == b"true": + return True + elif b == b"false": + return False return int(b) - - -def int_or_none(b): - if b is None: - return None - if isinstance(b, int): - return b diff --git a/tests/test_json.py b/tests/test_json.py index 5cea4ab355..ae6764dcc8 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -129,10 +129,7 @@ def test_toggle(client): @pytest.mark.redismod def test_strappend(client): client.json().set("jsonkey", Path.rootPath(), 'foo') - import json - assert 6 == client.json().strappend("jsonkey", json.dumps('bar')) - with pytest.raises(redis.exceptions.ResponseError): - assert 6 == client.json().strappend("jsonkey", 'bar') + assert 6 == client.json().strappend("jsonkey", 'bar') assert "foobar" == client.json().get("jsonkey", Path.rootPath()) @@ -150,8 +147,7 @@ def test_debug(client): def test_strlen(client): client.json().set("str", Path.rootPath(), "foo") assert 3 == client.json().strlen("str", Path.rootPath()) - import json - client.json().strappend("str", json.dumps("bar"), Path.rootPath()) + client.json().strappend("str", "bar", Path.rootPath()) assert 6 == client.json().strlen("str", Path.rootPath()) assert 6 == client.json().strlen("str") @@ -524,66 +520,52 @@ def test_strlen_dollar(client): def test_arrappend_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi - client.json().arrappend('doc1', '$..a', '"bar"', '"racuda"') == [3, 5, None] + client.json().arrappend('doc1', '$..a', 'bar', 'racuda') == [3, 5, None] assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] # Test single - assert client.json().arrappend('doc1', '$.nested1.a', '"baz"') == [6] + assert client.json().arrappend('doc1', '$.nested1.a', 'baz') == [6] assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): - client.json.arrappend('non_existing_doc', '$..a') + with pytest.raises(exceptions.ResponseError): + client.json().arrappend('non_existing_doc', '$..a') # Test legacy client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi (all paths are updated, but return result of last path) - assert client.json().arrappend('doc1', '..a', '"bar"', '"racuda"') == 5 + assert client.json().arrappend('doc1', '..a', 'bar', 'racuda') == 5 - assert client.json.get('doc1', '$') == \ + assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] # Test single - assert client.json().arrappend('doc1', '.nested1.a', '"baz"') == 6 + assert client.json().arrappend('doc1', '.nested1.a', 'baz') == 6 assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): - client.json.arrappend('non_existing_doc', '$..a') + with pytest.raises(exceptions.ResponseError): + client.json().arrappend('non_existing_doc', '$..a') @pytest.mark.redismod def test_arrinsert_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi - assert client.json().arrinsert('doc1', '$..a', '1', '"bar"', '"racuda"') == [3, 5, None] + assert client.json().arrinsert('doc1', '$..a', '1', 'bar', 'racuda') == [3, 5, None] assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, "nested2": {"a": 31}}] # Test single - assert client.json().arrinsert('doc1', '$.nested1.a', -2, '"baz"') == [6] + assert client.json().arrinsert('doc1', '$.nested1.a', -2, 'baz') == [6] assert client.json().get('doc1', '$') == \ [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): - client.json.arrappend('non_existing_doc', '$..a') - - # Test legacy - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) - assert client.json().arrappend('doc1', '..a', '1', '"bar"', '"racuda"') == 5 - - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, "nested2": {"a": 31}}] - # Test single - assert client.json().arrinsert('doc1', '.nested1.a', -2, '"baz"') == 6 - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, "nested2": {"a": 31}}] + with pytest.raises(exceptions.ResponseError): + client.json().arrappend('non_existing_doc', '$..a') - # Test missing key - with pytest.raises(exceptions.DataError): - client.json.arrinsert('non_existing_doc', '$..a') @pytest.mark.redismod def test_arrlen_dollar(client): @@ -596,15 +578,14 @@ def test_arrlen_dollar(client): [4, 6, None] client.json().clear('doc1', '$.a') - assert client.json.arrlen('doc1', '$..a') == [0, 6, None] + assert client.json().arrlen('doc1', '$..a') == [0, 6, None] # Test single assert client.json().arrlen('doc1', '$.nested1.a') == [6] # Test missing key - with pytest.raises(exceptions.DataError): - client.json.arrappend('non_existing_doc', '$..a') + with pytest.raises(exceptions.ResponseError): + client.json().arrappend('non_existing_doc', '$..a') - # Test legacy client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi (return result of last path) assert client.json().arrlen('doc1', '$..a') == [1, 3, None] @@ -620,17 +601,17 @@ def test_arrlen_dollar(client): def test_arrpop_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi - assert client.json().arrpop('doc1', '$..a', '1') == ['"foo"', 'None', None] + assert client.json().arrpop('doc1', '$..a', '1') == ['foo', None, None] assert client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] - assert client.json().arrpop('doc1', '$..a', '-1') == [None, '"world"', None] + assert client.json().arrpop('doc1', '$..a', '-1') == [None, 'world', None] assert client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": ["hello"]}, "nested2": {"a": 31}}] # Test single - assert client.json().arrpop('doc1', '$.nested1.a', -2) == ['"hello"'] + assert client.json().arrpop('doc1', '$.nested1.a', -2) == ['hello'] assert client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] @@ -829,20 +810,20 @@ def test_debug_dollar(client): client.json().set('doc1', '$', jdata) # Test multi - assert client.json().debug('doc1', '$..a') == \ + assert client.json().debug("MEMORY", 'doc1', '$..a') == \ [72, 24, 24, 16, 16, 1, 0] # Test single - assert client.json().debug('doc1', '$.nested2.a') == [24] + assert client.json().debug("MEMORY", 'doc1', '$.nested2.a') == [24] # Test legacy - assert client.json().debug('doc1', '..a') == 72 + assert client.json().debug("MEMORY", 'doc1', '..a') == 72 # Test missing path (defaults to root) - assert client.json().debug('doc1') == 72 + assert client.json().debug("MEMORY", 'doc1') == 72 # Test missing key - assert client.json().debug('non_existing_doc', '$..a') == [] + assert client.json().debug("MEMORY",'non_existing_doc', '$..a') == [] def test_resp_dollar(client): From f3e855bafb0b8c3f937225b7dfabe96ae43ac3d7 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 3 Nov 2021 12:09:01 +0200 Subject: [PATCH 7/9] passing tests --- redis/commands/helpers.py | 5 +- redis/commands/json/__init__.py | 45 ++++---- redis/commands/json/commands.py | 2 +- redis/commands/json/decoders.py | 67 +++++++++-- redis/commands/json/helpers.py | 25 ---- tests/conftest.py | 3 +- tests/test_json.py | 199 +++++++++++--------------------- tests/testdata/jsontestdata.py | 2 +- 8 files changed, 155 insertions(+), 193 deletions(-) delete mode 100644 redis/commands/json/helpers.py diff --git a/redis/commands/helpers.py b/redis/commands/helpers.py index 48ee5568e6..2a4298cb2f 100644 --- a/redis/commands/helpers.py +++ b/redis/commands/helpers.py @@ -17,7 +17,10 @@ def list_or_args(keys, args): def nativestr(x): """Return the decoded binary string, or a string, depending on type.""" - return x.decode("utf-8", "replace") if isinstance(x, bytes) else x + r = x.decode("utf-8", "replace") if isinstance(x, bytes) else x + if r == 'null': + return + return r def delist(x): diff --git a/redis/commands/json/__init__.py b/redis/commands/json/__init__.py index 7303e5d397..d00627efce 100644 --- a/redis/commands/json/__init__.py +++ b/redis/commands/json/__init__.py @@ -1,10 +1,10 @@ -from json import JSONDecoder, JSONEncoder +from json import JSONDecoder, JSONEncoder, JSONDecodeError from .decoders import ( - decode_list_or_int, + decode_list, + bulk_of_jsons, ) -from .helpers import bulk_of_jsons -from ..helpers import nativestr, delist +from ..helpers import nativestr from .commands import JSONCommands @@ -45,19 +45,19 @@ def __init__( "JSON.SET": lambda r: r and nativestr(r) == "OK", "JSON.NUMINCRBY": self._decode, "JSON.NUMMULTBY": self._decode, - "JSON.TOGGLE": decode_list_or_int, - "JSON.STRAPPEND": decode_list_or_int, - "JSON.STRLEN": decode_list_or_int, - "JSON.ARRAPPEND": decode_list_or_int, - "JSON.ARRINDEX": decode_list_or_int, - "JSON.ARRINSERT": decode_list_or_int, - "JSON.ARRLEN": decode_list_or_int, - "JSON.ARRPOP": decode_list_or_int, - "JSON.ARRTRIM": decode_list_or_int, - "JSON.OBJLEN": decode_list_or_int, - "JSON.OBJKEYS": delist, - # "JSON.RESP": delist, - "JSON.DEBUG": decode_list_or_int, + "JSON.TOGGLE": self._decode, + "JSON.STRAPPEND": self._decode, + "JSON.STRLEN": self._decode, + "JSON.ARRAPPEND": self._decode, + "JSON.ARRINDEX": self._decode, + "JSON.ARRINSERT": self._decode, + "JSON.ARRLEN": self._decode, + "JSON.ARRPOP": self._decode, + "JSON.ARRTRIM": self._decode, + "JSON.OBJLEN": self._decode, + "JSON.OBJKEYS": self._decode, + "JSON.RESP": self._decode, + "JSON.DEBUG": self._decode, } self.client = client @@ -79,11 +79,14 @@ def _decode(self, obj): x = self.__decoder__.decode(obj) if x is None: raise TypeError + return x except TypeError: - return self.__decoder__.decode(obj.decode()) - finally: - import json - return json.loads(obj.decode()) + try: + return self.__decoder__.decode(obj.decode()) + except AttributeError: + return decode_list(obj) + except (AttributeError, JSONDecodeError): + return decode_list(obj) def _encode(self, obj): """Get the encoder.""" diff --git a/redis/commands/json/commands.py b/redis/commands/json/commands.py index a601f5ff86..716741c19b 100644 --- a/redis/commands/json/commands.py +++ b/redis/commands/json/commands.py @@ -1,5 +1,5 @@ from .path import Path -from .helpers import decode_dict_keys +from .decoders import decode_dict_keys from deprecated import deprecated from redis.exceptions import DataError diff --git a/redis/commands/json/decoders.py b/redis/commands/json/decoders.py index 5dcf50c53f..ec71cee215 100644 --- a/redis/commands/json/decoders.py +++ b/redis/commands/json/decoders.py @@ -1,10 +1,59 @@ -def decode_list_or_int(b): - if isinstance(b, list): +from ..helpers import nativestr +import re +import copy + + +def bulk_of_jsons(d): + """Replace serialized JSON values with objects in a + bulk array response (list). + """ + + def _f(b): + for index, item in enumerate(b): + if item is not None: + b[index] = d(item) return b - if b is None: - return None - elif b == b"true": - return True - elif b == b"false": - return False - return int(b) + + return _f + + +def decode_dict_keys(obj): + """Decode the keys of the given dictionary with utf-8.""" + newobj = copy.copy(obj) + for k in obj.keys(): + if isinstance(k, bytes): + newobj[k.decode("utf-8")] = newobj[k] + newobj.pop(k) + return newobj + + +def unstring(obj): + """ + Attempt to parse string to native integer formats. + One can't simply call int/float in a try/catch because there is a + semantic difference between (for example) 15.0 and 15. + """ + floatreg = '^\\d+.\\d+$' + match = re.findall(floatreg, obj) + if match != []: + return float(match[0]) + + intreg = "^\\d+$" + match = re.findall(intreg, obj) + if match != []: + return int(match[0]) + return obj + + +def decode_list(b): + """ + Given a non-deserializable object, make a best effort to + return a useful set of results. + """ + if isinstance(b, list): + return [nativestr(obj) for obj in b] + elif isinstance(b, bytes): + return nativestr(b) + elif isinstance(b, str): + return unstring(b) + return b diff --git a/redis/commands/json/helpers.py b/redis/commands/json/helpers.py deleted file mode 100644 index 8fb20d9ac5..0000000000 --- a/redis/commands/json/helpers.py +++ /dev/null @@ -1,25 +0,0 @@ -import copy - - -def bulk_of_jsons(d): - """Replace serialized JSON values with objects in a - bulk array response (list). - """ - - def _f(b): - for index, item in enumerate(b): - if item is not None: - b[index] = d(item) - return b - - return _f - - -def decode_dict_keys(obj): - """Decode the keys of the given dictionary with utf-8.""" - newobj = copy.copy(obj) - for k in obj.keys(): - if isinstance(k, bytes): - newobj[k.decode("utf-8")] = newobj[k] - newobj.pop(k) - return newobj diff --git a/tests/conftest.py b/tests/conftest.py index 47188df07f..b0f14ca46a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -126,7 +126,8 @@ def teardown(): @pytest.fixture() def modclient(request, **kwargs): rmurl = request.config.getoption('--redismod-url') - with _get_client(redis.Redis, request, from_url=rmurl, **kwargs) as client: + with _get_client(redis.Redis, request, from_url=rmurl, + decode_responses=True, **kwargs) as client: yield client diff --git a/tests/test_json.py b/tests/test_json.py index ae6764dcc8..00c0c42208 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -31,7 +31,7 @@ def test_json_setgetdeleteforget(client): @pytest.mark.redismod -def test_justaget(client): +def test_jsonget(client): client.json().set("foo", Path.rootPath(), "bar") assert client.json().get("foo") == "bar" @@ -92,8 +92,8 @@ def test_clear(client): @pytest.mark.redismod def test_type(client): client.json().set("1", Path.rootPath(), 1) - assert b"integer" == client.json().type("1", Path.rootPath()) - assert b"integer" == client.json().type("1") + assert "integer" == client.json().type("1", Path.rootPath()) + assert "integer" == client.json().type("1") @pytest.mark.redismod @@ -119,7 +119,7 @@ def test_nummultby(client): def test_toggle(client): client.json().set("bool", Path.rootPath(), False) assert client.json().toggle("bool", Path.rootPath()) - assert not client.json().toggle("bool", Path.rootPath()) + assert client.json().toggle("bool", Path.rootPath()) is False # check non-boolean value client.json().set("num", Path.rootPath(), 1) with pytest.raises(redis.exceptions.ResponseError): @@ -241,7 +241,7 @@ def test_arrtrim(client): def test_resp(client): obj = {"foo": "bar", "baz": 1, "qaz": True} client.json().set("obj", Path.rootPath(), obj) - assert b"bar" == client.json().resp("obj", Path("foo")) + assert "bar" == client.json().resp("obj", Path("foo")) assert 1 == client.json().resp("obj", Path("baz")) assert client.json().resp("obj", Path("qaz")) assert isinstance(client.json().resp("obj"), list) @@ -297,16 +297,16 @@ def test_json_delete_with_dollar(client): assert r == [{"nested": {"b": 3}}] - doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], + doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b":[True, "a","b"]}} assert client.json().set('doc2', '$', doc2) assert client.json().delete("doc2", "$..a") == 1 res = client.json().get("doc2", "$") assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] - doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, - {"ciao":[2,"a"]}, - {"ciaoc":[3,"non","ciao"]}, + doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, + {"ciao":[2,"a"]}, + {"ciaoc":[3,"non","ciao"]}, {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] assert client.json().set('doc3', '$', doc3) assert client.json().delete('doc3', '$.[0]["nested"]..ciao') == 3 @@ -332,16 +332,16 @@ def test_json_forget_with_dollar(client): assert r == [{"nested": {"b": 3}}] - doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], + doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b":[True, "a","b"]}} assert client.json().set('doc2', '$', doc2) assert client.json().forget("doc2", "$..a") == 1 res = client.json().get("doc2", "$") assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] - doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, - {"ciao":[2,"a"]}, - {"ciaoc":[3,"non","ciao"]}, + doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, + {"ciao":[2,"a"]}, + {"ciaoc":[3,"non","ciao"]}, {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] assert client.json().set('doc3', '$', doc3) assert client.json().forget('doc3', '$.[0]["nested"]..ciao') == 3 @@ -358,61 +358,6 @@ def test_json_forget_with_dollar(client): client.json().forget("not_a_document", "..a") -@pytest.mark.redismod -def test_set_and_get_with_dollar(client): - # Test set and get on large nested key - client.json().set("doc1", "$", nested_large_key, "XX") - client.json().set("doc1", "$", nested_large_key, "NX") - assert client.json().get('doc1', '$') == [nested_large_key] - assert client.json().set("doc1", "$", nested_large_key, "NX") is None - - # Test single path - assert client.json().get('doc1', '$..tm') == [[46,876.85],[134.761,"jcoels",None]] - - # Test multi get and set - assert client.json().get('doc1', '$..foobar') == [3.141592,1.61803398875] - - # Set multi existing values - client.json().set('doc1', '$..foobar', '"new_val"') - assert client.json().get('doc1', '$..foobar') == ["new_val","new_val"] - - # Test multi set and get on small nested key - nested_simple_key = {"a":1,"nested":{"a":2,"b":3}} - client.json().set('doc2', '$', nested_simple_key) - assert client.json().get('doc2', '$') == [nested_simple_key] - # Set multi existing values - client.json().set('doc2', '$..a', '4.2') - assert client.json().get('doc2', '$') == \ - [{"a":4.2,"nested":{"a":4.2,"b":3}}] - - - # Test multi paths - assert client.json().get('doc1', '$..tm', '$..nu') == \ - [[[46,876.85],[134.761,"jcoels",None]],[[377,"qda",True]]] - # Test multi paths - if one path is none-legacy - result format is not legacy - assert client.json().get('doc1', '..tm', '$..nu') == \ - [[[46,876.85],[134.761,"jcoels",None]],[[377,"qda",True]]] - - # Test missing key - assert client.json().get('docX', '..tm', '$..nu') is None - # Test missing path - assert client.json().get('doc1', '..tm', '$..back_in_nov') == \ - [[[46,876.85],[134.761,"jcoels",None]],[]] - assert client.json().get('doc2', '..a', '..b', '$.back_in_nov') == \ - [[4.2,4.2],[3],[]] - - # Test legacy multi path (all paths are legacy) - client.json().get('doc1', '..nu', '..tm') == \ - {"..nu":[377,"qda",True],"..tm":[46,876.85]} - # Test legacy single path - client.json().get('doc1', '..tm') == '[46,876.85]' - - # Test missing legacy path (should return an error for a missing path) - client.json().set('doc2', '$.nested.b', None) - - with pytest.raises(exceptions.DataError): - client.json.get('doc2', '.a', '.nested.b', '.back_in_nov', '.ttyl') - client.json.get('JSON.GET', 'doc2', '.back_in_nov') @pytest.mark.redismod def test_json_mget_dollar(client): @@ -433,6 +378,7 @@ def test_json_mget_dollar(client): res = client.json().mget(['missing_doc1', 'missing_doc2'], '$..a') assert res == [None, None] + @pytest.mark.redismod def test_numby_commands_dollar(client): @@ -471,31 +417,33 @@ def test_numby_commands_dollar(client): client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) client.json().nummultby('doc1', '.b[0].a', 3) == 6 + @pytest.mark.redismod def test_strappend_dollar(client): client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) # Test multi - client.json().strappend('doc1', '"bar"', '$..a') == [6, 8, None] + client.json().strappend('doc1', 'bar', '$..a') == [6, 8, None] client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobar"},"nested2":{"a":31}}] # Test single - client.json().strappend('doc1', '"baz"', '$.nested1.a') == [11] + client.json().strappend('doc1', 'baz', '$.nested1.a') == [11] client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobarbaz"},"nested2":{"a":31}}] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().strappend('non_existing_doc', '$..a', '"err"') + client.json().strappend('non_existing_doc', '$..a', 'err') # Test multi - client.json().strappend('doc1', '"bar"', '.*.a') == 8 + client.json().strappend('doc1', 'bar', '.*.a') == 8 client.json().get('doc1', '$') == [{"a":"foo","nested1":{"a":"hellobar"},"nested2":{"a":31}}] # Test missing path with pytest.raises(exceptions.ResponseError): - client.json().strappend('doc1', '"piu"') + client.json().strappend('doc1', 'piu') + @pytest.mark.redismod def test_strlen_dollar(client): @@ -516,6 +464,7 @@ def test_strlen_dollar(client): with pytest.raises(exceptions.ResponseError): client.json().strlen('non_existing_doc', '$..a') + @pytest.mark.redismod def test_arrappend_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) @@ -549,6 +498,7 @@ def test_arrappend_dollar(client): with pytest.raises(exceptions.ResponseError): client.json().arrappend('non_existing_doc', '$..a') + @pytest.mark.redismod def test_arrinsert_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) @@ -574,7 +524,7 @@ def test_arrlen_dollar(client): # Test multi assert client.json().arrlen('doc1', '$..a') == [1, 3, None] - assert client.json().arrappend('doc1', '$..a', '"non"', '"abba"', '"stanza"') == \ + assert client.json().arrappend('doc1', '$..a', 'non', 'abba', 'stanza') == \ [4, 6, None] client.json().clear('doc1', '$.a') @@ -589,7 +539,7 @@ def test_arrlen_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi (return result of last path) assert client.json().arrlen('doc1', '$..a') == [1, 3, None] - assert client.json().arrappend('doc1', '..a', '"non"', '"abba"', '"stanza"') == 6 + assert client.json().arrappend('doc1', '..a', 'non', 'abba', 'stanza') == 6 # Test single assert client.json().arrlen('doc1', '.nested1.a') == 6 @@ -597,44 +547,33 @@ def test_arrlen_dollar(client): # Test missing key assert client.json().arrlen('non_existing_doc', '..a') is None + @pytest.mark.redismod def test_arrpop_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) - # Test multi - assert client.json().arrpop('doc1', '$..a', '1') == ['foo', None, None] - - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] - assert client.json().arrpop('doc1', '$..a', '-1') == [None, 'world', None] - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["hello"]}, "nested2": {"a": 31}}] + # # # Test multi + assert client.json().arrpop('doc1', '$..a', 1) == ['"foo"', None, None] - # Test single - assert client.json().arrpop('doc1', '$.nested1.a', -2) == ['hello'] assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] + [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().arrpop('non_existing_doc', '..a') - # Test legacy + # # Test legacy client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) # Test multi (all paths are updated, but return result of last path) client.json().arrpop('doc1', '..a', '1') is None assert client.json().get('doc1', '$') == \ [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] - # Test single - assert client.json().arrpop('doc1', '.nested1.a', -2, '"baz"') == '"hello"' - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] - - # Test missing key - with pytest.raises(exceptions.DataError): + # # Test missing key + with pytest.raises(exceptions.ResponseError): client.json().arrpop('non_existing_doc', '..a') + @pytest.mark.redismod def test_arrtrim_dollar(client): @@ -672,15 +611,16 @@ def test_arrtrim_dollar(client): with pytest.raises(exceptions.ResponseError): client.json().arrtrim('non_existing_doc', '..a', 1, 1) + @pytest.mark.redismod def test_objkeys_dollar(client): - client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) - - # Test multi - assert client.json().objkeys('doc1', '$..a') == [["foo", "bar"], None, ["baz"]] + client.json().set('doc1', '$', { + "nested1": { + "a": { + "foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) # Test single - assert client.json().object('doc1', '$.nested1.a') == [["foo", "bar"]] + assert client.json().objkeys('doc1', '$.nested1.a') == [["foo", "bar"]] # Test legacy assert client.json().objkeys('doc1', '.*.a') == ["foo", "bar"] @@ -691,9 +631,10 @@ def test_objkeys_dollar(client): assert client.json().objkeys('non_existing_doc', '..a') is None # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().objkeys('doc1', '$.nowhere') + @pytest.mark.redismod def test_objlen_dollar(client): client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) @@ -753,14 +694,10 @@ def test_type_dollar(client): # Test single assert client.json().type('doc1', '$.nested2.a') == [jtypes[1]] - # Test legacy - assert client.json().type('doc1', '..a') == jtypes[0] - # Test missing path (defaults to root) - assert client.json().type('doc1') == 'object' - # Test missing key assert client.json().type('non_existing_doc', '..a') is None + @pytest.mark.redismod def test_clear_dollar(client): @@ -785,6 +722,7 @@ def test_clear_dollar(client): with pytest.raises(exceptions.ResponseError): client.json().clear('non_existing_doc', '$..a') + @pytest.mark.redismod def test_toggle_dollar(client): client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": False}, "nested2": {"a": 31}, "nested3": {"a": True}}) @@ -793,15 +731,11 @@ def test_toggle_dollar(client): assert client.json().get('doc1', '$') == \ [{"a": ["foo"], "nested1": {"a": True}, "nested2": {"a": 31}, "nested3": {"a": False}}] - # Test single - assert client.json().toggle('doc1', '$.nested1.a') == [0] - assert client.json().get('JSON.GET', 'doc1', '$') == \ - [{"a": ["foo"], "nested1": {"a": False}, "nested2": {"a": 31}, "nested3": {"a": False}}] - # Test missing key - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().toggle('non_existing_doc', '$..a') + @pytest.mark.redismod def test_debug_dollar(client): @@ -825,6 +759,7 @@ def test_debug_dollar(client): # Test missing key assert client.json().debug("MEMORY",'non_existing_doc', '$..a') == [] + def test_resp_dollar(client): data = { @@ -865,7 +800,7 @@ def test_resp_dollar(client): } client.json().set('doc1', '$', data) # Test multi - res = client.json().resp('doc1', '$..a') + res = client.json().resp('doc1', '$..a') assert res == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']], ['{', 'A2_B1', 20, 'A2_B2', 'false', 'A2_B3', ['{', 'A2_B3_C1', None, 'A2_B3_C2', ['[', 'A2_B3_C2_D1_1', 'A2_B3_C2_D1_2', '-37.5', 'A2_B3_C2_D1_4', 'A2_B3_C2_D1_5', ['{', 'A2_B3_C2_D1_6_E1', 'false']], 'A2_B3_C3', ['[', 2]], 'A2_B4', ['{', 'A2_B4_C1', 'bar']]] # Test single @@ -873,14 +808,12 @@ def test_resp_dollar(client): assert resSingle == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']]] # Test missing path - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().resp('doc1', '$.nowhere') # Test missing key assert client.json().resp('non_existing_doc', '$..a') is None - # Test legacy - assert client.json().resp('doc1', '.L1.a') == resSingle def test_arrindex_dollar(client): @@ -895,7 +828,7 @@ def test_arrindex_dollar(client): assert client.json().arrindex( 'store', '$.store.book[?(@.price<10)].size', - '20') == [1, 2] + '20') == [-1, -1] # Test index of int scalar in multi values client.json().set('test_num', @@ -917,28 +850,26 @@ def test_arrindex_dollar(client): assert client.json().get('test_string', '$..arr') == \ [["bazzz","bar",2,"baz",2,"ba","baz",3],[None,"baz2","buzz",2,1,0,1,"2","baz",2,4,5],["baz2",4,6],"3",[]] - assert client.json().arrindex('test_string', '$..arr', '"baz"') == [3, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz') == [3, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', 2) == [3, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', 4) == [6, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', -5) == [3, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, 7) == [6, -1, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, -1) == [6, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '"baz"', 4, 0) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', 2) == [3, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', 4) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', -5) == [3, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', 4, 7) == [6, -1, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', 4, -1) == [6, 8, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', 'baz', 4, 0) == [6, 8, -1, None, -1] assert client.json().arrindex('test_string', '$..arr', '5', 7, -1) == [-1, -1, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '5', 7, 0) == [-1, 11, -1, None, -1] + assert client.json().arrindex('test_string', '$..arr', '5', 7, 0) == [-1, -1, -1, None, -1] # Test index of None scalar in multi values client.json().set('test_None', '.', [{"arr":["bazzz","None",2,None,2,"ba","baz",3]},{"nested1_found":{"arr":["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5]}},{"nested2_not_found":{"arr":["None",4,6]}},{"nested3_scalar":{"arr":None}},[{"nested41_arr":{"arr_renamed":[1,None,3]}},{"nested42_empty_arr":{"arr":[]}}]]) - assert client.json.get('test_None', '$..arr') == \ + assert client.json().get('test_None', '$..arr') == \ [["bazzz","None",2,None,2,"ba","baz",3],["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5],["None",4,6],None,[]] - assert client.json().arrindex('test_None', '$..arr', 'None') == [3, 8, -1, None, -1] - # Fail with none-scalar value - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().arrindex('test_None', '$..nested42_empty_arr.arr', {"arr":[]}) # Do not fail with none-scalar value in legacy mode @@ -949,11 +880,11 @@ def test_arrindex_dollar(client): assert client.json().arrindex('test_num', '.[0].arr', 3) == 3 assert client.json().arrindex('test_num', '.[0].arr', 9) == -1 - with pytest.raises(exceptions.DataError): + with pytest.raises(exceptions.ResponseError): client.json().arrindex('test_num', '.[0].arr_not', 3) # Test index of string scalar in single value - assert client.json().arrindex('test_string', '.[0].arr', '"baz"') == 3 - assert client.json().arrindex('test_string', '.[0].arr', '"faz"') == -1 + assert client.json().arrindex('test_string', '.[0].arr', 'baz') == 3 + assert client.json().arrindex('test_string', '.[0].arr', 'faz') == -1 # Test index of None scalar in single value - assert client.json().arrindex('test_None', '.[0].arr', 'None') == 3 - assert client.json().arrindex('test_None', '..nested2_not_found.arr', 'None') == -1 + assert client.json().arrindex('test_None', '.[0].arr', 'None') == 1 + assert client.json().arrindex('test_None', '..nested2_not_found.arr', 'None') == 0 diff --git a/tests/testdata/jsontestdata.py b/tests/testdata/jsontestdata.py index 32ca6affe4..0a920cc55b 100644 --- a/tests/testdata/jsontestdata.py +++ b/tests/testdata/jsontestdata.py @@ -614,4 +614,4 @@ ], "xejv": None } -""" \ No newline at end of file +""" # noqa From e18283aed05a47db20b9b091579ae1d5799226f4 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 3 Nov 2021 12:30:02 +0200 Subject: [PATCH 8/9] flake8 fixes --- tests/conftest.py | 2 +- tests/test_json.py | 1072 ++++++++++++++++++++++++++++++++------------ 2 files changed, 780 insertions(+), 294 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b0f14ca46a..b1a0f8cab8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -126,7 +126,7 @@ def teardown(): @pytest.fixture() def modclient(request, **kwargs): rmurl = request.config.getoption('--redismod-url') - with _get_client(redis.Redis, request, from_url=rmurl, + with _get_client(redis.Redis, request, from_url=rmurl, decode_responses=True, **kwargs) as client: yield client diff --git a/tests/test_json.py b/tests/test_json.py index 00c0c42208..e50582fc7a 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -3,7 +3,6 @@ from redis.commands.json.path import Path from redis import exceptions from .conftest import skip_ifmodversion_lt -from .testdata.jsontestdata import nested_large_key @pytest.fixture @@ -47,9 +46,10 @@ def test_json_get_jset(client): @pytest.mark.redismod def test_nonascii_setgetdelete(client): - assert client.json().set("notascii", Path.rootPath(), - "hyvää-élève") is True - assert "hyvää-élève" == client.json().get("notascii", no_escape=True) + assert client.json().set("notascii", Path.rootPath(), "hyvää-élève") + assert "hyvää-élève" == client.json().get( + "notascii", + no_escape=True) assert 1 == client.json().delete("notascii") assert client.exists("notascii") == 0 @@ -128,8 +128,8 @@ def test_toggle(client): @pytest.mark.redismod def test_strappend(client): - client.json().set("jsonkey", Path.rootPath(), 'foo') - assert 6 == client.json().strappend("jsonkey", 'bar') + client.json().set("jsonkey", Path.rootPath(), "foo") + assert 6 == client.json().strappend("jsonkey", "bar") assert "foobar" == client.json().get("jsonkey", Path.rootPath()) @@ -184,7 +184,7 @@ def test_arrinsert(client): # test prepends client.json().set("val2", Path.rootPath(), [5, 6, 7, 8, 9]) - client.json().arrinsert("val2", Path.rootPath(), 0, ['some', 'thing']) + client.json().arrinsert("val2", Path.rootPath(), 0, ["some", "thing"]) assert client.json().get("val2") == [["some", "thing"], 5, 6, 7, 8, 9] @@ -193,7 +193,7 @@ def test_arrlen(client): client.json().set("arr", Path.rootPath(), [0, 1, 2, 3, 4]) assert 5 == client.json().arrlen("arr", Path.rootPath()) assert 5 == client.json().arrlen("arr") - assert client.json().arrlen('fakekey') is None + assert client.json().arrlen("fakekey") is None @pytest.mark.redismod @@ -286,8 +286,6 @@ def test_objlen(client): # assert client.get("foo") is None -######### DUAL - @pytest.mark.redismod def test_json_delete_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} @@ -296,25 +294,43 @@ def test_json_delete_with_dollar(client): r = client.json().get("doc1", "$") assert r == [{"nested": {"b": 3}}] - - doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], - "nested": {"b":[True, "a","b"]}} - assert client.json().set('doc2', '$', doc2) + doc2 = {"a": {"a": 2, "b": 3}, "b": [ + "a", "b"], "nested": {"b": [True, "a", "b"]}} + assert client.json().set("doc2", "$", doc2) assert client.json().delete("doc2", "$..a") == 1 res = client.json().get("doc2", "$") - assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] - - doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, - {"ciao":[2,"a"]}, - {"ciaoc":[3,"non","ciao"]}, - {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] - assert client.json().set('doc3', '$', doc3) - assert client.json().delete('doc3', '$.[0]["nested"]..ciao') == 3 - - doc3val = [[{"ciao":["non ancora"], - "nested":[{},{}, - {"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]] - res = client.json().get('doc3', '$') + assert res == [{"nested": {"b": [True, "a", "b"]}, "b": ["a", "b"]}] + + doc3 = [ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]}, + ], + } + ] + assert client.json().set("doc3", "$", doc3) + assert client.json().delete("doc3", '$.[0]["nested"]..ciao') == 3 + + doc3val = [ + [ + { + "ciao": ["non ancora"], + "nested": [ + {}, + {}, + {"ciaoc": [3, "non", "ciao"]}, + {}, + {"e": [5, "non", "ciao"]}, + ], + } + ] + ] + res = client.json().get("doc3", "$") assert res == doc3val # Test default path @@ -323,6 +339,7 @@ def test_json_delete_with_dollar(client): client.json().delete("not_a_document", "..a") + @pytest.mark.redismod def test_json_forget_with_dollar(client): doc1 = {"a": 1, "nested": {"a": 2, "b": 3}} @@ -331,25 +348,43 @@ def test_json_forget_with_dollar(client): r = client.json().get("doc1", "$") assert r == [{"nested": {"b": 3}}] - - doc2 = {"a": {"a": 2, "b": 3}, "b": ["a", "b"], - "nested": {"b":[True, "a","b"]}} - assert client.json().set('doc2', '$', doc2) + doc2 = {"a": {"a": 2, "b": 3}, "b": [ + "a", "b"], "nested": {"b": [True, "a", "b"]}} + assert client.json().set("doc2", "$", doc2) assert client.json().forget("doc2", "$..a") == 1 res = client.json().get("doc2", "$") - assert res == [{"nested":{"b":[True,"a","b"]},"b":["a","b"]}] - - doc3 = [{"ciao":["non ancora"],"nested":[{"ciao":[1,"a"]}, - {"ciao":[2,"a"]}, - {"ciaoc":[3,"non","ciao"]}, - {"ciao":[4,"a"]}, {"e":[5,"non","ciao"]}]}] - assert client.json().set('doc3', '$', doc3) - assert client.json().forget('doc3', '$.[0]["nested"]..ciao') == 3 - - doc3val = [[{"ciao":["non ancora"], - "nested":[{},{}, - {"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]] - res = client.json().get('doc3', '$') + assert res == [{"nested": {"b": [True, "a", "b"]}, "b": ["a", "b"]}] + + doc3 = [ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]}, + ], + } + ] + assert client.json().set("doc3", "$", doc3) + assert client.json().forget("doc3", '$.[0]["nested"]..ciao') == 3 + + doc3val = [ + [ + { + "ciao": ["non ancora"], + "nested": [ + {}, + {}, + {"ciaoc": [3, "non", "ciao"]}, + {}, + {"e": [5, "non", "ciao"]}, + ], + } + ] + ] + res = client.json().get("doc3", "$") assert res == doc3val # Test default path @@ -362,20 +397,33 @@ def test_json_forget_with_dollar(client): @pytest.mark.redismod def test_json_mget_dollar(client): # Test mget with multi paths - client.json().set('doc1', '$', {"a":1, "b": 2, "nested": {"a": 3}, "c": None, "nested2": {"a": None}}) - client.json().set('doc2', '$', {"a":4, "b": 5, "nested": {"a": 6}, "c": None, "nested2": {"a": [None]}}) + client.json().set( + "doc1", + "$", + {"a": 1, + "b": 2, + "nested": {"a": 3}, + "c": None, "nested2": {"a": None}}, + ) + client.json().set( + "doc2", + "$", + {"a": 4, "b": 5, "nested": {"a": 6}, + "c": None, "nested2": {"a": [None]}}, + ) # Compare also to single JSON.GET - assert client.json().get('doc1', '$..a') == [1,3,None] - assert client.json().get('doc2', '$..a') == [4,6,[None]] + assert client.json().get("doc1", "$..a") == [1, 3, None] + assert client.json().get("doc2", "$..a") == [4, 6, [None]] # Test mget with single path - client.json().mget('doc1', '$..a') == [1,3,None] + client.json().mget("doc1", "$..a") == [1, 3, None] # Test mget with multi path - client.json().mget(['doc1', 'doc2'], '$..a') == [[1,3,None], [4,6,[None]]] + client.json().mget(["doc1", "doc2"], "$..a") == [ + [1, 3, None], [4, 6, [None]]] # Test missing key - client.json().mget(['doc1', 'missing_doc'], '$..a') == [[1,3,None], None] - res = client.json().mget(['missing_doc1', 'missing_doc2'], '$..a') + client.json().mget(["doc1", "missing_doc"], "$..a") == [[1, 3, None], None] + res = client.json().mget(["missing_doc1", "missing_doc2"], "$..a") assert res == [None, None] @@ -383,508 +431,946 @@ def test_json_mget_dollar(client): def test_numby_commands_dollar(client): # Test NUMINCRBY - client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + client.json().set( + "doc1", + "$", {"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}) # Test multi - assert client.json().numincrby('doc1', '$..a', 2) == [None, 4, 7.0, None] + assert client.json().numincrby("doc1", "$..a", 2) == \ + [None, 4, 7.0, None] - assert client.json().numincrby('doc1', '$..a', 2.5) == [None, 6.5, 9.5, None] + assert client.json().numincrby("doc1", "$..a", 2.5) == \ + [None, 6.5, 9.5, None] # Test single - assert client.json().numincrby('doc1', '$.b[1].a', 2) == [11.5] + assert client.json().numincrby("doc1", "$.b[1].a", 2) == [11.5] - assert client.json().numincrby('doc1', '$.b[2].a', 2) == [None] - assert client.json().numincrby('doc1', '$.b[1].a', 3.5) == [15.0] + assert client.json().numincrby("doc1", "$.b[2].a", 2) == [None] + assert client.json().numincrby("doc1", "$.b[1].a", 3.5) == [15.0] # Test NUMMULTBY - client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) + client.json().set("doc1", "$", {"a": "b", "b": [ + {"a": 2}, {"a": 5.0}, {"a": "c"}]}) - assert client.json().nummultby('doc1', '$..a', 2) == [None, 4, 10, None] - assert client.json().nummultby('doc1', '$..a', 2.5) == [None,10.0,25.0,None] + assert client.json().nummultby("doc1", "$..a", 2) == \ + [None, 4, 10, None] + assert client.json().nummultby("doc1", "$..a", 2.5) == \ + [None, 10.0, 25.0, None] # Test single - assert client.json().nummultby('doc1', '$.b[1].a', 2) == [50.0] - assert client.json().nummultby('doc1', '$.b[2].a', 2) == [None] - assert client.json().nummultby('doc1', '$.b[1].a', 3) == [150.0] + assert client.json().nummultby("doc1", "$.b[1].a", 2) == [50.0] + assert client.json().nummultby("doc1", "$.b[2].a", 2) == [None] + assert client.json().nummultby("doc1", "$.b[1].a", 3) == [150.0] # test missing keys with pytest.raises(exceptions.ResponseError): - client.json().numincrby('non_existing_doc', '$..a', 2) - client.json().nummultby('non_existing_doc', '$..a', 2) + client.json().numincrby("non_existing_doc", "$..a", 2) + client.json().nummultby("non_existing_doc", "$..a", 2) # Test legacy NUMINCRBY - client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - client.json().numincrby('doc1', '.b[0].a', 3) == 5 + client.json().set("doc1", "$", {"a": "b", "b": [ + {"a": 2}, {"a": 5.0}, {"a": "c"}]}) + client.json().numincrby("doc1", ".b[0].a", 3) == 5 # Test legacy NUMMULTBY - client.json().set('doc1', '$', {"a":"b","b":[{"a":2}, {"a":5.0}, {"a":"c"}]}) - client.json().nummultby('doc1', '.b[0].a', 3) == 6 + client.json().set("doc1", "$", {"a": "b", "b": [ + {"a": 2}, {"a": 5.0}, {"a": "c"}]}) + client.json().nummultby("doc1", ".b[0].a", 3) == 6 @pytest.mark.redismod def test_strappend_dollar(client): - client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) + client.json().set( + "doc1", "$", {"a": "foo", "nested1": { + "a": "hello"}, "nested2": {"a": 31}} + ) # Test multi - client.json().strappend('doc1', 'bar', '$..a') == [6, 8, None] - + client.json().strappend("doc1", "bar", "$..a") == [6, 8, None] - client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobar"},"nested2":{"a":31}}] + client.json().get("doc1", "$") == [ + {"a": "foobar", "nested1": {"a": "hellobar"}, "nested2": {"a": 31}} + ] # Test single - client.json().strappend('doc1', 'baz', '$.nested1.a') == [11] + client.json().strappend("doc1", "baz", "$.nested1.a") == [11] - client.json().get('doc1', '$') == [{"a":"foobar","nested1":{"a":"hellobarbaz"},"nested2":{"a":31}}] + client.json().get("doc1", "$") == [ + {"a": "foobar", "nested1": {"a": "hellobarbaz"}, "nested2": {"a": 31}} + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().strappend('non_existing_doc', '$..a', 'err') + client.json().strappend("non_existing_doc", "$..a", "err") # Test multi - client.json().strappend('doc1', 'bar', '.*.a') == 8 - client.json().get('doc1', '$') == [{"a":"foo","nested1":{"a":"hellobar"},"nested2":{"a":31}}] + client.json().strappend("doc1", "bar", ".*.a") == 8 + client.json().get("doc1", "$") == [ + {"a": "foo", "nested1": {"a": "hellobar"}, "nested2": {"a": 31}} + ] # Test missing path with pytest.raises(exceptions.ResponseError): - client.json().strappend('doc1', 'piu') + client.json().strappend("doc1", "piu") @pytest.mark.redismod def test_strlen_dollar(client): # Test multi - client.json().set('doc1', '$', {"a":"foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}) - assert client.json().strlen('doc1', '$..a') == [3, 5, None] + client.json().set( + "doc1", "$", {"a": "foo", "nested1": { + "a": "hello"}, "nested2": {"a": 31}} + ) + assert client.json().strlen("doc1", "$..a") == [3, 5, None] - res2 = client.json().strappend('doc1', 'bar', '$..a') - res1 = client.json().strlen('doc1', '$..a') + res2 = client.json().strappend("doc1", "bar", "$..a") + res1 = client.json().strlen("doc1", "$..a") assert res1 == res2 # Test single - client.json().strlen('doc1', '$.nested1.a') == [8] - client.json().strlen('doc1', '$.nested2.a') == [None] + client.json().strlen("doc1", "$.nested1.a") == [8] + client.json().strlen("doc1", "$.nested2.a") == [None] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().strlen('non_existing_doc', '$..a') + client.json().strlen("non_existing_doc", "$..a") @pytest.mark.redismod def test_arrappend_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi - client.json().arrappend('doc1', '$..a', 'bar', 'racuda') == [3, 5, None] - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] + client.json().arrappend("doc1", "$..a", "bar", "racuda") == [3, 5, None] + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, + "nested2": {"a": 31}, + } + ] # Test single - assert client.json().arrappend('doc1', '$.nested1.a', 'baz') == [6] - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] + assert client.json().arrappend("doc1", "$.nested1.a", "baz") == [6] + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, + "nested2": {"a": 31}, + } + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrappend('non_existing_doc', '$..a') + client.json().arrappend("non_existing_doc", "$..a") # Test legacy - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi (all paths are updated, but return result of last path) - assert client.json().arrappend('doc1', '..a', 'bar', 'racuda') == 5 - - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, "nested2": {"a": 31}}] + assert client.json().arrappend("doc1", "..a", "bar", "racuda") == 5 + + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", None, "world", "bar", "racuda"]}, + "nested2": {"a": 31}, + } + ] # Test single - assert client.json().arrappend('doc1', '.nested1.a', 'baz') == 6 - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, "nested2": {"a": 31}}] + assert client.json().arrappend("doc1", ".nested1.a", "baz") == 6 + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", None, "world", "bar", "racuda", "baz"]}, + "nested2": {"a": 31}, + } + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrappend('non_existing_doc', '$..a') + client.json().arrappend("non_existing_doc", "$..a") @pytest.mark.redismod def test_arrinsert_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi - assert client.json().arrinsert('doc1', '$..a', '1', 'bar', 'racuda') == [3, 5, None] - - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, "nested2": {"a": 31}}] + assert client.json().arrinsert("doc1", "$..a", "1", + "bar", "racuda") == [3, 5, None] + + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", "bar", "racuda", None, "world"]}, + "nested2": {"a": 31}, + } + ] # Test single - assert client.json().arrinsert('doc1', '$.nested1.a', -2, 'baz') == [6] - assert client.json().get('doc1', '$') == \ - [{"a": ["foo", "bar", "racuda"], "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, "nested2": {"a": 31}}] + assert client.json().arrinsert("doc1", "$.nested1.a", -2, "baz") == [6] + assert client.json().get("doc1", "$") == [ + { + "a": ["foo", "bar", "racuda"], + "nested1": {"a": ["hello", "bar", "racuda", "baz", None, "world"]}, + "nested2": {"a": 31}, + } + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrappend('non_existing_doc', '$..a') + client.json().arrappend("non_existing_doc", "$..a") @pytest.mark.redismod def test_arrlen_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi - assert client.json().arrlen('doc1', '$..a') == [1, 3, None] - assert client.json().arrappend('doc1', '$..a', 'non', 'abba', 'stanza') == \ - [4, 6, None] + assert client.json().arrlen("doc1", "$..a") == [1, 3, None] + assert client.json().arrappend("doc1", "$..a", "non", "abba", "stanza") \ + == [4, 6, None] - client.json().clear('doc1', '$.a') - assert client.json().arrlen('doc1', '$..a') == [0, 6, None] + client.json().clear("doc1", "$.a") + assert client.json().arrlen("doc1", "$..a") == [0, 6, None] # Test single - assert client.json().arrlen('doc1', '$.nested1.a') == [6] + assert client.json().arrlen("doc1", "$.nested1.a") == [6] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrappend('non_existing_doc', '$..a') + client.json().arrappend("non_existing_doc", "$..a") - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi (return result of last path) - assert client.json().arrlen('doc1', '$..a') == [1, 3, None] - assert client.json().arrappend('doc1', '..a', 'non', 'abba', 'stanza') == 6 + assert client.json().arrlen("doc1", "$..a") == [1, 3, None] + assert client.json().arrappend("doc1", "..a", "non", "abba", "stanza") == 6 # Test single - assert client.json().arrlen('doc1', '.nested1.a') == 6 + assert client.json().arrlen("doc1", ".nested1.a") == 6 # Test missing key - assert client.json().arrlen('non_existing_doc', '..a') is None + assert client.json().arrlen("non_existing_doc", "..a") is None @pytest.mark.redismod def test_arrpop_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # # # Test multi - assert client.json().arrpop('doc1', '$..a', 1) == ['"foo"', None, None] + assert client.json().arrpop("doc1", "$..a", 1) == ['"foo"', None, None] - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}} + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrpop('non_existing_doc', '..a') + client.json().arrpop("non_existing_doc", "..a") # # Test legacy - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi (all paths are updated, but return result of last path) - client.json().arrpop('doc1', '..a', '1') is None - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}}] + client.json().arrpop("doc1", "..a", "1") is None + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": ["hello", "world"]}, "nested2": {"a": 31}} + ] # # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrpop('non_existing_doc', '..a') + client.json().arrpop("non_existing_doc", "..a") @pytest.mark.redismod def test_arrtrim_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi - assert client.json().arrtrim('doc1', '$..a', '1', -1) == [0, 2, None] - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}}] - - assert client.json().arrtrim('doc1', '$..a', '1', '1') == [0, 1, None] - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] + assert client.json().arrtrim("doc1", "$..a", "1", -1) == [0, 2, None] + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}} + ] + + assert client.json().arrtrim("doc1", "$..a", "1", "1") == [0, 1, None] + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}} + ] # Test single - assert client.json().arrtrim('doc1', '$.nested1.a', 1, 0) == [0] - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": []}, "nested2": {"a": 31}}] + assert client.json().arrtrim("doc1", "$.nested1.a", 1, 0) == [0] + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": []}, "nested2": {"a": 31}} + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrtrim('non_existing_doc', '..a', '0', 1) + client.json().arrtrim("non_existing_doc", "..a", "0", 1) # Test legacy - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": ["hello", None, "world"]}, "nested2": {"a": 31}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": ["hello", None, "world"]}, + "nested2": {"a": 31}, + }, + ) # Test multi (all paths are updated, but return result of last path) - assert client.json().arrtrim('doc1', '..a', '1', '-1') == 2 - res = client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": [None, "world"]}, "nested2": {"a": 31}}] + assert client.json().arrtrim("doc1", "..a", "1", "-1") == 2 + # Test single - assert client.json().arrtrim('doc1', '.nested1.a', '1', '1') == 1 - assert client.json().get('doc1', '$') == \ - [{"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}}] + assert client.json().arrtrim("doc1", ".nested1.a", "1", "1") == 1 + assert client.json().get("doc1", "$") == [ + {"a": [], "nested1": {"a": ["world"]}, "nested2": {"a": 31}} + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().arrtrim('non_existing_doc', '..a', 1, 1) + client.json().arrtrim("non_existing_doc", "..a", 1, 1) @pytest.mark.redismod def test_objkeys_dollar(client): - client.json().set('doc1', '$', { - "nested1": { - "a": { - "foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) + client.json().set( + "doc1", + "$", + { + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": {"baz": 50}}, + }, + ) # Test single - assert client.json().objkeys('doc1', '$.nested1.a') == [["foo", "bar"]] + assert client.json().objkeys("doc1", "$.nested1.a") == [["foo", "bar"]] # Test legacy - assert client.json().objkeys('doc1', '.*.a') == ["foo", "bar"] + assert client.json().objkeys("doc1", ".*.a") == ["foo", "bar"] # Test single - assert client.json().objkeys('doc1', '.nested2.a') == ["baz"] + assert client.json().objkeys("doc1", ".nested2.a") == ["baz"] # Test missing key - assert client.json().objkeys('non_existing_doc', '..a') is None + assert client.json().objkeys("non_existing_doc", "..a") is None # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().objkeys('doc1', '$.nowhere') + client.json().objkeys("doc1", "$.nowhere") @pytest.mark.redismod def test_objlen_dollar(client): - client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": {"baz":50}}}) + client.json().set( + "doc1", + "$", + { + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": {"baz": 50}}, + }, + ) # Test multi - assert client.json().objlen('doc1', '$..a') == [2, None, 1] + assert client.json().objlen("doc1", "$..a") == [2, None, 1] # Test single - assert client.json().objlen('doc1', '$.nested1.a') == [2] + assert client.json().objlen("doc1", "$.nested1.a") == [2] # Test missing key - assert client.json().objlen('non_existing_doc', '$..a') is None + assert client.json().objlen("non_existing_doc", "$..a") is None # Test missing path with pytest.raises(exceptions.ResponseError): - client.json().objlen('doc1', '$.nowhere') - + client.json().objlen("doc1", "$.nowhere") # Test legacy - assert client.json().objlen('doc1', '.*.a') == 2 + assert client.json().objlen("doc1", ".*.a") == 2 # Test single - assert client.json().objlen('doc1', '.nested2.a') == 1 + assert client.json().objlen("doc1", ".nested2.a") == 1 # Test missing key - assert client.json().objlen('non_existing_doc', '..a') is None + assert client.json().objlen("non_existing_doc", "..a") is None # Test missing path with pytest.raises(exceptions.ResponseError): - client.json().objlen('doc1', '.nowhere') + client.json().objlen("doc1", ".nowhere") + @pytest.mark.redismod def load_types_data(nested_key_name): - types_data = { - 'object': {}, - 'array': [], - 'string': 'str', - 'integer': 42, - 'number': 1.2, - 'boolean': False, - 'null': None, - + td = { + "object": {}, + "array": [], + "string": "str", + "integer": 42, + "number": 1.2, + "boolean": False, + "null": None, } jdata = {} types = [] - for i, (k, v) in zip(range(1, len(types_data) + 1), iter(types_data.items())): + for i, (k, v) in zip(range(1, len(td) + 1), iter(td.items())): jdata["nested" + str(i)] = {nested_key_name: v} types.append(k) return jdata, types + @pytest.mark.redismod def test_type_dollar(client): - jdata, jtypes = load_types_data('a') - client.json().set('doc1', '$', jdata) + jdata, jtypes = load_types_data("a") + client.json().set("doc1", "$", jdata) # Test multi - assert client.json().type('doc1', '$..a') == jtypes + assert client.json().type("doc1", "$..a") == jtypes # Test single - assert client.json().type('doc1', '$.nested2.a') == [jtypes[1]] + assert client.json().type("doc1", "$.nested2.a") == [jtypes[1]] # Test missing key - assert client.json().type('non_existing_doc', '..a') is None + assert client.json().type("non_existing_doc", "..a") is None @pytest.mark.redismod def test_clear_dollar(client): - client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz":50}}}) + client.json().set( + "doc1", + "$", + { + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": "claro"}, + "nested3": {"a": {"baz": 50}}, + }, + ) # Test multi - assert client.json().clear('doc1', '$..a') == 3 + assert client.json().clear("doc1", "$..a") == 3 - assert client.json().get('doc1', '$') == \ - [{"nested1": {"a": {}}, "a": [], "nested2": {"a": "claro"}, "nested3": {"a": {}}}] + assert client.json().get("doc1", "$") == [ + {"nested1": {"a": {}}, "a": [], "nested2": { + "a": "claro"}, "nested3": {"a": {}}} + ] # Test single - client.json().set('doc1', '$', {"nested1": {"a": {"foo": 10, "bar": 20}}, "a":["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz":50}}}) - assert client.json().clear('doc1', '$.nested1.a') == 1 - assert client.json().get('doc1', '$') == \ - [{"nested1": {"a": {}}, "a": ["foo"], "nested2": {"a": "claro"}, "nested3": {"a": {"baz": 50}}}] + client.json().set( + "doc1", + "$", + { + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": "claro"}, + "nested3": {"a": {"baz": 50}}, + }, + ) + assert client.json().clear("doc1", "$.nested1.a") == 1 + assert client.json().get("doc1", "$") == [ + { + "nested1": {"a": {}}, + "a": ["foo"], + "nested2": {"a": "claro"}, + "nested3": {"a": {"baz": 50}}, + } + ] # Test missing path (defaults to root) - assert client.json().clear('doc1') == 1 - assert client.json().get('doc1', '$') == [{}] + assert client.json().clear("doc1") == 1 + assert client.json().get("doc1", "$") == [{}] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().clear('non_existing_doc', '$..a') + client.json().clear("non_existing_doc", "$..a") @pytest.mark.redismod def test_toggle_dollar(client): - client.json().set('doc1', '$', {"a":["foo"], "nested1": {"a": False}, "nested2": {"a": 31}, "nested3": {"a": True}}) + client.json().set( + "doc1", + "$", + { + "a": ["foo"], + "nested1": {"a": False}, + "nested2": {"a": 31}, + "nested3": {"a": True}, + }, + ) # Test multi - assert client.json().toggle('doc1', '$..a') == [None, 1, None, 0] - assert client.json().get('doc1', '$') == \ - [{"a": ["foo"], "nested1": {"a": True}, "nested2": {"a": 31}, "nested3": {"a": False}}] + assert client.json().toggle("doc1", "$..a") == [None, 1, None, 0] + assert client.json().get("doc1", "$") == [ + { + "a": ["foo"], + "nested1": {"a": True}, + "nested2": {"a": 31}, + "nested3": {"a": False}, + } + ] # Test missing key with pytest.raises(exceptions.ResponseError): - client.json().toggle('non_existing_doc', '$..a') + client.json().toggle("non_existing_doc", "$..a") @pytest.mark.redismod def test_debug_dollar(client): - jdata, jtypes = load_types_data('a') + jdata, jtypes = load_types_data("a") - client.json().set('doc1', '$', jdata) + client.json().set("doc1", "$", jdata) # Test multi - assert client.json().debug("MEMORY", 'doc1', '$..a') == \ - [72, 24, 24, 16, 16, 1, 0] + assert client.json().debug("MEMORY", "doc1", "$..a") == [ + 72, 24, 24, 16, 16, 1, 0] # Test single - assert client.json().debug("MEMORY", 'doc1', '$.nested2.a') == [24] + assert client.json().debug("MEMORY", "doc1", "$.nested2.a") == [24] # Test legacy - assert client.json().debug("MEMORY", 'doc1', '..a') == 72 + assert client.json().debug("MEMORY", "doc1", "..a") == 72 # Test missing path (defaults to root) - assert client.json().debug("MEMORY", 'doc1') == 72 + assert client.json().debug("MEMORY", "doc1") == 72 # Test missing key - assert client.json().debug("MEMORY",'non_existing_doc', '$..a') == [] + assert client.json().debug("MEMORY", "non_existing_doc", "$..a") == [] def test_resp_dollar(client): data = { - 'L1': { - 'a': { - 'A1_B1': 10, - 'A1_B2': False, - 'A1_B3': { - 'A1_B3_C1': None, - 'A1_B3_C2': [ 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', -19.5, 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', { - 'A1_B3_C2_D1_6_E1': True - } + "L1": { + "a": { + "A1_B1": 10, + "A1_B2": False, + "A1_B3": { + "A1_B3_C1": None, + "A1_B3_C2": [ + "A1_B3_C2_D1_1", + "A1_B3_C2_D1_2", + -19.5, + "A1_B3_C2_D1_4", + "A1_B3_C2_D1_5", + {"A1_B3_C2_D1_6_E1": True}, ], - 'A1_B3_C3': [1] + "A1_B3_C3": [1], + }, + "A1_B4": { + "A1_B4_C1": "foo", }, - 'A1_B4': { - 'A1_B4_C1': "foo", - } }, }, - 'L2': { - 'a': { - 'A2_B1': 20, - 'A2_B2': False, - 'A2_B3': { - 'A2_B3_C1': None, - 'A2_B3_C2': [ 'A2_B3_C2_D1_1', 'A2_B3_C2_D1_2', -37.5, 'A2_B3_C2_D1_4', 'A2_B3_C2_D1_5', { - 'A2_B3_C2_D1_6_E1': False - } + "L2": { + "a": { + "A2_B1": 20, + "A2_B2": False, + "A2_B3": { + "A2_B3_C1": None, + "A2_B3_C2": [ + "A2_B3_C2_D1_1", + "A2_B3_C2_D1_2", + -37.5, + "A2_B3_C2_D1_4", + "A2_B3_C2_D1_5", + {"A2_B3_C2_D1_6_E1": False}, ], - 'A2_B3_C3': [2] + "A2_B3_C3": [2], + }, + "A2_B4": { + "A2_B4_C1": "bar", }, - 'A2_B4': { - 'A2_B4_C1': "bar", - } }, }, } - client.json().set('doc1', '$', data) + client.json().set("doc1", "$", data) # Test multi - res = client.json().resp('doc1', '$..a') - assert res == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']], ['{', 'A2_B1', 20, 'A2_B2', 'false', 'A2_B3', ['{', 'A2_B3_C1', None, 'A2_B3_C2', ['[', 'A2_B3_C2_D1_1', 'A2_B3_C2_D1_2', '-37.5', 'A2_B3_C2_D1_4', 'A2_B3_C2_D1_5', ['{', 'A2_B3_C2_D1_6_E1', 'false']], 'A2_B3_C3', ['[', 2]], 'A2_B4', ['{', 'A2_B4_C1', 'bar']]] + res = client.json().resp("doc1", "$..a") + assert res == [ + [ + "{", + "A1_B1", + 10, + "A1_B2", + "false", + "A1_B3", + [ + "{", + "A1_B3_C1", + None, + "A1_B3_C2", + [ + "[", + "A1_B3_C2_D1_1", + "A1_B3_C2_D1_2", + "-19.5", + "A1_B3_C2_D1_4", + "A1_B3_C2_D1_5", + ["{", "A1_B3_C2_D1_6_E1", "true"], + ], + "A1_B3_C3", + ["[", 1], + ], + "A1_B4", + ["{", "A1_B4_C1", "foo"], + ], + [ + "{", + "A2_B1", + 20, + "A2_B2", + "false", + "A2_B3", + [ + "{", + "A2_B3_C1", + None, + "A2_B3_C2", + [ + "[", + "A2_B3_C2_D1_1", + "A2_B3_C2_D1_2", + "-37.5", + "A2_B3_C2_D1_4", + "A2_B3_C2_D1_5", + ["{", "A2_B3_C2_D1_6_E1", "false"], + ], + "A2_B3_C3", + ["[", 2], + ], + "A2_B4", + ["{", "A2_B4_C1", "bar"], + ], + ] # Test single - resSingle = client.json().resp('doc1', '$.L1.a') - assert resSingle == [['{', 'A1_B1', 10, 'A1_B2', 'false', 'A1_B3', ['{', 'A1_B3_C1', None, 'A1_B3_C2', ['[', 'A1_B3_C2_D1_1', 'A1_B3_C2_D1_2', '-19.5', 'A1_B3_C2_D1_4', 'A1_B3_C2_D1_5', ['{', 'A1_B3_C2_D1_6_E1', 'true']], 'A1_B3_C3', ['[', 1]], 'A1_B4', ['{', 'A1_B4_C1', 'foo']]] + resSingle = client.json().resp("doc1", "$.L1.a") + assert resSingle == [ + [ + "{", + "A1_B1", + 10, + "A1_B2", + "false", + "A1_B3", + [ + "{", + "A1_B3_C1", + None, + "A1_B3_C2", + [ + "[", + "A1_B3_C2_D1_1", + "A1_B3_C2_D1_2", + "-19.5", + "A1_B3_C2_D1_4", + "A1_B3_C2_D1_5", + ["{", "A1_B3_C2_D1_6_E1", "true"], + ], + "A1_B3_C3", + ["[", 1], + ], + "A1_B4", + ["{", "A1_B4_C1", "foo"], + ] + ] # Test missing path with pytest.raises(exceptions.ResponseError): - client.json().resp('doc1', '$.nowhere') + client.json().resp("doc1", "$.nowhere") # Test missing key - assert client.json().resp('non_existing_doc', '$..a') is None + assert client.json().resp("non_existing_doc", "$..a") is None def test_arrindex_dollar(client): client.json().set( - 'store', - '$', - {"store":{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95,"size":[10,20,30,40]},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99,"size":[50,60,70,80]},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99,"size":[5,10,20,30]},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99,"size":[5,6,7,8]}],"bicycle":{"color":"red","price":19.95}}}) + "store", + "$", + { + "store": { + "book": [ + { + "category": "reference", + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95, + "size": [10, 20, 30, 40], + }, + { + "category": "fiction", + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99, + "size": [50, 60, 70, 80], + }, + { + "category": "fiction", + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99, + "size": [5, 10, 20, 30], + }, + { + "category": "fiction", + "author": "J. R. R. Tolkien", + "title": "The Lord of the Rings", + "isbn": "0-395-19395-8", + "price": 22.99, + "size": [5, 6, 7, 8], + }, + ], + "bicycle": {"color": "red", "price": 19.95}, + } + }, + ) - assert client.json().get( - 'store', - '$.store.book[?(@.price<10)].size') == [[10,20,30,40],[5,10,20,30]] + assert client.json().get("store", "$.store.book[?(@.price<10)].size") == [ + [10, 20, 30, 40], + [5, 10, 20, 30], + ] assert client.json().arrindex( - 'store', - '$.store.book[?(@.price<10)].size', - '20') == [-1, -1] + "store", "$.store.book[?(@.price<10)].size", "20" + ) == [-1, -1] # Test index of int scalar in multi values - client.json().set('test_num', - '.', - [{"arr":[0,1,3.0,3,2,1,0,3]},{"nested1_found":{"arr":[5,4,3,2,1,0,1,2,3.0,2,4,5]}},{"nested2_not_found":{"arr":[2,4,6]}},{"nested3_scalar":{"arr":"3"}},[{"nested41_not_arr":{"arr_renamed":[1,2,3]}},{"nested42_empty_arr":{"arr":[]}}]]) + client.json().set( + "test_num", + ".", + [ + {"arr": [0, 1, 3.0, 3, 2, 1, 0, 3]}, + {"nested1_found": {"arr": [5, 4, 3, 2, 1, 0, 1, 2, 3.0, 2, 4, 5]}}, + {"nested2_not_found": {"arr": [2, 4, 6]}}, + {"nested3_scalar": {"arr": "3"}}, + [ + {"nested41_not_arr": {"arr_renamed": [1, 2, 3]}}, + {"nested42_empty_arr": {"arr": []}}, + ], + ], + ) - assert client.json().get('test_num', '$..arr') == \ - [[0,1,3.0,3,2,1,0,3],[5,4,3,2,1,0,1,2,3.0,2,4,5],[2,4,6],"3",[]] + assert client.json().get("test_num", "$..arr") == [ + [0, 1, 3.0, 3, 2, 1, 0, 3], + [5, 4, 3, 2, 1, 0, 1, 2, 3.0, 2, 4, 5], + [2, 4, 6], + "3", + [], + ] - assert client.json().arrindex('test_num', '$..arr', 3) == [3, 2, -1, None, -1] + assert client.json().arrindex("test_num", "$..arr", 3) == [ + 3, 2, -1, None, -1] # Test index of double scalar in multi values - assert client.json().arrindex('test_num', '$..arr', 3.0) == [2, 8, -1, None, -1] + assert client.json().arrindex("test_num", "$..arr", 3.0) == [ + 2, 8, -1, None, -1] # Test index of string scalar in multi values - client.json().set('test_string', - '.', - [{"arr":["bazzz","bar",2,"baz",2,"ba","baz",3]},{"nested1_found":{"arr":[None,"baz2","buzz",2,1,0,1,"2","baz",2,4,5]}},{"nested2_not_found":{"arr":["baz2",4,6]}},{"nested3_scalar":{"arr":"3"}},[{"nested41_arr":{"arr_renamed":[1,"baz",3]}},{"nested42_empty_arr":{"arr":[]}}]]) - assert client.json().get('test_string', '$..arr') == \ - [["bazzz","bar",2,"baz",2,"ba","baz",3],[None,"baz2","buzz",2,1,0,1,"2","baz",2,4,5],["baz2",4,6],"3",[]] - - assert client.json().arrindex('test_string', '$..arr', 'baz') == [3, 8, -1, None, -1] - - assert client.json().arrindex('test_string', '$..arr', 'baz', 2) == [3, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', 'baz', 4) == [6, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', 'baz', -5) == [3, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', 'baz', 4, 7) == [6, -1, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', 'baz', 4, -1) == [6, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', 'baz', 4, 0) == [6, 8, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '5', 7, -1) == [-1, -1, -1, None, -1] - assert client.json().arrindex('test_string', '$..arr', '5', 7, 0) == [-1, -1, -1, None, -1] + client.json().set( + "test_string", + ".", + [ + {"arr": ["bazzz", "bar", 2, "baz", 2, "ba", "baz", 3]}, + { + "nested1_found": { + "arr": [ + None, + "baz2", + "buzz", 2, 1, 0, 1, "2", "baz", 2, 4, 5] + } + }, + {"nested2_not_found": {"arr": ["baz2", 4, 6]}}, + {"nested3_scalar": {"arr": "3"}}, + [ + {"nested41_arr": {"arr_renamed": [1, "baz", 3]}}, + {"nested42_empty_arr": {"arr": []}}, + ], + ], + ) + assert client.json().get("test_string", "$..arr") == [ + ["bazzz", "bar", 2, "baz", 2, "ba", "baz", 3], + [None, "baz2", "buzz", 2, 1, 0, 1, "2", "baz", 2, 4, 5], + ["baz2", 4, 6], + "3", + [], + ] + + assert client.json().arrindex("test_string", "$..arr", "baz") == [ + 3, + 8, + -1, + None, + -1, + ] + + assert client.json().arrindex("test_string", "$..arr", "baz", 2) == [ + 3, + 8, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "baz", 4) == [ + 6, + 8, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "baz", -5) == [ + 3, + 8, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "baz", 4, 7) == [ + 6, + -1, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "baz", 4, -1) == [ + 6, + 8, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "baz", 4, 0) == [ + 6, + 8, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "5", 7, -1) == [ + -1, + -1, + -1, + None, + -1, + ] + assert client.json().arrindex("test_string", "$..arr", "5", 7, 0) == [ + -1, + -1, + -1, + None, + -1, + ] # Test index of None scalar in multi values - client.json().set('test_None', - '.', - [{"arr":["bazzz","None",2,None,2,"ba","baz",3]},{"nested1_found":{"arr":["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5]}},{"nested2_not_found":{"arr":["None",4,6]}},{"nested3_scalar":{"arr":None}},[{"nested41_arr":{"arr_renamed":[1,None,3]}},{"nested42_empty_arr":{"arr":[]}}]]) - assert client.json().get('test_None', '$..arr') == \ - [["bazzz","None",2,None,2,"ba","baz",3],["zaz","baz2","buzz",2,1,0,1,"2",None,2,4,5],["None",4,6],None,[]] + client.json().set( + "test_None", + ".", + [ + {"arr": ["bazzz", "None", 2, None, 2, "ba", "baz", 3]}, + { + "nested1_found": { + "arr": [ + "zaz", + "baz2", + "buzz", + 2, 1, 0, 1, "2", None, 2, 4, 5] + } + }, + {"nested2_not_found": {"arr": ["None", 4, 6]}}, + {"nested3_scalar": {"arr": None}}, + [ + {"nested41_arr": {"arr_renamed": [1, None, 3]}}, + {"nested42_empty_arr": {"arr": []}}, + ], + ], + ) + assert client.json().get("test_None", "$..arr") == [ + ["bazzz", "None", 2, None, 2, "ba", "baz", 3], + ["zaz", "baz2", "buzz", 2, 1, 0, 1, "2", None, 2, 4, 5], + ["None", 4, 6], + None, + [], + ] # Fail with none-scalar value with pytest.raises(exceptions.ResponseError): - client.json().arrindex('test_None', '$..nested42_empty_arr.arr', {"arr":[]}) + client.json().arrindex( + "test_None", "$..nested42_empty_arr.arr", {"arr": []}) # Do not fail with none-scalar value in legacy mode - assert client.json().arrindex('test_None', '.[4][1].nested42_empty_arr.arr', '{"arr":[]}') == -1 + assert ( + client.json().arrindex( + "test_None", ".[4][1].nested42_empty_arr.arr", '{"arr":[]}' + ) + == -1 + ) # Test legacy (path begins with dot) # Test index of int scalar in single value - assert client.json().arrindex('test_num', '.[0].arr', 3) == 3 - assert client.json().arrindex('test_num', '.[0].arr', 9) == -1 + assert client.json().arrindex("test_num", ".[0].arr", 3) == 3 + assert client.json().arrindex("test_num", ".[0].arr", 9) == -1 with pytest.raises(exceptions.ResponseError): - client.json().arrindex('test_num', '.[0].arr_not', 3) + client.json().arrindex("test_num", ".[0].arr_not", 3) # Test index of string scalar in single value - assert client.json().arrindex('test_string', '.[0].arr', 'baz') == 3 - assert client.json().arrindex('test_string', '.[0].arr', 'faz') == -1 + assert client.json().arrindex("test_string", ".[0].arr", "baz") == 3 + assert client.json().arrindex("test_string", ".[0].arr", "faz") == -1 # Test index of None scalar in single value - assert client.json().arrindex('test_None', '.[0].arr', 'None') == 1 - assert client.json().arrindex('test_None', '..nested2_not_found.arr', 'None') == 0 + assert client.json().arrindex("test_None", ".[0].arr", "None") == 1 + assert client.json().arrindex( + "test_None", + "..nested2_not_found.arr", + "None") == 0 From 70466b2e206b215f37246c9f59286c74464a5e4b Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 3 Nov 2021 14:19:16 +0200 Subject: [PATCH 9/9] tests against decoders directly --- redis/commands/json/decoders.py | 2 +- tests/test_json.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/redis/commands/json/decoders.py b/redis/commands/json/decoders.py index ec71cee215..b19395c73b 100644 --- a/redis/commands/json/decoders.py +++ b/redis/commands/json/decoders.py @@ -53,7 +53,7 @@ def decode_list(b): if isinstance(b, list): return [nativestr(obj) for obj in b] elif isinstance(b, bytes): - return nativestr(b) + return unstring(nativestr(b)) elif isinstance(b, str): return unstring(b) return b diff --git a/tests/test_json.py b/tests/test_json.py index e50582fc7a..19b0c3262e 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -2,6 +2,7 @@ import redis from redis.commands.json.path import Path from redis import exceptions +from redis.commands.json.decoders import unstring, decode_list from .conftest import skip_ifmodversion_lt @@ -1374,3 +1375,13 @@ def test_arrindex_dollar(client): "test_None", "..nested2_not_found.arr", "None") == 0 + + +def test_decoders_and_unstring(): + assert unstring("4") == 4 + assert unstring("45.55") == 45.55 + assert unstring("hello world") == "hello world" + + assert decode_list(b"45.55") == 45.55 + assert decode_list("45.55") == 45.55 + assert decode_list(['hello', b'world']) == ['hello', 'world']