summaryrefslogtreecommitdiff
path: root/redis
diff options
context:
space:
mode:
authorChayim <chayim@users.noreply.github.com>2021-11-04 13:20:31 +0200
committerGitHub <noreply@github.com>2021-11-04 13:20:31 +0200
commit8d3c61598706eb049caa66a23501018f2f416673 (patch)
tree0aa3430a7ad3b1a9212194043a9e08c187ecd9fe /redis
parent72b49263f86f32c9df945433f21d3f3a7444d1a0 (diff)
downloadredis-py-8d3c61598706eb049caa66a23501018f2f416673.tar.gz
Support for json multipath ($) (#1663)
Diffstat (limited to 'redis')
-rw-r--r--redis/commands/helpers.py5
-rw-r--r--redis/commands/json/__init__.py45
-rw-r--r--redis/commands/json/commands.py4
-rw-r--r--redis/commands/json/decoders.py65
-rw-r--r--redis/commands/json/helpers.py25
5 files changed, 88 insertions, 56 deletions
diff --git a/redis/commands/helpers.py b/redis/commands/helpers.py
index 48ee556..2a4298c 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 3149bb8..d00627e 100644
--- a/redis/commands/json/__init__.py
+++ b/redis/commands/json/__init__.py
@@ -1,11 +1,10 @@
-from json import JSONDecoder, JSONEncoder
+from json import JSONDecoder, JSONEncoder, JSONDecodeError
from .decoders import (
- int_or_list,
- int_or_none
+ decode_list,
+ bulk_of_jsons,
)
-from .helpers import bulk_of_jsons
-from ..helpers import nativestr, delist
+from ..helpers import nativestr
from .commands import JSONCommands
@@ -46,19 +45,19 @@ class JSON(JSONCommands):
"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.STRAPPEND": int,
- "JSON.STRLEN": int,
- "JSON.ARRAPPEND": int,
- "JSON.ARRINDEX": int,
- "JSON.ARRINSERT": int,
- "JSON.ARRLEN": int_or_none,
+ "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": int,
- "JSON.OBJLEN": int,
- "JSON.OBJKEYS": delist,
- # "JSON.RESP": delist,
- "JSON.DEBUG": int_or_list,
+ "JSON.ARRTRIM": self._decode,
+ "JSON.OBJLEN": self._decode,
+ "JSON.OBJKEYS": self._decode,
+ "JSON.RESP": self._decode,
+ "JSON.DEBUG": self._decode,
}
self.client = client
@@ -77,9 +76,17 @@ class JSON(JSONCommands):
return obj
try:
- return self.__decoder__.decode(obj)
+ x = self.__decoder__.decode(obj)
+ if x is None:
+ raise TypeError
+ return x
except TypeError:
- return self.__decoder__.decode(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 fb00e22..716741c 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
@@ -192,7 +192,7 @@ class JSONCommands:
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 0ee102a..b19395c 100644
--- a/redis/commands/json/decoders.py
+++ b/redis/commands/json/decoders.py
@@ -1,12 +1,59 @@
-def int_or_list(b):
- if isinstance(b, int):
- return b
- else:
- return b
+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 int_or_none(b):
- if b is None:
- return None
- if isinstance(b, int):
+ 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
+
+
+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 unstring(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 8fb20d9..0000000
--- 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