图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
15 KiB

  1. import os
  2. from json import JSONDecodeError, loads
  3. from typing import Dict, List, Optional, Tuple, Union
  4. from redis.exceptions import DataError
  5. from redis.utils import deprecated_function
  6. from ._util import JsonType
  7. from .decoders import decode_dict_keys
  8. from .path import Path
  9. class JSONCommands:
  10. """json commands."""
  11. def arrappend(
  12. self, name: str, path: Optional[str] = Path.root_path(), *args: List[JsonType]
  13. ) -> List[Union[int, None]]:
  14. """Append the objects ``args`` to the array under the
  15. ``path` in key ``name``.
  16. For more information see `JSON.ARRAPPEND <https://redis.io/commands/json.arrappend>`_..
  17. """ # noqa
  18. pieces = [name, str(path)]
  19. for o in args:
  20. pieces.append(self._encode(o))
  21. return self.execute_command("JSON.ARRAPPEND", *pieces)
  22. def arrindex(
  23. self,
  24. name: str,
  25. path: str,
  26. scalar: int,
  27. start: Optional[int] = None,
  28. stop: Optional[int] = None,
  29. ) -> List[Union[int, None]]:
  30. """
  31. Return the index of ``scalar`` in the JSON array under ``path`` at key
  32. ``name``.
  33. The search can be limited using the optional inclusive ``start``
  34. and exclusive ``stop`` indices.
  35. For more information see `JSON.ARRINDEX <https://redis.io/commands/json.arrindex>`_.
  36. """ # noqa
  37. pieces = [name, str(path), self._encode(scalar)]
  38. if start is not None:
  39. pieces.append(start)
  40. if stop is not None:
  41. pieces.append(stop)
  42. return self.execute_command("JSON.ARRINDEX", *pieces)
  43. def arrinsert(
  44. self, name: str, path: str, index: int, *args: List[JsonType]
  45. ) -> List[Union[int, None]]:
  46. """Insert the objects ``args`` to the array at index ``index``
  47. under the ``path` in key ``name``.
  48. For more information see `JSON.ARRINSERT <https://redis.io/commands/json.arrinsert>`_.
  49. """ # noqa
  50. pieces = [name, str(path), index]
  51. for o in args:
  52. pieces.append(self._encode(o))
  53. return self.execute_command("JSON.ARRINSERT", *pieces)
  54. def arrlen(
  55. self, name: str, path: Optional[str] = Path.root_path()
  56. ) -> List[Union[int, None]]:
  57. """Return the length of the array JSON value under ``path``
  58. at key``name``.
  59. For more information see `JSON.ARRLEN <https://redis.io/commands/json.arrlen>`_.
  60. """ # noqa
  61. return self.execute_command("JSON.ARRLEN", name, str(path))
  62. def arrpop(
  63. self,
  64. name: str,
  65. path: Optional[str] = Path.root_path(),
  66. index: Optional[int] = -1,
  67. ) -> List[Union[str, None]]:
  68. """Pop the element at ``index`` in the array JSON value under
  69. ``path`` at key ``name``.
  70. For more information see `JSON.ARRPOP <https://redis.io/commands/json.arrpop>`_.
  71. """ # noqa
  72. return self.execute_command("JSON.ARRPOP", name, str(path), index)
  73. def arrtrim(
  74. self, name: str, path: str, start: int, stop: int
  75. ) -> List[Union[int, None]]:
  76. """Trim the array JSON value under ``path`` at key ``name`` to the
  77. inclusive range given by ``start`` and ``stop``.
  78. For more information see `JSON.ARRTRIM <https://redis.io/commands/json.arrtrim>`_.
  79. """ # noqa
  80. return self.execute_command("JSON.ARRTRIM", name, str(path), start, stop)
  81. def type(self, name: str, path: Optional[str] = Path.root_path()) -> List[str]:
  82. """Get the type of the JSON value under ``path`` from key ``name``.
  83. For more information see `JSON.TYPE <https://redis.io/commands/json.type>`_.
  84. """ # noqa
  85. return self.execute_command("JSON.TYPE", name, str(path))
  86. def resp(self, name: str, path: Optional[str] = Path.root_path()) -> List:
  87. """Return the JSON value under ``path`` at key ``name``.
  88. For more information see `JSON.RESP <https://redis.io/commands/json.resp>`_.
  89. """ # noqa
  90. return self.execute_command("JSON.RESP", name, str(path))
  91. def objkeys(
  92. self, name: str, path: Optional[str] = Path.root_path()
  93. ) -> List[Union[List[str], None]]:
  94. """Return the key names in the dictionary JSON value under ``path`` at
  95. key ``name``.
  96. For more information see `JSON.OBJKEYS <https://redis.io/commands/json.objkeys>`_.
  97. """ # noqa
  98. return self.execute_command("JSON.OBJKEYS", name, str(path))
  99. def objlen(
  100. self, name: str, path: Optional[str] = Path.root_path()
  101. ) -> List[Optional[int]]:
  102. """Return the length of the dictionary JSON value under ``path`` at key
  103. ``name``.
  104. For more information see `JSON.OBJLEN <https://redis.io/commands/json.objlen>`_.
  105. """ # noqa
  106. return self.execute_command("JSON.OBJLEN", name, str(path))
  107. def numincrby(self, name: str, path: str, number: int) -> str:
  108. """Increment the numeric (integer or floating point) JSON value under
  109. ``path`` at key ``name`` by the provided ``number``.
  110. For more information see `JSON.NUMINCRBY <https://redis.io/commands/json.numincrby>`_.
  111. """ # noqa
  112. return self.execute_command(
  113. "JSON.NUMINCRBY", name, str(path), self._encode(number)
  114. )
  115. @deprecated_function(version="4.0.0", reason="deprecated since redisjson 1.0.0")
  116. def nummultby(self, name: str, path: str, number: int) -> str:
  117. """Multiply the numeric (integer or floating point) JSON value under
  118. ``path`` at key ``name`` with the provided ``number``.
  119. For more information see `JSON.NUMMULTBY <https://redis.io/commands/json.nummultby>`_.
  120. """ # noqa
  121. return self.execute_command(
  122. "JSON.NUMMULTBY", name, str(path), self._encode(number)
  123. )
  124. def clear(self, name: str, path: Optional[str] = Path.root_path()) -> int:
  125. """Empty arrays and objects (to have zero slots/keys without deleting the
  126. array/object).
  127. Return the count of cleared paths (ignoring non-array and non-objects
  128. paths).
  129. For more information see `JSON.CLEAR <https://redis.io/commands/json.clear>`_.
  130. """ # noqa
  131. return self.execute_command("JSON.CLEAR", name, str(path))
  132. def delete(self, key: str, path: Optional[str] = Path.root_path()) -> int:
  133. """Delete the JSON value stored at key ``key`` under ``path``.
  134. For more information see `JSON.DEL <https://redis.io/commands/json.del>`_.
  135. """
  136. return self.execute_command("JSON.DEL", key, str(path))
  137. # forget is an alias for delete
  138. forget = delete
  139. def get(
  140. self, name: str, *args, no_escape: Optional[bool] = False
  141. ) -> Optional[List[JsonType]]:
  142. """
  143. Get the object stored as a JSON value at key ``name``.
  144. ``args`` is zero or more paths, and defaults to root path
  145. ```no_escape`` is a boolean flag to add no_escape option to get
  146. non-ascii characters
  147. For more information see `JSON.GET <https://redis.io/commands/json.get>`_.
  148. """ # noqa
  149. pieces = [name]
  150. if no_escape:
  151. pieces.append("noescape")
  152. if len(args) == 0:
  153. pieces.append(Path.root_path())
  154. else:
  155. for p in args:
  156. pieces.append(str(p))
  157. # Handle case where key doesn't exist. The JSONDecoder would raise a
  158. # TypeError exception since it can't decode None
  159. try:
  160. return self.execute_command("JSON.GET", *pieces)
  161. except TypeError:
  162. return None
  163. def mget(self, keys: List[str], path: str) -> List[JsonType]:
  164. """
  165. Get the objects stored as a JSON values under ``path``. ``keys``
  166. is a list of one or more keys.
  167. For more information see `JSON.MGET <https://redis.io/commands/json.mget>`_.
  168. """ # noqa
  169. pieces = []
  170. pieces += keys
  171. pieces.append(str(path))
  172. return self.execute_command("JSON.MGET", *pieces)
  173. def set(
  174. self,
  175. name: str,
  176. path: str,
  177. obj: JsonType,
  178. nx: Optional[bool] = False,
  179. xx: Optional[bool] = False,
  180. decode_keys: Optional[bool] = False,
  181. ) -> Optional[str]:
  182. """
  183. Set the JSON value at key ``name`` under the ``path`` to ``obj``.
  184. ``nx`` if set to True, set ``value`` only if it does not exist.
  185. ``xx`` if set to True, set ``value`` only if it exists.
  186. ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
  187. with utf-8.
  188. For the purpose of using this within a pipeline, this command is also
  189. aliased to JSON.SET.
  190. For more information see `JSON.SET <https://redis.io/commands/json.set>`_.
  191. """
  192. if decode_keys:
  193. obj = decode_dict_keys(obj)
  194. pieces = [name, str(path), self._encode(obj)]
  195. # Handle existential modifiers
  196. if nx and xx:
  197. raise Exception(
  198. "nx and xx are mutually exclusive: use one, the "
  199. "other or neither - but not both"
  200. )
  201. elif nx:
  202. pieces.append("NX")
  203. elif xx:
  204. pieces.append("XX")
  205. return self.execute_command("JSON.SET", *pieces)
  206. def mset(self, triplets: List[Tuple[str, str, JsonType]]) -> Optional[str]:
  207. """
  208. Set the JSON value at key ``name`` under the ``path`` to ``obj``
  209. for one or more keys.
  210. ``triplets`` is a list of one or more triplets of key, path, value.
  211. For the purpose of using this within a pipeline, this command is also
  212. aliased to JSON.MSET.
  213. For more information see `JSON.MSET <https://redis.io/commands/json.mset>`_.
  214. """
  215. pieces = []
  216. for triplet in triplets:
  217. pieces.extend([triplet[0], str(triplet[1]), self._encode(triplet[2])])
  218. return self.execute_command("JSON.MSET", *pieces)
  219. def merge(
  220. self,
  221. name: str,
  222. path: str,
  223. obj: JsonType,
  224. decode_keys: Optional[bool] = False,
  225. ) -> Optional[str]:
  226. """
  227. Merges a given JSON value into matching paths. Consequently, JSON values
  228. at matching paths are updated, deleted, or expanded with new children
  229. ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
  230. with utf-8.
  231. For more information see `JSON.MERGE <https://redis.io/commands/json.merge>`_.
  232. """
  233. if decode_keys:
  234. obj = decode_dict_keys(obj)
  235. pieces = [name, str(path), self._encode(obj)]
  236. return self.execute_command("JSON.MERGE", *pieces)
  237. def set_file(
  238. self,
  239. name: str,
  240. path: str,
  241. file_name: str,
  242. nx: Optional[bool] = False,
  243. xx: Optional[bool] = False,
  244. decode_keys: Optional[bool] = False,
  245. ) -> Optional[str]:
  246. """
  247. Set the JSON value at key ``name`` under the ``path`` to the content
  248. of the json file ``file_name``.
  249. ``nx`` if set to True, set ``value`` only if it does not exist.
  250. ``xx`` if set to True, set ``value`` only if it exists.
  251. ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
  252. with utf-8.
  253. """
  254. with open(file_name, "r") as fp:
  255. file_content = loads(fp.read())
  256. return self.set(name, path, file_content, nx=nx, xx=xx, decode_keys=decode_keys)
  257. def set_path(
  258. self,
  259. json_path: str,
  260. root_folder: str,
  261. nx: Optional[bool] = False,
  262. xx: Optional[bool] = False,
  263. decode_keys: Optional[bool] = False,
  264. ) -> Dict[str, bool]:
  265. """
  266. Iterate over ``root_folder`` and set each JSON file to a value
  267. under ``json_path`` with the file name as the key.
  268. ``nx`` if set to True, set ``value`` only if it does not exist.
  269. ``xx`` if set to True, set ``value`` only if it exists.
  270. ``decode_keys`` If set to True, the keys of ``obj`` will be decoded
  271. with utf-8.
  272. """
  273. set_files_result = {}
  274. for root, dirs, files in os.walk(root_folder):
  275. for file in files:
  276. file_path = os.path.join(root, file)
  277. try:
  278. file_name = file_path.rsplit(".")[0]
  279. self.set_file(
  280. file_name,
  281. json_path,
  282. file_path,
  283. nx=nx,
  284. xx=xx,
  285. decode_keys=decode_keys,
  286. )
  287. set_files_result[file_path] = True
  288. except JSONDecodeError:
  289. set_files_result[file_path] = False
  290. return set_files_result
  291. def strlen(self, name: str, path: Optional[str] = None) -> List[Union[int, None]]:
  292. """Return the length of the string JSON value under ``path`` at key
  293. ``name``.
  294. For more information see `JSON.STRLEN <https://redis.io/commands/json.strlen>`_.
  295. """ # noqa
  296. pieces = [name]
  297. if path is not None:
  298. pieces.append(str(path))
  299. return self.execute_command("JSON.STRLEN", *pieces)
  300. def toggle(
  301. self, name: str, path: Optional[str] = Path.root_path()
  302. ) -> Union[bool, List[Optional[int]]]:
  303. """Toggle boolean value under ``path`` at key ``name``.
  304. returning the new value.
  305. For more information see `JSON.TOGGLE <https://redis.io/commands/json.toggle>`_.
  306. """ # noqa
  307. return self.execute_command("JSON.TOGGLE", name, str(path))
  308. def strappend(
  309. self, name: str, value: str, path: Optional[str] = Path.root_path()
  310. ) -> Union[int, List[Optional[int]]]:
  311. """Append to the string JSON value. If two options are specified after
  312. the key name, the path is determined to be the first. If a single
  313. option is passed, then the root_path (i.e Path.root_path()) is used.
  314. For more information see `JSON.STRAPPEND <https://redis.io/commands/json.strappend>`_.
  315. """ # noqa
  316. pieces = [name, str(path), self._encode(value)]
  317. return self.execute_command("JSON.STRAPPEND", *pieces)
  318. def debug(
  319. self,
  320. subcommand: str,
  321. key: Optional[str] = None,
  322. path: Optional[str] = Path.root_path(),
  323. ) -> Union[int, List[str]]:
  324. """Return the memory usage in bytes of a value under ``path`` from
  325. key ``name``.
  326. For more information see `JSON.DEBUG <https://redis.io/commands/json.debug>`_.
  327. """ # noqa
  328. valid_subcommands = ["MEMORY", "HELP"]
  329. if subcommand not in valid_subcommands:
  330. raise DataError("The only valid subcommands are ", str(valid_subcommands))
  331. pieces = [subcommand]
  332. if subcommand == "MEMORY":
  333. if key is None:
  334. raise DataError("No key specified")
  335. pieces.append(key)
  336. pieces.append(str(path))
  337. return self.execute_command("JSON.DEBUG", *pieces)
  338. @deprecated_function(
  339. version="4.0.0", reason="redisjson-py supported this, call get directly."
  340. )
  341. def jsonget(self, *args, **kwargs):
  342. return self.get(*args, **kwargs)
  343. @deprecated_function(
  344. version="4.0.0", reason="redisjson-py supported this, call get directly."
  345. )
  346. def jsonmget(self, *args, **kwargs):
  347. return self.mget(*args, **kwargs)
  348. @deprecated_function(
  349. version="4.0.0", reason="redisjson-py supported this, call get directly."
  350. )
  351. def jsonset(self, *args, **kwargs):
  352. return self.set(*args, **kwargs)