Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
|
|
@ -0,0 +1,366 @@
|
|||
import asyncio
|
||||
import os
|
||||
from json import JSONDecodeError, JSONDecoder, JSONEncoder, loads
|
||||
from typing import Literal
|
||||
|
||||
import redis
|
||||
|
||||
from ..helpers import (
|
||||
apply_module_callbacks,
|
||||
get_legacy_responses,
|
||||
get_protocol_version,
|
||||
nativestr,
|
||||
)
|
||||
from .commands import FPHAType, JSONCommands
|
||||
from .decoders import bulk_of_jsons, decode_list
|
||||
|
||||
|
||||
class _JSONBase(JSONCommands):
|
||||
"""
|
||||
Create a client for talking to json.
|
||||
|
||||
:param decoder:
|
||||
:type json.JSONDecoder: An instance of json.JSONDecoder
|
||||
|
||||
:param encoder:
|
||||
:type json.JSONEncoder: An instance of json.JSONEncoder
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, client, version=None, decoder=JSONDecoder(), encoder=JSONEncoder()
|
||||
):
|
||||
"""
|
||||
Create a client for talking to json.
|
||||
|
||||
:param decoder:
|
||||
:type json.JSONDecoder: An instance of json.JSONDecoder
|
||||
|
||||
:param encoder:
|
||||
:type json.JSONEncoder: An instance of json.JSONEncoder
|
||||
"""
|
||||
# Set the module commands' callbacks
|
||||
_MODULE_CALLBACKS = {
|
||||
"JSON.ARRPOP": self._decode,
|
||||
"JSON.DEBUG": self._decode,
|
||||
"JSON.GET": self._decode,
|
||||
"JSON.MERGE": lambda r: r and nativestr(r) == "OK",
|
||||
"JSON.MGET": bulk_of_jsons(self._decode),
|
||||
"JSON.MSET": lambda r: r and nativestr(r) == "OK",
|
||||
"JSON.RESP": self._decode,
|
||||
"JSON.SET": lambda r: r and nativestr(r) == "OK",
|
||||
"JSON.TOGGLE": self._decode,
|
||||
}
|
||||
|
||||
_RESP2_MODULE_CALLBACKS = {
|
||||
"JSON.ARRAPPEND": self._decode,
|
||||
"JSON.ARRINDEX": self._decode,
|
||||
"JSON.ARRINSERT": self._decode,
|
||||
"JSON.ARRLEN": self._decode,
|
||||
"JSON.ARRTRIM": self._decode,
|
||||
"JSON.CLEAR": int,
|
||||
"JSON.DEL": int,
|
||||
"JSON.FORGET": int,
|
||||
"JSON.GET": self._decode,
|
||||
"JSON.NUMINCRBY": lambda r, **kwargs: self._decode(r),
|
||||
"JSON.NUMMULTBY": lambda r, **kwargs: self._decode(r),
|
||||
"JSON.OBJKEYS": self._decode,
|
||||
"JSON.STRAPPEND": self._decode,
|
||||
"JSON.OBJLEN": self._decode,
|
||||
"JSON.STRLEN": self._decode,
|
||||
"JSON.TOGGLE": self._decode,
|
||||
}
|
||||
|
||||
_RESP3_MODULE_CALLBACKS = {}
|
||||
# RESP2 wire normalised to the unified shape from the reverted
|
||||
# unification PR: NUMINCRBY/NUMMULTBY are always arrays,
|
||||
# JSON.RESP uses native floats, and missing JSON.TYPE keys stay
|
||||
# ``None`` instead of becoming ``[None]``.
|
||||
_RESP2_UNIFIED_MODULE_CALLBACKS = {
|
||||
"JSON.CLEAR": int,
|
||||
"JSON.DEL": int,
|
||||
"JSON.FORGET": int,
|
||||
"JSON.NUMINCRBY": self._decode_json_numop,
|
||||
"JSON.NUMMULTBY": self._decode_json_numop,
|
||||
"JSON.RESP": self._decode_resp_command_unified,
|
||||
"JSON.TYPE": lambda r: [r] if r is not None else r,
|
||||
}
|
||||
_RESP3_UNIFIED_MODULE_CALLBACKS = {
|
||||
"JSON.TYPE": lambda r: None if r == [None] else r,
|
||||
}
|
||||
# RESP3 wire normalised back to today's RESP2 Python shapes:
|
||||
# keep ``nativestr`` for OBJKEYS, unwrap JSON.TYPE one level so
|
||||
# legacy paths return scalars and missing keys return ``None``,
|
||||
# and re-encode native floats inside JSON.RESP back to string
|
||||
# form. NUMINCRBY/NUMMULTBY use the command path captured at the
|
||||
# call site to unwrap legacy paths while preserving JSONPath arrays.
|
||||
_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS = {
|
||||
"JSON.NUMINCRBY": self._decode_resp3_legacy_numop,
|
||||
"JSON.NUMMULTBY": self._decode_resp3_legacy_numop,
|
||||
"JSON.OBJKEYS": self._decode,
|
||||
"JSON.RESP": self._resp_floats_to_str_command,
|
||||
"JSON.TYPE": lambda r: r[0] if isinstance(r, list) and len(r) == 1 else r,
|
||||
}
|
||||
|
||||
self.client = client
|
||||
self.execute_command = client.execute_command
|
||||
self.MODULE_VERSION = version
|
||||
|
||||
self._MODULE_CALLBACKS = apply_module_callbacks(
|
||||
get_protocol_version(self.client),
|
||||
get_legacy_responses(self.client),
|
||||
common=_MODULE_CALLBACKS,
|
||||
resp2=_RESP2_MODULE_CALLBACKS,
|
||||
resp3=_RESP3_MODULE_CALLBACKS,
|
||||
resp2_unified=_RESP2_UNIFIED_MODULE_CALLBACKS,
|
||||
resp3_unified=_RESP3_UNIFIED_MODULE_CALLBACKS,
|
||||
resp3_to_resp2_legacy=_RESP3_TO_RESP2_LEGACY_MODULE_CALLBACKS,
|
||||
)
|
||||
|
||||
for key, value in self._MODULE_CALLBACKS.items():
|
||||
self.client.set_response_callback(key, value)
|
||||
|
||||
self.__encoder__ = encoder
|
||||
self.__decoder__ = decoder
|
||||
|
||||
def _decode(self, obj):
|
||||
"""Get the decoder."""
|
||||
if obj is None:
|
||||
return obj
|
||||
|
||||
try:
|
||||
x = self.__decoder__.decode(obj)
|
||||
if x is None:
|
||||
raise TypeError
|
||||
return x
|
||||
except TypeError:
|
||||
try:
|
||||
return self.__decoder__.decode(obj.decode())
|
||||
except AttributeError:
|
||||
return decode_list(obj)
|
||||
except (AttributeError, JSONDecodeError):
|
||||
return decode_list(obj)
|
||||
|
||||
def _decode_json_numop(self, obj, **kwargs):
|
||||
"""Decode a JSON.NUMINCRBY / JSON.NUMMULTBY result and normalise
|
||||
it to the unified array form.
|
||||
|
||||
RESP2 wire returns a JSON bulk string — a scalar for legacy
|
||||
paths and a JSON-encoded list for dollar paths. RESP3 wire
|
||||
returns a native list. The unified shape is always a list.
|
||||
"""
|
||||
if obj is None:
|
||||
return obj
|
||||
try:
|
||||
result = self.__decoder__.decode(
|
||||
obj if isinstance(obj, str) else obj.decode()
|
||||
)
|
||||
except (AttributeError, JSONDecodeError):
|
||||
return obj
|
||||
if not isinstance(result, list):
|
||||
result = [result]
|
||||
return result
|
||||
|
||||
def _decode_resp3_legacy_numop(self, obj, **kwargs):
|
||||
"""Decode RESP3 JSON numeric operations back to legacy RESP2 shape.
|
||||
|
||||
RedisJSON returns a RESP3 array for both legacy paths (``.foo``)
|
||||
and JSONPath paths (``$.foo``). The command builder records the
|
||||
path so default RESP3-wire legacy clients can keep v8 scalar
|
||||
results for legacy paths while leaving JSONPath results as lists.
|
||||
"""
|
||||
result = self._decode(obj)
|
||||
path = kwargs.get("_json_path")
|
||||
if (
|
||||
isinstance(path, str)
|
||||
and not path.startswith("$")
|
||||
and isinstance(result, list)
|
||||
and len(result) == 1
|
||||
):
|
||||
return result[0]
|
||||
return result
|
||||
|
||||
def _decode_resp_command_unified(self, obj):
|
||||
"""Decode JSON.RESP and lift string-encoded floats inside the
|
||||
nested response to native ``float`` values so the unified shape
|
||||
matches the RESP3 wire.
|
||||
"""
|
||||
return self._convert_resp_floats(self._decode(obj))
|
||||
|
||||
def _resp_floats_to_str_command(self, obj):
|
||||
"""Decode JSON.RESP and re-encode native ``float`` values back
|
||||
to their string form so the legacy RESP2 shape is preserved
|
||||
when the wire is RESP3.
|
||||
"""
|
||||
return self._resp_floats_to_str(self._decode(obj))
|
||||
|
||||
@staticmethod
|
||||
def _convert_resp_floats(obj):
|
||||
"""Recursively convert string-encoded JSON floats.
|
||||
|
||||
RESP2 has no native double type, so JSON.RESP returns JSON floats as
|
||||
bulk strings. Any string/bytes leaf that parses as ``float`` is the
|
||||
unified representation of such a value; non-numeric strings are left
|
||||
untouched.
|
||||
"""
|
||||
if isinstance(obj, list):
|
||||
return [_JSONBase._convert_resp_floats(item) for item in obj]
|
||||
if isinstance(obj, (str, bytes)):
|
||||
value = obj.decode() if isinstance(obj, bytes) else obj
|
||||
try:
|
||||
return float(value)
|
||||
except (ValueError, OverflowError):
|
||||
return obj
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def _resp_floats_to_str(obj):
|
||||
"""Recursively walk ``obj`` and convert native ``float`` values
|
||||
back to their string-encoded form. Lists are walked
|
||||
element-wise; non-float leaves are returned unchanged.
|
||||
"""
|
||||
if isinstance(obj, list):
|
||||
return [_JSONBase._resp_floats_to_str(item) for item in obj]
|
||||
if isinstance(obj, float):
|
||||
return str(obj)
|
||||
return obj
|
||||
|
||||
def _encode(self, obj):
|
||||
"""Get the encoder."""
|
||||
return self.__encoder__.encode(obj)
|
||||
|
||||
def pipeline(self, transaction=True, shard_hint=None):
|
||||
"""Creates a pipeline for the JSON module, that can be used for executing
|
||||
JSON commands, as well as classic core commands.
|
||||
|
||||
Usage example:
|
||||
|
||||
r = redis.Redis()
|
||||
pipe = r.json().pipeline()
|
||||
pipe.jsonset('foo', '.', {'hello!': 'world'})
|
||||
pipe.jsonget('foo')
|
||||
pipe.jsonget('notakey')
|
||||
"""
|
||||
if isinstance(self.client, redis.RedisCluster):
|
||||
p = ClusterPipeline(
|
||||
nodes_manager=self.client.nodes_manager,
|
||||
commands_parser=self.client.commands_parser,
|
||||
startup_nodes=self.client.nodes_manager.startup_nodes,
|
||||
result_callbacks=self.client.result_callbacks,
|
||||
cluster_response_callbacks=self.client.cluster_response_callbacks,
|
||||
cluster_error_retry_attempts=self.client.retry.get_retries(),
|
||||
read_from_replicas=self.client.read_from_replicas,
|
||||
reinitialize_steps=self.client.reinitialize_steps,
|
||||
lock=self.client._lock,
|
||||
)
|
||||
|
||||
else:
|
||||
p = Pipeline(
|
||||
connection_pool=self.client.connection_pool,
|
||||
response_callbacks=dict(self.client.response_callbacks),
|
||||
transaction=transaction,
|
||||
shard_hint=shard_hint,
|
||||
)
|
||||
|
||||
p._encode = self._encode
|
||||
p._decode = self._decode
|
||||
return p
|
||||
|
||||
|
||||
class ClusterPipeline(JSONCommands, redis.cluster.ClusterPipeline):
|
||||
"""Cluster pipeline for the module."""
|
||||
|
||||
|
||||
class Pipeline(JSONCommands, redis.client.Pipeline):
|
||||
"""Pipeline for the module."""
|
||||
|
||||
|
||||
class JSON(_JSONBase):
|
||||
_is_async_client: Literal[False] = False
|
||||
|
||||
|
||||
class AsyncJSON(_JSONBase):
|
||||
_is_async_client: Literal[True] = True
|
||||
|
||||
async def set_file(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
file_name: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> bool | None:
|
||||
"""
|
||||
Set the JSON value at key ``name`` under the ``path`` to the content
|
||||
of the json file ``file_name``.
|
||||
|
||||
This runs the blocking file read in a thread pool to avoid blocking
|
||||
the event loop.
|
||||
"""
|
||||
|
||||
def _read_file(fp: str) -> dict:
|
||||
with open(fp) as f:
|
||||
return loads(f.read())
|
||||
|
||||
file_content = await asyncio.to_thread(_read_file, file_name)
|
||||
return await self.set(
|
||||
name,
|
||||
path,
|
||||
file_content,
|
||||
nx=nx,
|
||||
xx=xx,
|
||||
decode_keys=decode_keys,
|
||||
fpha=fpha,
|
||||
)
|
||||
|
||||
async def set_path(
|
||||
self,
|
||||
json_path: str,
|
||||
root_folder: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> dict[str, bool]:
|
||||
"""
|
||||
Iterate over ``root_folder`` and set each JSON file to a value
|
||||
under ``json_path`` with the file name as the key.
|
||||
|
||||
This method runs blocking filesystem operations (os.walk and file reads)
|
||||
in a thread pool to avoid blocking the event loop.
|
||||
"""
|
||||
|
||||
def _walk_directory(folder: str) -> list[str]:
|
||||
"""Walk directory and return list of file paths (runs in thread pool)."""
|
||||
file_paths = []
|
||||
for root, dirs, files in os.walk(folder):
|
||||
for file in files:
|
||||
file_paths.append(os.path.join(root, file))
|
||||
return file_paths
|
||||
|
||||
set_files_result = {}
|
||||
|
||||
# Run blocking os.walk in thread pool
|
||||
file_paths = await asyncio.to_thread(_walk_directory, root_folder)
|
||||
|
||||
for file_path in file_paths:
|
||||
try:
|
||||
# TODO: rsplit(".") splits on all dots, mishandling paths
|
||||
# with dots in directories (e.g. /data/v1.2/file.json).
|
||||
# Should be rsplit(".", 1) — fix in a separate PR.
|
||||
file_name = file_path.rsplit(".")[0]
|
||||
await self.set_file(
|
||||
file_name,
|
||||
json_path,
|
||||
file_path,
|
||||
nx=nx,
|
||||
xx=xx,
|
||||
decode_keys=decode_keys,
|
||||
fpha=fpha,
|
||||
)
|
||||
set_files_result[file_path] = True
|
||||
except JSONDecodeError:
|
||||
set_files_result[file_path] = False
|
||||
|
||||
return set_files_result
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
|||
from typing import List, Mapping, Union
|
||||
|
||||
JsonType = Union[
|
||||
str, int, float, bool, None, Mapping[str, "JsonType"], List["JsonType"]
|
||||
]
|
||||
|
|
@ -0,0 +1,881 @@
|
|||
import os
|
||||
from enum import Enum
|
||||
from json import JSONDecodeError, loads
|
||||
from typing import Any, Awaitable, overload
|
||||
|
||||
from redis.exceptions import DataError
|
||||
from redis.typing import AsyncClientProtocol, SyncClientProtocol
|
||||
from redis.utils import deprecated_function
|
||||
|
||||
from ._util import JsonType
|
||||
from .decoders import decode_dict_keys
|
||||
from .path import Path
|
||||
|
||||
|
||||
class FPHAType(str, Enum):
|
||||
"""Floating-point type options for homogeneous array storage in JSON.SET.
|
||||
|
||||
Used with the ``fpha`` parameter to force Redis to store all FP arrays
|
||||
using the specified floating-point type.
|
||||
"""
|
||||
|
||||
BF16 = "BF16"
|
||||
FP16 = "FP16"
|
||||
FP32 = "FP32"
|
||||
FP64 = "FP64"
|
||||
|
||||
@classmethod
|
||||
def from_value(cls, value: "FPHAType | str") -> "FPHAType":
|
||||
"""Convert a string or FPHAType instance to a validated FPHAType.
|
||||
|
||||
Args:
|
||||
value: An ``FPHAType`` member or a case-insensitive string
|
||||
(e.g. ``"bf16"``, ``"FP32"``).
|
||||
|
||||
Returns:
|
||||
The corresponding ``FPHAType`` enum member.
|
||||
|
||||
Raises:
|
||||
DataError: If the string does not match any valid FPHA type.
|
||||
"""
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
try:
|
||||
return cls(value.upper())
|
||||
except ValueError:
|
||||
raise DataError(
|
||||
f"Invalid FPHA type: {value}. "
|
||||
f"Must be one of {', '.join(t.value for t in cls)}"
|
||||
)
|
||||
|
||||
|
||||
class JSONCommands:
|
||||
"""json commands."""
|
||||
|
||||
@overload
|
||||
def arrappend(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str | None = Path.root_path(),
|
||||
*args: JsonType,
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def arrappend(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str | None = Path.root_path(),
|
||||
*args: JsonType,
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def arrappend(
|
||||
self, name: str, path: str | None = Path.root_path(), *args: JsonType
|
||||
) -> (int | list[int | None] | None) | Awaitable[int | list[int | None] | None]:
|
||||
"""Append the objects ``args`` to the array under the
|
||||
``path` in key ``name``.
|
||||
|
||||
For more information see `JSON.ARRAPPEND <https://redis.io/commands/json.arrappend>`_..
|
||||
""" # noqa
|
||||
pieces = [name, str(path)]
|
||||
for o in args:
|
||||
pieces.append(self._encode(o))
|
||||
return self.execute_command("JSON.ARRAPPEND", *pieces)
|
||||
|
||||
@overload
|
||||
def arrindex(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
scalar: int,
|
||||
start: int | None = None,
|
||||
stop: int | None = None,
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def arrindex(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
scalar: int,
|
||||
start: int | None = None,
|
||||
stop: int | None = None,
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def arrindex(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
scalar: int,
|
||||
start: int | None = None,
|
||||
stop: int | None = None,
|
||||
) -> (int | list[int | None] | None) | Awaitable[int | list[int | None] | None]:
|
||||
"""
|
||||
Return the index of ``scalar`` in the JSON array under ``path`` at key
|
||||
``name``.
|
||||
|
||||
The search can be limited using the optional inclusive ``start``
|
||||
and exclusive ``stop`` indices.
|
||||
|
||||
For more information see `JSON.ARRINDEX <https://redis.io/commands/json.arrindex>`_.
|
||||
""" # noqa
|
||||
pieces = [name, str(path), self._encode(scalar)]
|
||||
if start is not None:
|
||||
pieces.append(start)
|
||||
if stop is not None:
|
||||
pieces.append(stop)
|
||||
|
||||
return self.execute_command("JSON.ARRINDEX", *pieces, keys=[name])
|
||||
|
||||
@overload
|
||||
def arrinsert(
|
||||
self: SyncClientProtocol, name: str, path: str, index: int, *args: JsonType
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def arrinsert(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
index: int,
|
||||
*args: JsonType,
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def arrinsert(self, name: str, path: str, index: int, *args: JsonType) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Insert the objects ``args`` to the array at index ``index``
|
||||
under the ``path` in key ``name``.
|
||||
|
||||
For more information see `JSON.ARRINSERT <https://redis.io/commands/json.arrinsert>`_.
|
||||
""" # noqa
|
||||
pieces = [name, str(path), index]
|
||||
for o in args:
|
||||
pieces.append(self._encode(o))
|
||||
return self.execute_command("JSON.ARRINSERT", *pieces)
|
||||
|
||||
@overload
|
||||
def arrlen(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def arrlen(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def arrlen(self, name: str, path: str | None = Path.root_path()) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Return the length of the array JSON value under ``path``
|
||||
at key``name``.
|
||||
|
||||
For more information see `JSON.ARRLEN <https://redis.io/commands/json.arrlen>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.ARRLEN", name, str(path), keys=[name])
|
||||
|
||||
@overload
|
||||
def arrpop(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str | None = Path.root_path(),
|
||||
index: int | None = -1,
|
||||
) -> JsonType | str | list[Any] | None: ...
|
||||
|
||||
@overload
|
||||
def arrpop(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str | None = Path.root_path(),
|
||||
index: int | None = -1,
|
||||
) -> Awaitable[JsonType | str | list[Any] | None]: ...
|
||||
|
||||
def arrpop(
|
||||
self,
|
||||
name: str,
|
||||
path: str | None = Path.root_path(),
|
||||
index: int | None = -1,
|
||||
) -> (JsonType | str | list[Any] | None) | Awaitable[
|
||||
JsonType | str | list[Any] | None
|
||||
]:
|
||||
"""Pop the element at ``index`` in the array JSON value under
|
||||
``path`` at key ``name``.
|
||||
|
||||
For more information see `JSON.ARRPOP <https://redis.io/commands/json.arrpop>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.ARRPOP", name, str(path), index)
|
||||
|
||||
@overload
|
||||
def arrtrim(
|
||||
self: SyncClientProtocol, name: str, path: str, start: int, stop: int
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def arrtrim(
|
||||
self: AsyncClientProtocol, name: str, path: str, start: int, stop: int
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def arrtrim(self, name: str, path: str, start: int, stop: int) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Trim the array JSON value under ``path`` at key ``name`` to the
|
||||
inclusive range given by ``start`` and ``stop``.
|
||||
|
||||
For more information see `JSON.ARRTRIM <https://redis.io/commands/json.arrtrim>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.ARRTRIM", name, str(path), start, stop)
|
||||
|
||||
@overload
|
||||
def type(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> str | None | list[str | None] | list[list[str]]: ...
|
||||
|
||||
@overload
|
||||
def type(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[str | None | list[str | None] | list[list[str]]]: ...
|
||||
|
||||
def type(self, name: str, path: str | None = Path.root_path()) -> (
|
||||
str | None | list[str | None] | list[list[str]]
|
||||
) | Awaitable[str | None | list[str | None] | list[list[str]]]:
|
||||
"""Get the type of the JSON value under ``path`` from key ``name``.
|
||||
|
||||
For more information see `JSON.TYPE <https://redis.io/commands/json.type>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.TYPE", name, str(path), keys=[name])
|
||||
|
||||
@overload
|
||||
def resp(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Any: ...
|
||||
|
||||
@overload
|
||||
def resp(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[Any]: ...
|
||||
|
||||
def resp(
|
||||
self, name: str, path: str | None = Path.root_path()
|
||||
) -> Any | Awaitable[Any]:
|
||||
"""Return the JSON value under ``path`` at key ``name``.
|
||||
|
||||
For more information see `JSON.RESP <https://redis.io/commands/json.resp>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.RESP", name, str(path), keys=[name])
|
||||
|
||||
@overload
|
||||
def objkeys(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> list[str] | list[list[str] | None] | None: ...
|
||||
|
||||
@overload
|
||||
def objkeys(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[list[str] | list[list[str] | None] | None]: ...
|
||||
|
||||
def objkeys(self, name: str, path: str | None = Path.root_path()) -> (
|
||||
list[str] | list[list[str] | None] | None
|
||||
) | Awaitable[list[str] | list[list[str] | None] | None]:
|
||||
"""Return the key names in the dictionary JSON value under ``path`` at
|
||||
key ``name``.
|
||||
|
||||
For more information see `JSON.OBJKEYS <https://redis.io/commands/json.objkeys>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.OBJKEYS", name, str(path), keys=[name])
|
||||
|
||||
@overload
|
||||
def objlen(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def objlen(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def objlen(self, name: str, path: str | None = Path.root_path()) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Return the length of the dictionary JSON value under ``path`` at key
|
||||
``name``.
|
||||
|
||||
For more information see `JSON.OBJLEN <https://redis.io/commands/json.objlen>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.OBJLEN", name, str(path), keys=[name])
|
||||
|
||||
@overload
|
||||
def numincrby(
|
||||
self: SyncClientProtocol, name: str, path: str, number: int
|
||||
) -> int | float | list[int | float | None]: ...
|
||||
|
||||
@overload
|
||||
def numincrby(
|
||||
self: AsyncClientProtocol, name: str, path: str, number: int
|
||||
) -> Awaitable[int | float | list[int | float | None]]: ...
|
||||
|
||||
def numincrby(self, name: str, path: str, number: int) -> (
|
||||
int | float | list[int | float | None]
|
||||
) | Awaitable[int | float | list[int | float | None]]:
|
||||
"""Increment the numeric (integer or floating point) JSON value under
|
||||
``path`` at key ``name`` by the provided ``number``.
|
||||
|
||||
For more information see `JSON.NUMINCRBY <https://redis.io/commands/json.numincrby>`_.
|
||||
""" # noqa
|
||||
path = str(path)
|
||||
return self.execute_command(
|
||||
"JSON.NUMINCRBY", name, path, self._encode(number), _json_path=path
|
||||
)
|
||||
|
||||
@overload
|
||||
def nummultby(
|
||||
self: SyncClientProtocol, name: str, path: str, number: int
|
||||
) -> int | float | list[int | float | None]: ...
|
||||
|
||||
@overload
|
||||
def nummultby(
|
||||
self: AsyncClientProtocol, name: str, path: str, number: int
|
||||
) -> Awaitable[int | float | list[int | float | None]]: ...
|
||||
|
||||
@deprecated_function(version="4.0.0", reason="deprecated since redisjson 1.0.0")
|
||||
def nummultby(self, name: str, path: str, number: int) -> (
|
||||
int | float | list[int | float | None]
|
||||
) | Awaitable[int | float | list[int | float | None]]:
|
||||
"""Multiply the numeric (integer or floating point) JSON value under
|
||||
``path`` at key ``name`` with the provided ``number``.
|
||||
|
||||
For more information see `JSON.NUMMULTBY <https://redis.io/commands/json.nummultby>`_.
|
||||
""" # noqa
|
||||
path = str(path)
|
||||
return self.execute_command(
|
||||
"JSON.NUMMULTBY", name, path, self._encode(number), _json_path=path
|
||||
)
|
||||
|
||||
@overload
|
||||
def clear(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> int: ...
|
||||
|
||||
@overload
|
||||
def clear(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[int]: ...
|
||||
|
||||
def clear(
|
||||
self, name: str, path: str | None = Path.root_path()
|
||||
) -> int | Awaitable[int]:
|
||||
"""Empty arrays and objects (to have zero slots/keys without deleting the
|
||||
array/object).
|
||||
|
||||
Return the count of cleared paths (ignoring non-array and non-objects
|
||||
paths).
|
||||
|
||||
For more information see `JSON.CLEAR <https://redis.io/commands/json.clear>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.CLEAR", name, str(path))
|
||||
|
||||
@overload
|
||||
def delete(
|
||||
self: SyncClientProtocol, key: str, path: str | None = Path.root_path()
|
||||
) -> int: ...
|
||||
|
||||
@overload
|
||||
def delete(
|
||||
self: AsyncClientProtocol, key: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[int]: ...
|
||||
|
||||
def delete(
|
||||
self, key: str, path: str | None = Path.root_path()
|
||||
) -> int | Awaitable[int]:
|
||||
"""Delete the JSON value stored at key ``key`` under ``path``.
|
||||
|
||||
For more information see `JSON.DEL <https://redis.io/commands/json.del>`_.
|
||||
"""
|
||||
return self.execute_command("JSON.DEL", key, str(path))
|
||||
|
||||
# forget is an alias for delete
|
||||
forget = delete
|
||||
|
||||
@overload
|
||||
def get(
|
||||
self: SyncClientProtocol, name: str, *args, no_escape: bool | None = False
|
||||
) -> JsonType | None: ...
|
||||
|
||||
@overload
|
||||
def get(
|
||||
self: AsyncClientProtocol, name: str, *args, no_escape: bool | None = False
|
||||
) -> Awaitable[JsonType | None]: ...
|
||||
|
||||
def get(self, name: str, *args, no_escape: bool | None = False) -> (
|
||||
JsonType | None
|
||||
) | Awaitable[JsonType | None]:
|
||||
"""
|
||||
Get the object stored as a JSON value at key ``name``.
|
||||
|
||||
``args`` is zero or more paths, and defaults to root path
|
||||
```no_escape`` is a boolean flag to add no_escape option to get
|
||||
non-ascii characters
|
||||
|
||||
For more information see `JSON.GET <https://redis.io/commands/json.get>`_.
|
||||
""" # noqa
|
||||
pieces = [name]
|
||||
if no_escape:
|
||||
pieces.append("noescape")
|
||||
|
||||
if len(args) == 0:
|
||||
pieces.append(Path.root_path())
|
||||
|
||||
else:
|
||||
for p in args:
|
||||
pieces.append(str(p))
|
||||
|
||||
# Handle case where key doesn't exist. The JSONDecoder would raise a
|
||||
# TypeError exception since it can't decode None
|
||||
try:
|
||||
return self.execute_command("JSON.GET", *pieces, keys=[name])
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
@overload
|
||||
def mget(
|
||||
self: SyncClientProtocol, keys: list[str], path: str
|
||||
) -> list[JsonType | None]: ...
|
||||
|
||||
@overload
|
||||
def mget(
|
||||
self: AsyncClientProtocol, keys: list[str], path: str
|
||||
) -> Awaitable[list[JsonType | None]]: ...
|
||||
|
||||
def mget(
|
||||
self, keys: list[str], path: str
|
||||
) -> list[JsonType | None] | Awaitable[list[JsonType | None]]:
|
||||
"""
|
||||
Get the objects stored as a JSON values under ``path``. ``keys``
|
||||
is a list of one or more keys.
|
||||
|
||||
For more information see `JSON.MGET <https://redis.io/commands/json.mget>`_.
|
||||
""" # noqa
|
||||
pieces = []
|
||||
pieces += keys
|
||||
pieces.append(str(path))
|
||||
return self.execute_command("JSON.MGET", *pieces, keys=keys)
|
||||
|
||||
@overload
|
||||
def set(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> bool | None: ...
|
||||
|
||||
@overload
|
||||
def set(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> Awaitable[bool | None]: ...
|
||||
|
||||
def set(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> (bool | None) | Awaitable[bool | None]:
|
||||
"""
|
||||
Set the JSON value at key ``name`` under the ``path`` to ``obj``.
|
||||
|
||||
``nx`` if set to True, set ``value`` only if it does not exist.
|
||||
``xx`` if set to True, set ``value`` only if it exists.
|
||||
``decode_keys`` If set to True, the keys of ``obj`` will be decoded
|
||||
with utf-8.
|
||||
``fpha`` if set, forces Redis to use the specified floating-point type
|
||||
for storing all FP homogeneous arrays in ``obj``.
|
||||
Accepts a :class:`FPHAType` enum value or a string
|
||||
(``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).
|
||||
|
||||
For the purpose of using this within a pipeline, this command is also
|
||||
aliased to JSON.SET.
|
||||
|
||||
For more information see `JSON.SET <https://redis.io/commands/json.set>`_.
|
||||
"""
|
||||
if decode_keys:
|
||||
obj = decode_dict_keys(obj)
|
||||
|
||||
pieces = [name, str(path), self._encode(obj)]
|
||||
|
||||
# Handle existential modifiers
|
||||
if nx and xx:
|
||||
raise Exception(
|
||||
"nx and xx are mutually exclusive: use one, the "
|
||||
"other or neither - but not both"
|
||||
)
|
||||
elif nx:
|
||||
pieces.append("NX")
|
||||
elif xx:
|
||||
pieces.append("XX")
|
||||
|
||||
if fpha is not None:
|
||||
pieces.extend(["FPHA", FPHAType.from_value(fpha).value])
|
||||
|
||||
return self.execute_command("JSON.SET", *pieces)
|
||||
|
||||
@overload
|
||||
def mset(
|
||||
self: SyncClientProtocol, triplets: list[tuple[str, str, JsonType]]
|
||||
) -> bool: ...
|
||||
|
||||
@overload
|
||||
def mset(
|
||||
self: AsyncClientProtocol, triplets: list[tuple[str, str, JsonType]]
|
||||
) -> Awaitable[bool]: ...
|
||||
|
||||
def mset(self, triplets: list[tuple[str, str, JsonType]]) -> bool | Awaitable[bool]:
|
||||
"""
|
||||
Set the JSON value at key ``name`` under the ``path`` to ``obj``
|
||||
for one or more keys.
|
||||
|
||||
``triplets`` is a list of one or more triplets of key, path, value.
|
||||
|
||||
For the purpose of using this within a pipeline, this command is also
|
||||
aliased to JSON.MSET.
|
||||
|
||||
For more information see `JSON.MSET <https://redis.io/commands/json.mset>`_.
|
||||
"""
|
||||
pieces = []
|
||||
for triplet in triplets:
|
||||
pieces.extend([triplet[0], str(triplet[1]), self._encode(triplet[2])])
|
||||
return self.execute_command("JSON.MSET", *pieces)
|
||||
|
||||
@overload
|
||||
def merge(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
decode_keys: bool | None = False,
|
||||
) -> bool: ...
|
||||
|
||||
@overload
|
||||
def merge(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
decode_keys: bool | None = False,
|
||||
) -> Awaitable[bool]: ...
|
||||
|
||||
def merge(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
obj: JsonType,
|
||||
decode_keys: bool | None = False,
|
||||
) -> bool | Awaitable[bool]:
|
||||
"""
|
||||
Merges a given JSON value into matching paths. Consequently, JSON values
|
||||
at matching paths are updated, deleted, or expanded with new children
|
||||
|
||||
``decode_keys`` If set to True, the keys of ``obj`` will be decoded
|
||||
with utf-8.
|
||||
|
||||
For more information see `JSON.MERGE <https://redis.io/commands/json.merge>`_.
|
||||
"""
|
||||
if decode_keys:
|
||||
obj = decode_dict_keys(obj)
|
||||
|
||||
pieces = [name, str(path), self._encode(obj)]
|
||||
|
||||
return self.execute_command("JSON.MERGE", *pieces)
|
||||
|
||||
@overload
|
||||
def set_file(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
file_name: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> bool | None: ...
|
||||
|
||||
@overload
|
||||
def set_file(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
path: str,
|
||||
file_name: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> Awaitable[bool | None]: ...
|
||||
|
||||
def set_file(
|
||||
self,
|
||||
name: str,
|
||||
path: str,
|
||||
file_name: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> (bool | None) | Awaitable[bool | None]:
|
||||
"""
|
||||
Set the JSON value at key ``name`` under the ``path`` to the content
|
||||
of the json file ``file_name``.
|
||||
|
||||
``nx`` if set to True, set ``value`` only if it does not exist.
|
||||
``xx`` if set to True, set ``value`` only if it exists.
|
||||
``decode_keys`` If set to True, the keys of ``obj`` will be decoded
|
||||
with utf-8.
|
||||
``fpha`` if set, forces Redis to use the specified floating-point type
|
||||
for storing all FP homogeneous arrays in the file content.
|
||||
Accepts a :class:`FPHAType` enum value or a string
|
||||
(``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).
|
||||
|
||||
"""
|
||||
|
||||
with open(file_name) as fp:
|
||||
file_content = loads(fp.read())
|
||||
|
||||
return self.set(
|
||||
name, path, file_content, nx=nx, xx=xx, decode_keys=decode_keys, fpha=fpha
|
||||
)
|
||||
|
||||
@overload
|
||||
def set_path(
|
||||
self: SyncClientProtocol,
|
||||
json_path: str,
|
||||
root_folder: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> dict[str, bool]: ...
|
||||
|
||||
@overload
|
||||
def set_path(
|
||||
self: AsyncClientProtocol,
|
||||
json_path: str,
|
||||
root_folder: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> Awaitable[dict[str, bool]]: ...
|
||||
|
||||
def set_path(
|
||||
self,
|
||||
json_path: str,
|
||||
root_folder: str,
|
||||
nx: bool | None = False,
|
||||
xx: bool | None = False,
|
||||
decode_keys: bool | None = False,
|
||||
fpha: FPHAType | str | None = None,
|
||||
) -> dict[str, bool] | Awaitable[dict[str, bool]]:
|
||||
"""
|
||||
Iterate over ``root_folder`` and set each JSON file to a value
|
||||
under ``json_path`` with the file name as the key.
|
||||
|
||||
``nx`` if set to True, set ``value`` only if it does not exist.
|
||||
``xx`` if set to True, set ``value`` only if it exists.
|
||||
``decode_keys`` If set to True, the keys of ``obj`` will be decoded
|
||||
with utf-8.
|
||||
``fpha`` if set, forces Redis to use the specified floating-point type
|
||||
for storing all FP homogeneous arrays in the file content.
|
||||
Accepts a :class:`FPHAType` enum value or a string
|
||||
(``"BF16"``, ``"FP16"``, ``"FP32"``, ``"FP64"``).
|
||||
|
||||
"""
|
||||
set_files_result = {}
|
||||
for root, dirs, files in os.walk(root_folder):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
try:
|
||||
# TODO: rsplit(".") splits on all dots, mishandling paths
|
||||
# with dots in directories (e.g. /data/v1.2/file.json).
|
||||
# Should be rsplit(".", 1) — fix in a separate PR.
|
||||
file_name = file_path.rsplit(".")[0]
|
||||
self.set_file(
|
||||
file_name,
|
||||
json_path,
|
||||
file_path,
|
||||
nx=nx,
|
||||
xx=xx,
|
||||
decode_keys=decode_keys,
|
||||
fpha=fpha,
|
||||
)
|
||||
set_files_result[file_path] = True
|
||||
except JSONDecodeError:
|
||||
set_files_result[file_path] = False
|
||||
|
||||
return set_files_result
|
||||
|
||||
@overload
|
||||
def strlen(
|
||||
self: SyncClientProtocol, name: str, path: str | None = None
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def strlen(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = None
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def strlen(self, name: str, path: str | None = None) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Return the length of the string JSON value under ``path`` at key
|
||||
``name``.
|
||||
|
||||
For more information see `JSON.STRLEN <https://redis.io/commands/json.strlen>`_.
|
||||
""" # noqa
|
||||
pieces = [name]
|
||||
if path is not None:
|
||||
pieces.append(str(path))
|
||||
return self.execute_command("JSON.STRLEN", *pieces, keys=[name])
|
||||
|
||||
@overload
|
||||
def toggle(
|
||||
self: SyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> bool | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def toggle(
|
||||
self: AsyncClientProtocol, name: str, path: str | None = Path.root_path()
|
||||
) -> Awaitable[bool | list[int | None] | None]: ...
|
||||
|
||||
def toggle(self, name: str, path: str | None = Path.root_path()) -> (
|
||||
bool | list[int | None] | None
|
||||
) | Awaitable[bool | list[int | None] | None]:
|
||||
"""Toggle boolean value under ``path`` at key ``name``.
|
||||
returning the new value.
|
||||
|
||||
For more information see `JSON.TOGGLE <https://redis.io/commands/json.toggle>`_.
|
||||
""" # noqa
|
||||
return self.execute_command("JSON.TOGGLE", name, str(path))
|
||||
|
||||
@overload
|
||||
def strappend(
|
||||
self: SyncClientProtocol,
|
||||
name: str,
|
||||
value: str,
|
||||
path: str | None = Path.root_path(),
|
||||
) -> int | list[int | None] | None: ...
|
||||
|
||||
@overload
|
||||
def strappend(
|
||||
self: AsyncClientProtocol,
|
||||
name: str,
|
||||
value: str,
|
||||
path: str | None = Path.root_path(),
|
||||
) -> Awaitable[int | list[int | None] | None]: ...
|
||||
|
||||
def strappend(self, name: str, value: str, path: str | None = Path.root_path()) -> (
|
||||
int | list[int | None] | None
|
||||
) | Awaitable[int | list[int | None] | None]:
|
||||
"""Append to the string JSON value. If two options are specified after
|
||||
the key name, the path is determined to be the first. If a single
|
||||
option is passed, then the root_path (i.e Path.root_path()) is used.
|
||||
|
||||
For more information see `JSON.STRAPPEND <https://redis.io/commands/json.strappend>`_.
|
||||
""" # noqa
|
||||
pieces = [name, str(path), self._encode(value)]
|
||||
return self.execute_command("JSON.STRAPPEND", *pieces)
|
||||
|
||||
@overload
|
||||
def debug(
|
||||
self: SyncClientProtocol,
|
||||
subcommand: str,
|
||||
key: str | None = None,
|
||||
path: str | None = Path.root_path(),
|
||||
) -> int | list[str]: ...
|
||||
|
||||
@overload
|
||||
def debug(
|
||||
self: AsyncClientProtocol,
|
||||
subcommand: str,
|
||||
key: str | None = None,
|
||||
path: str | None = Path.root_path(),
|
||||
) -> Awaitable[int | list[str]]: ...
|
||||
|
||||
def debug(
|
||||
self,
|
||||
subcommand: str,
|
||||
key: str | None = None,
|
||||
path: str | None = Path.root_path(),
|
||||
) -> (int | list[str]) | Awaitable[int | list[str]]:
|
||||
"""Return the memory usage in bytes of a value under ``path`` from
|
||||
key ``name``.
|
||||
|
||||
For more information see `JSON.DEBUG <https://redis.io/commands/json.debug>`_.
|
||||
""" # noqa
|
||||
valid_subcommands = ["MEMORY", "HELP"]
|
||||
if subcommand not in valid_subcommands:
|
||||
raise DataError("The only valid subcommands are ", str(valid_subcommands))
|
||||
pieces = [subcommand]
|
||||
if subcommand == "MEMORY":
|
||||
if key is None:
|
||||
raise DataError("No key specified")
|
||||
pieces.append(key)
|
||||
pieces.append(str(path))
|
||||
return self.execute_command("JSON.DEBUG", *pieces)
|
||||
|
||||
@overload
|
||||
def jsonget(self: SyncClientProtocol, *args, **kwargs) -> JsonType | None: ...
|
||||
|
||||
@overload
|
||||
def jsonget(
|
||||
self: AsyncClientProtocol, *args, **kwargs
|
||||
) -> Awaitable[JsonType | None]: ...
|
||||
|
||||
@deprecated_function(
|
||||
version="4.0.0", reason="redisjson-py supported this, call get directly."
|
||||
)
|
||||
def jsonget(self, *args, **kwargs) -> (JsonType | None) | Awaitable[
|
||||
JsonType | None
|
||||
]:
|
||||
return self.get(*args, **kwargs)
|
||||
|
||||
@overload
|
||||
def jsonmget(
|
||||
self: SyncClientProtocol, *args, **kwargs
|
||||
) -> list[JsonType | None]: ...
|
||||
|
||||
@overload
|
||||
def jsonmget(
|
||||
self: AsyncClientProtocol, *args, **kwargs
|
||||
) -> Awaitable[list[JsonType | None]]: ...
|
||||
|
||||
@deprecated_function(
|
||||
version="4.0.0", reason="redisjson-py supported this, call get directly."
|
||||
)
|
||||
def jsonmget(
|
||||
self, *args, **kwargs
|
||||
) -> list[JsonType | None] | Awaitable[list[JsonType | None]]:
|
||||
return self.mget(*args, **kwargs)
|
||||
|
||||
@overload
|
||||
def jsonset(self: SyncClientProtocol, *args, **kwargs) -> bool | None: ...
|
||||
|
||||
@overload
|
||||
def jsonset(
|
||||
self: AsyncClientProtocol, *args, **kwargs
|
||||
) -> Awaitable[bool | None]: ...
|
||||
|
||||
@deprecated_function(
|
||||
version="4.0.0", reason="redisjson-py supported this, call get directly."
|
||||
)
|
||||
def jsonset(self, *args, **kwargs) -> (bool | None) | Awaitable[bool | None]:
|
||||
return self.set(*args, **kwargs)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import copy
|
||||
import re
|
||||
|
||||
from ..helpers import nativestr
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
class Path:
|
||||
"""This class represents a path in a JSON value."""
|
||||
|
||||
strPath = ""
|
||||
|
||||
@staticmethod
|
||||
def root_path():
|
||||
"""Return the root path's string representation."""
|
||||
return "."
|
||||
|
||||
def __init__(self, path):
|
||||
"""Make a new path based on the string representation in `path`."""
|
||||
self.strPath = path
|
||||
|
||||
def __repr__(self):
|
||||
return self.strPath
|
||||
Loading…
Add table
Add a link
Reference in a new issue