-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python: adds json module and JSON.SET JSON.GET commands #1056
Conversation
python/python/glide/async_commands/redis_modules/json/commands.py
Outdated
Show resolved
Hide resolved
client: TRedisClient, | ||
key: str, | ||
path: str, | ||
value: TJson, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is always translated to a string, and always returned as a string, right?
If so, isn't it a bit confusing that you set a dictionary and get a string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, but I think it could be nice to be able to pass a dictionary (or another complex object) and also to be able to pass a string representation of the dictionary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Shachar, if we expect that the user will use python's json module to convert the response from a string to a complex object, it's relevant to the passed value too. If we except only string as a value, we can avoid importing 'json' module, which might not be in the standard libraries in other languages.
python/pytest.ini
Outdated
@@ -1,4 +1,4 @@ | |||
[pytest] | |||
markers = | |||
smoke_test: mark a test as a build verification testing. | |||
addopts = -k "not test_redis_modules.py" | |||
addopts = -k "not test_redis_modules.py and not test_json.py" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of adding this for each new module we add, can you check if we can move all module tests into a folder (tests/modules) and skip the whole folder by default?
TJson = Union[str, int, float, bool, None, List["TJson"], Dict[str, "TJson"]] | ||
|
||
|
||
class Json: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class doesn't save any state, not sure what's the gain of using it as a class rather than just a module, e.g.:
json.py
def set():
...
def get():
...
main.py
import json
json.set()
json.get()
python/python/glide/async_commands/redis_modules/json/commands.py
Outdated
Show resolved
Hide resolved
The key will be modified only if `value` is added as the last child in the specified `path`, or if the specified `path` acts as the parent of a new child being added. | ||
value (TJSON): The value to set at the specific path. | ||
set_condition (Optional[ConditionalChange]): Set the value only if the given condition is met (within the key or path). | ||
Equivalent to [`XX` | `NX`] in the Redis API. Defaults to None. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Equivalent to [`XX` | `NX`] in the Redis API. Defaults to None. | |
Equivalent to `XX`/`NX` in the Redis API. Defaults to None. |
python/python/glide/async_commands/redis_modules/json/commands.py
Outdated
Show resolved
Hide resolved
"[]" # Returns an empty array since the path '$.non_existing_path' does not exist in the JSON document stored at 'doc'. | ||
>>> import json | ||
>>> json.loads(await json_get(client, "doc", "$")) | ||
[{"a": 1.0, "b" :2}] # JSON object retrieved from the key "doc" using json.loads() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[{"a": 1.0, "b" :2}] # JSON object retrieved from the key "doc" using json.loads() | |
[{"a": 1.0, "b" :2}] # JSON object retrieved from the key "doc" using `json.loads()` |
indent: Optional[str] = None, | ||
newline: Optional[str] = None, | ||
space: Optional[str] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to merge this into JsonOptions
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding Yuri's proposal, are those options being utilized in other JSON functions as well?
Transaction test? |
If value isn't set because of `set_condition`, return None. | ||
|
||
Examples: | ||
>>> await json_set(client, "doc", "$", {'a': 1.0, 'b': 2}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand json examples to be more informative, for example:
import json
value = {'a': 1.0, 'b': 2}
json_str = json.dumps(value)
await json.set(client, "doc", "$", json_str)
'OK' # Indicates successful setting of the value at path '$' in the key stored at 'doc'.
and for json.get show how you use json to decode the response to python object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(we can have a single example and remove the second you did here)
Examples: | ||
>>> await json_set(client, "doc", "$", {'a': 1.0, 'b': 2}) | ||
'OK' # Indicates successful setting of the value at path '$' in the key stored at 'doc'. | ||
>>> await json_set(client, "doc", "$.a", 1.5 , ConditionalChange.ONLY_IF_DOES_NOT_EXIST) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(it should be changed after we'll change Json to a module instead of a class - but note that json_set function is incorrect)
"{\n \"$.a\": [\n 1.0\n ],\n \"$.b\": [\n 2\n ]\n}" # Returns the values at paths '$.a' and '$.b' in the JSON document stored at 'doc', with specified formatting options. | ||
>>> await json_get(client, "doc", "$.non_existing_path") | ||
"[]" # Returns an empty array since the path '$.non_existing_path' does not exist in the JSON document stored at 'doc'. | ||
>>> import json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets do the import on top and have one/two examples only that use json.loads
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Round
@barshaul round |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will continue reviewing tests tomorrow
python/pytest.ini
Outdated
@@ -1,4 +1,4 @@ | |||
[pytest] | |||
markers = | |||
smoke_test: mark a test as a build verification testing. | |||
addopts = -k "not test_redis_modules.py" | |||
addopts = --ignore "tests_redis_modules" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's already under tests folder, please rename the folder name to "redis_modules"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about changing the GitHub action that tests modules accordingly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to change a thing, the GitHub action overrides addopts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
( pytest --asyncio-mode=auto --override-ini=addopts= --load-module=$GITHUB_WORKSPACE/redisearch.so --load-module=$GITHUB_WORKSPACE/redisjson.so
)
@@ -0,0 +1,104 @@ | |||
# Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets remove the json folder, we can always add it later on if the json module will need to be extended
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General note: please add json to the all in glide/init
Equivalent to [`XX` | `NX`] in the Redis API. Defaults to None. | ||
|
||
Returns: | ||
Optional[TOK]: If the value is successfully set, return OK. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: return-> returns
(In both lines)
If value isn't set because of `set_condition`, return None. | ||
|
||
Examples: | ||
>>> from glide.async_commands.json import json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After adding it to all, change to '''
from glide import json as redisJson
Import json
...
|
||
Examples: | ||
>>> from glide.async_commands.json import json | ||
>>> import json as OuterJson |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Same as in the comment above - don't use OuterJson)
Args: | ||
client (TRedisClient): The Redis client to execute the command. | ||
key (str): The key of the JSON document. | ||
paths (ptional[Union[str, List[str]]]): The path or list of paths within the JSON document. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it defaults to None? What is the default path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: ptional-> optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default is root $
.
|
||
Examples: | ||
>>> import json as OuterJson | ||
>>> import json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
>>> import json as OuterJson | ||
>>> import json | ||
>>> OuterJson.loads(await json_get(client, "doc", "$")) | ||
[{"a": 1.0, "b" :2}] # JSON object retrieved from the key "doc" using json.loads() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json_get -> json.get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have it in two steps so you'll explain you're getting a jsonstring and you load it to a python object
paths = [paths] | ||
|
||
for path in paths: | ||
args.append(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not using extend?
Args: | ||
client (TRedisClient): The Redis client to execute the command. | ||
key (str): The key of the JSON document. | ||
paths (Optional[Union[str, List[str]]]): The path or list of paths within the JSON document. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets write that the default is root, it feels not readable enough without this info
import json as OuterJson | ||
|
||
import pytest | ||
from glide import json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to be consistent, please use the full import path
class TestJson: | ||
@pytest.mark.parametrize("cluster_mode", [True, False]) | ||
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) | ||
async def test_json_module_loadded(self, redis_client: TRedisClient): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loadded => is_loaded
key = get_random_string(5) | ||
|
||
assert ( | ||
await json.set(redis_client, key, "$", OuterJson.dumps({"a": 1.0, "b": 2})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets save it in a variable, something like:
json_value = {"a": 1.0, "b": 2}
await json.set(redis_client, key, "$", OuterJson.dumps(json_value))
...
result = await json.get(redis_client, key, ".")
assert isinstance(result, str)
assert OuterJson.loads(result) == json_value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add test with boolean too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove this file? looks like these tests are a part of the specific module tests
@@ -125,6 +125,8 @@ enum RequestType { | |||
XGroupDestroy = 81; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this change to CHANGELOG
'OK' # Indicates successful setting of the value at path '$' in the key stored at 'doc'. | ||
>>> await redisJson.get(client, "doc", "$") | ||
"[{\"a\":1.0,\"b\":2}]" # Returns the value at path '$' in the JSON document stored at 'doc'. | ||
>>> json_get = await redisJson.get(client, "doc", "$") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove duplication of json get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix & merge
0ab2c95
to
e3633d9
Compare
Issue #, if available:
Description of changes:
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.