diff --git a/.gitignore b/.gitignore
index 6a12d09f..1dc9e858 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ scratch*
.python-version
.env
scripts/*.md
+CLAUDE.md
diff --git a/docs/about/changelog.md b/docs/about/changelog.md
index 2027d668..ccf9e59c 100644
--- a/docs/about/changelog.md
+++ b/docs/about/changelog.md
@@ -12,6 +12,8 @@ toc_depth: 2
### 🚀 Features
- Support for idempotent message processing introduced in Redis 8.6 #461
+- Support for vector set commands `VADD`, `VCARD`, `VDIM`, `VEMB`, `VGETATTR`, `VINFO`, `VLINKS`, `VRANDMEMBER`,
+ `VRANGE`, `VREM`, `VSETATTR`, `VSIM` #451
### 🐛 Bug Fixes
diff --git a/docs/supported-commands/Redis/HASH.md b/docs/supported-commands/Redis/HASH.md
index a9594e39..3131bb8b 100644
--- a/docs/supported-commands/Redis/HASH.md
+++ b/docs/supported-commands/Redis/HASH.md
@@ -34,7 +34,7 @@ Returns the value of a field and deletes it from the hash.
## [HGETEX](https://redis.io/commands/hgetex/)
-Get the value of one or more fields of a given hash key, and optionally set
+Get the value of one or more fields of a given hash key, and optionally set their expiration.
## [HINCRBY](https://redis.io/commands/hincrby/)
@@ -94,7 +94,7 @@ Creates or modifies the value of a field in a hash.
## [HSETEX](https://redis.io/commands/hsetex/)
-Set the value of one or more fields of a given hash key, and optionally set
+Set the value of one or more fields of a given hash key, and optionally set their expiration.
## [HSETNX](https://redis.io/commands/hsetnx/)
diff --git a/docs/supported-commands/Redis/STRING.md b/docs/supported-commands/Redis/STRING.md
index b29b8505..f3220cf1 100644
--- a/docs/supported-commands/Redis/STRING.md
+++ b/docs/supported-commands/Redis/STRING.md
@@ -58,7 +58,7 @@ Atomically creates or modifies the string values of one or more keys.
## [MSETEX](https://redis.io/commands/msetex/)
-Atomically sets multiple string keys with a shared expiration in a single
+Atomically sets multiple string keys with a shared expiration in a single operation.
## [MSETNX](https://redis.io/commands/msetnx/)
diff --git a/docs/supported-commands/Redis/VECTOR_SET.md b/docs/supported-commands/Redis/VECTOR_SET.md
index 0e89f1c6..9aba2494 100644
--- a/docs/supported-commands/Redis/VECTOR_SET.md
+++ b/docs/supported-commands/Redis/VECTOR_SET.md
@@ -1,51 +1,49 @@
+# Redis `vector_set` commands (12/12 implemented)
-## Unsupported vector_set commands
-> To implement support for a command, see [here](/guides/implement-command/)
-
-#### [VADD](https://redis.io/commands/vadd/) (not implemented)
+## [VADD](https://redis.io/commands/vadd/)
Add a new element to a vector set, or update its vector if it already exists.
-#### [VCARD](https://redis.io/commands/vcard/) (not implemented)
+## [VCARD](https://redis.io/commands/vcard/)
Return the number of elements in a vector set.
-#### [VDIM](https://redis.io/commands/vdim/) (not implemented)
+## [VDIM](https://redis.io/commands/vdim/)
Return the dimension of vectors in the vector set.
-#### [VEMB](https://redis.io/commands/vemb/) (not implemented)
+## [VEMB](https://redis.io/commands/vemb/)
Return the vector associated with an element.
-#### [VGETATTR](https://redis.io/commands/vgetattr/) (not implemented)
+## [VGETATTR](https://redis.io/commands/vgetattr/)
Retrieve the JSON attributes of elements.
-#### [VINFO](https://redis.io/commands/vinfo/) (not implemented)
+## [VINFO](https://redis.io/commands/vinfo/)
Return information about a vector set.
-#### [VLINKS](https://redis.io/commands/vlinks/) (not implemented)
+## [VLINKS](https://redis.io/commands/vlinks/)
Return the neighbors of an element at each layer in the HNSW graph.
-#### [VRANDMEMBER](https://redis.io/commands/vrandmember/) (not implemented)
+## [VRANDMEMBER](https://redis.io/commands/vrandmember/)
Return one or multiple random members from a vector set.
-#### [VRANGE](https://redis.io/commands/vrange/) (not implemented)
+## [VRANGE](https://redis.io/commands/vrange/)
Return elements in a lexicographical range
-#### [VREM](https://redis.io/commands/vrem/) (not implemented)
+## [VREM](https://redis.io/commands/vrem/)
Remove an element from a vector set.
-#### [VSETATTR](https://redis.io/commands/vsetattr/) (not implemented)
+## [VSETATTR](https://redis.io/commands/vsetattr/)
Associate or remove the JSON attributes of elements.
-#### [VSIM](https://redis.io/commands/vsim/) (not implemented)
+## [VSIM](https://redis.io/commands/vsim/)
Return elements by vector similarity.
diff --git a/fakeredis/_commands.py b/fakeredis/_commands.py
index e87097ff..9bb2912d 100644
--- a/fakeredis/_commands.py
+++ b/fakeredis/_commands.py
@@ -319,6 +319,10 @@ def __init__(self, value: Union[bytes, BeforeAny, AfterAny], exclusive: bool):
self.value = value
self.exclusive = exclusive
+ @property
+ def inclusive(self) -> bool:
+ return not self.exclusive
+
@classmethod
def decode(cls, value: bytes) -> "StringTest":
if value == b"-":
diff --git a/fakeredis/_fakesocket.py b/fakeredis/_fakesocket.py
index 71f17201..06a8d298 100644
--- a/fakeredis/_fakesocket.py
+++ b/fakeredis/_fakesocket.py
@@ -24,6 +24,7 @@
TopkCommandsMixin,
TDigestCommandsMixin,
TimeSeriesCommandsMixin,
+ VectorSetCommandsMixin,
)
from ._basefakesocket import BaseFakeSocket
from ._server import FakeServer
@@ -56,6 +57,7 @@ class FakeSocket(
TimeSeriesCommandsMixin,
DragonflyCommandsMixin,
AclCommandsMixin,
+ VectorSetCommandsMixin,
):
def __init__(
self,
diff --git a/fakeredis/_typing.py b/fakeredis/_typing.py
index fab99871..eef4d5e2 100644
--- a/fakeredis/_typing.py
+++ b/fakeredis/_typing.py
@@ -1,5 +1,5 @@
import sys
-from typing import Tuple, Union
+from typing import Tuple, Union, Dict, Any, List
import redis
@@ -22,9 +22,10 @@
lib_version = metadata.version("fakeredis")
VersionType = Tuple[int, ...]
ServerType = Literal["redis", "dragonfly", "valkey"]
-
+JsonType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]
RaiseErrorTypes = (redis.ResponseError, redis.AuthenticationError)
ResponseErrorType = redis.ResponseError
+
try:
import valkey
diff --git a/fakeredis/commands.json b/fakeredis/commands.json
index 77c1e494..6a81dd78 100644
--- a/fakeredis/commands.json
+++ b/fakeredis/commands.json
@@ -1 +1 @@
-{"acl cat": ["acl|cat", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "acl": ["acl", -1, [], 0, 0, 0, [], [], [], [["acl|cat", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["acl|deluser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|genpass", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["acl|getuser", 3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|list", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|load", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|log", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|save", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|setuser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|users", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|whoami", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []]]], "acl deluser": ["acl|deluser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl genpass": ["acl|genpass", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "acl getuser": ["acl|getuser", 3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl list": ["acl|list", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl load": ["acl|load", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl log": ["acl|log", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl save": ["acl|save", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl setuser": ["acl|setuser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl users": ["acl|users", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl whoami": ["acl|whoami", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "append": ["append", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "auth": ["auth", -2, ["noscript", "loading", "stale", "fast", "no_auth", "allow_busy"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "bgsave": ["bgsave", -1, ["admin", "noscript", "no_async_loading"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "bitcount": ["bitcount", -2, ["readonly"], 1, 1, 1, ["@bitmap", "@read", "@slow"], [], [], []], "bitfield": ["bitfield", -2, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], [["bitfield_ro", -2, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []]]], "bitop": ["bitop", -4, ["write", "denyoom"], 2, 3, 1, ["@bitmap", "@slow", "@write"], [], [], []], "bitpos": ["bitpos", -3, ["readonly"], 1, 1, 1, ["@bitmap", "@read", "@slow"], [], [], []], "blmove": ["blmove", 6, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "blmpop": ["blmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "blpop": ["blpop", -3, ["write", "blocking"], 1, 1, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "brpop": ["brpop", -3, ["write", "blocking"], 1, 1, 1, ["@blocking", "@list", "@slow", "@write"], [], [], [["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []]]], "brpoplpush": ["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "bzmpop": ["bzmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@blocking", "@slow", "@sortedset", "@write"], [], [], []], "bzpopmax": ["bzpopmax", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@blocking", "@fast", "@sortedset", "@write"], [], [], []], "bzpopmin": ["bzpopmin", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@blocking", "@fast", "@sortedset", "@write"], [], [], []], "client getname": ["client|getname", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client": ["client", -1, [], 0, 0, 0, [], [], [], [["client|getname", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|id", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|info", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|list", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@connection", "@dangerous", "@slow"], [], [], []], ["client|setinfo", 4, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|setname", 3, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], "client id": ["client|id", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client info": ["client|info", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client list": ["client|list", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@connection", "@dangerous", "@slow"], [], [], []], "client setinfo": ["client|setinfo", 4, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client setname": ["client|setname", 3, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "command": ["command", -1, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], [["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|docs", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|getkeys", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], [["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], ["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|help", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|list", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], "command count": ["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "command info": ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "config set": ["config|set", -4, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "config": ["config", -1, [], 0, 0, 0, [], [], [], [["config|set", -4, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []]]], "copy": ["copy", -3, ["write", "denyoom"], 1, 2, 1, ["@keyspace", "@slow", "@write"], [], [], []], "dbsize": ["dbsize", 1, ["readonly", "fast"], 0, 0, 0, ["@fast", "@keyspace", "@read"], [], [], []], "decr": ["decr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "decrby": ["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "del": ["del", -2, ["write"], 1, 1, 1, ["@keyspace", "@slow", "@write"], [], [], []], "discard": ["discard", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "dump": ["dump", 2, ["readonly"], 1, 1, 1, ["@keyspace", "@read", "@slow"], [], [], []], "echo": ["echo", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "eval": ["eval", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], ["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []], ["eval_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], "evalsha": ["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], "exec": ["exec", 1, ["noscript", "loading", "stale", "skip_slowlog"], 0, 0, 0, ["@slow", "@transaction"], [], [], []], "exists": ["exists", -2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "expire": ["expire", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], [["expireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []]]], "expireat": ["expireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "expiretime": ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "flushall": ["flushall", -1, ["write"], 0, 0, 0, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []], "flushdb": ["flushdb", -1, ["write"], 0, 0, 0, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []], "geoadd": ["geoadd", -5, ["write", "denyoom"], 1, 1, 1, ["@geo", "@slow", "@write"], [], [], []], "geodist": ["geodist", -4, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geohash": ["geohash", -2, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geopos": ["geopos", -2, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "georadius": ["georadius", -6, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], "georadiusbymember": ["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], "georadiusbymember_ro": ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "georadius_ro": ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geosearch": ["geosearch", -7, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], [["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@geo", "@slow", "@write"], [], [], []]]], "geosearchstore": ["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@geo", "@slow", "@write"], [], [], []], "get": ["get", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], [["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []], ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["getex", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "getbit": ["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []], "getdel": ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "getex": ["getex", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "getrange": ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "getset": ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "hdel": ["hdel", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hello": ["hello", -1, ["noscript", "loading", "stale", "fast", "no_auth", "allow_busy"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "hexists": ["hexists", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hexpire": ["hexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []]]], "hexpireat": ["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hexpiretime": ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hget": ["hget", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], [["hgetall", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], ["hgetf", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hgetall": ["hgetall", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hincrby": ["hincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hincrbyfloat": ["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hkeys": ["hkeys", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hlen": ["hlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hmget": ["hmget", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hmset": ["hmset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hpersist": ["hpersist", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hpexpire": ["hpexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []]]], "hpexpireat": ["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hpexpiretime": ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hpttl": ["hpttl", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hrandfield": ["hrandfield", -2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hscan": ["hscan", -3, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hset": ["hset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hsetf", -6, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hsetnx": ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hstrlen": ["hstrlen", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "httl": ["httl", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hvals": ["hvals", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "incr": ["incr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "incrby": ["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "incrbyfloat": ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "keys": ["keys", 2, ["readonly"], 0, 0, 0, ["@dangerous", "@keyspace", "@read", "@slow"], [], [], []], "lastsave": ["lastsave", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@admin", "@dangerous", "@fast"], [], [], []], "lcs": ["lcs", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "lindex": ["lindex", 3, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "linsert": ["linsert", 5, ["write", "denyoom"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "llen": ["llen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@list", "@read"], [], [], []], "lmove": ["lmove", 5, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []], "lmpop": ["lmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "lpop": ["lpop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "lpos": ["lpos", -3, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "lpush": ["lpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []]]], "lpushx": ["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "lrange": ["lrange", 4, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "lrem": ["lrem", 4, ["write"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "lset": ["lset", 4, ["write", "denyoom"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "ltrim": ["ltrim", 4, ["write"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "mget": ["mget", -2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], []], "move": ["move", 3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "mset": ["mset", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], [["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []]]], "msetnx": ["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []], "multi": ["multi", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "persist": ["persist", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "pexpire": ["pexpire", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], [["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []]]], "pexpireat": ["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "pexpiretime": ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "pfadd": ["pfadd", -2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hyperloglog", "@write"], [], [], []], "pfcount": ["pfcount", -2, ["readonly"], 1, 1, 1, ["@hyperloglog", "@read", "@slow"], [], [], []], "pfmerge": ["pfmerge", -2, ["write", "denyoom"], 1, 2, 1, ["@hyperloglog", "@slow", "@write"], [], [], []], "ping": ["ping", -1, ["fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "psetex": ["psetex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "psubscribe": ["psubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pttl": ["pttl", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "publish": ["publish", 3, ["pubsub", "loading", "stale", "fast"], 0, 0, 0, ["@fast", "@pubsub"], [], [], []], "pubsub": ["pubsub", -2, [], 0, 0, 0, ["@slow"], [], [], [["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []]]], "pubsub channels": ["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub help": ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "pubsub numpat": ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub numsub": ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub shardchannels": ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub shardnumsub": ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "punsubscribe": ["punsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "randomkey": ["randomkey", 1, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "rename": ["rename", 3, ["write"], 1, 2, 1, ["@keyspace", "@slow", "@write"], [], [], [["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@fast", "@keyspace", "@write"], [], [], []]]], "renamenx": ["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@fast", "@keyspace", "@write"], [], [], []], "restore": ["restore", -4, ["write", "denyoom"], 1, 1, 1, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], [["restore-asking", -4, ["write", "denyoom", "asking"], 1, 1, 1, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []]]], "rpop": ["rpop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []]]], "rpoplpush": ["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []], "rpush": ["rpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []]]], "rpushx": ["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "sadd": ["sadd", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "save": ["save", 1, ["admin", "noscript", "no_async_loading", "no_multi"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "scan": ["scan", -2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "scard": ["scard", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "script": ["script", -2, [], 0, 0, 0, ["@slow"], [], [], [["script|debug", 3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|kill", 2, ["noscript", "allow_busy"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []]]], "script exists": ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script flush": ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script help": ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script load": ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "sdiff": ["sdiff", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sdiffstore": ["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "select": ["select", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "set": ["set", -3, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], [["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], []], ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []]]], "setbit": ["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], []], "setex": ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "setnx": ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "setrange": ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "sinter": ["sinter", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sintercard": ["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sinterstore": ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "sismember": ["sismember", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "smembers": ["smembers", 2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "smismember": ["smismember", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "smove": ["smove", 4, ["write", "fast"], 1, 2, 1, ["@fast", "@set", "@write"], [], [], []], "sort": ["sort", -2, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@set", "@slow", "@sortedset", "@write"], [], [], [["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@read", "@set", "@slow", "@sortedset"], [], [], []]]], "sort_ro": ["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@read", "@set", "@slow", "@sortedset"], [], [], []], "spop": ["spop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "spublish": ["spublish", 3, ["pubsub", "loading", "stale", "fast"], 1, 1, 1, ["@fast", "@pubsub"], [], [], []], "srandmember": ["srandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "srem": ["srem", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "sscan": ["sscan", -3, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "ssubscribe": ["ssubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "strlen": ["strlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], []], "subscribe": ["subscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "substr": ["substr", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "sunion": ["sunion", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sunionstore": ["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "sunsubscribe": ["sunsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "swapdb": ["swapdb", 3, ["write", "fast"], 0, 0, 0, ["@dangerous", "@fast", "@keyspace", "@write"], [], [], []], "time": ["time", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@fast"], [], [], []], "ttl": ["ttl", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "type": ["type", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "unlink": ["unlink", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "unsubscribe": ["unsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "unwatch": ["unwatch", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "watch": ["watch", -2, ["noscript", "loading", "stale", "fast", "allow_busy"], 1, 1, 1, ["@fast", "@transaction"], [], [], []], "xack": ["xack", -4, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xadd": ["xadd", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xautoclaim": ["xautoclaim", -6, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xclaim": ["xclaim", -6, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xdel": ["xdel", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xgroup create": ["xgroup|create", -5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], [["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], "xgroup": ["xgroup", -1, [], 0, 0, 0, [], [], [], [["xgroup|create", -5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], [["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], ["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|delconsumer", 5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|destroy", 4, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|setid", -5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], "xgroup createconsumer": ["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup delconsumer": ["xgroup|delconsumer", 5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup destroy": ["xgroup|destroy", 4, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup setid": ["xgroup|setid", -5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xinfo consumers": ["xinfo|consumers", 4, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xinfo": ["xinfo", -1, [], 0, 0, 0, [], [], [], [["xinfo|consumers", 4, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], ["xinfo|groups", 3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], ["xinfo|stream", -3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []]]], "xinfo groups": ["xinfo|groups", 3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xinfo stream": ["xinfo|stream", -3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xlen": ["xlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@stream"], [], [], []], "xpending": ["xpending", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xrange": ["xrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xread": ["xread", -4, ["readonly", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@read", "@slow", "@stream"], [], [], [["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@slow", "@stream", "@write"], [], [], []]]], "xreadgroup": ["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@slow", "@stream", "@write"], [], [], []], "xrevrange": ["xrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xtrim": ["xtrim", -4, ["write"], 1, 1, 1, ["@slow", "@stream", "@write"], [], [], []], "zadd": ["zadd", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zcard": ["zcard", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zcount": ["zcount", 4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zdiff": ["zdiff", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zdiffstore": ["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zincrby": ["zincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zinter": ["zinter", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zintercard": ["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zinterstore": ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zlexcount": ["zlexcount", 4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zmpop": ["zmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zmscore": ["zmscore", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zpopmax": ["zpopmax", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zpopmin": ["zpopmin", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zrandmember": ["zrandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrange": ["zrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zrangebylex": ["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrangebyscore": ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrangestore": ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zrank": ["zrank", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zrem": ["zrem", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], [["zremrangebylex", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zremrangebylex": ["zremrangebylex", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zremrangebyrank": ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zremrangebyscore": ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zrevrange": ["zrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []]]], "zrevrangebylex": ["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrevrangebyscore": ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrevrank": ["zrevrank", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zscan": ["zscan", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zscore": ["zscore", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zunion": ["zunion", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zunionstore": ["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "json.del": ["json.del", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.forget": ["json.forget", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.get": ["json.get", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.toggle": ["json.toggle", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.clear": ["json.clear", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.set": ["json.set", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.mset": ["json.mset", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.merge": ["json.merge", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.mget": ["json.mget", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.numincrby": ["json.numincrby", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.nummultby": ["json.nummultby", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.strappend": ["json.strappend", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.strlen": ["json.strlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrappend": ["json.arrappend", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrindex": ["json.arrindex", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrinsert": ["json.arrinsert", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrlen": ["json.arrlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrpop": ["json.arrpop", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrtrim": ["json.arrtrim", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.objkeys": ["json.objkeys", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.objlen": ["json.objlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.type": ["json.type", -1, [], 0, 0, 0, ["@json"], [], [], []], "ts.create": ["ts.create", -1, [], 0, 0, 0, ["@timeseries"], [], [], [["ts.createrule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []]]], "ts.del": ["ts.del", -1, [], 0, 0, 0, ["@timeseries"], [], [], [["ts.deleterule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []]]], "ts.alter": ["ts.alter", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.add": ["ts.add", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.madd": ["ts.madd", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.incrby": ["ts.incrby", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.decrby": ["ts.decrby", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.createrule": ["ts.createrule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.deleterule": ["ts.deleterule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.range": ["ts.range", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.revrange": ["ts.revrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mrange": ["ts.mrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mrevrange": ["ts.mrevrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.get": ["ts.get", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mget": ["ts.mget", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.info": ["ts.info", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.queryindex": ["ts.queryindex", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "bf.reserve": ["bf.reserve", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.add": ["bf.add", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.madd": ["bf.madd", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.insert": ["bf.insert", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.exists": ["bf.exists", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.mexists": ["bf.mexists", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.scandump": ["bf.scandump", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.loadchunk": ["bf.loadchunk", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.info": ["bf.info", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.card": ["bf.card", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "cf.reserve": ["cf.reserve", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.add": ["cf.add", -1, [], 0, 0, 0, ["@cuckoo"], [], [], [["cf.addnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []]]], "cf.addnx": ["cf.addnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.insert": ["cf.insert", -1, [], 0, 0, 0, ["@cuckoo"], [], [], [["cf.insertnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []]]], "cf.insertnx": ["cf.insertnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.exists": ["cf.exists", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.mexists": ["cf.mexists", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.del": ["cf.del", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.count": ["cf.count", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.scandump": ["cf.scandump", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.loadchunk": ["cf.loadchunk", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.info": ["cf.info", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cms.initbydim": ["cms.initbydim", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.initbyprob": ["cms.initbyprob", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.incrby": ["cms.incrby", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.query": ["cms.query", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.merge": ["cms.merge", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.info": ["cms.info", -1, [], 0, 0, 0, ["@cms"], [], [], []], "topk.reserve": ["topk.reserve", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.add": ["topk.add", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.incrby": ["topk.incrby", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.query": ["topk.query", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.count": ["topk.count", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.list": ["topk.list", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.info": ["topk.info", -1, [], 0, 0, 0, ["@topk"], [], [], []], "tdigest.create": ["tdigest.create", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.reset": ["tdigest.reset", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.add": ["tdigest.add", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.merge": ["tdigest.merge", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.min": ["tdigest.min", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.max": ["tdigest.max", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.quantile": ["tdigest.quantile", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.cdf": ["tdigest.cdf", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.trimmed_mean": ["tdigest.trimmed_mean", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.rank": ["tdigest.rank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.revrank": ["tdigest.revrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.byrank": ["tdigest.byrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.byrevrank": ["tdigest.byrevrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.info": ["tdigest.info", -1, [], 0, 0, 0, ["@tdigest"], [], [], []]}
+{"acl cat": ["acl|cat", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "acl": ["acl", -1, [], 0, 0, 0, [], [], [], [["acl|cat", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["acl|deluser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|genpass", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["acl|getuser", 3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|list", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|load", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|log", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|save", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|setuser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|users", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], ["acl|whoami", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []]]], "acl deluser": ["acl|deluser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl genpass": ["acl|genpass", -2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "acl getuser": ["acl|getuser", 3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl list": ["acl|list", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl load": ["acl|load", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl log": ["acl|log", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl save": ["acl|save", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl setuser": ["acl|setuser", -3, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl users": ["acl|users", 2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "acl whoami": ["acl|whoami", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "append": ["append", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "auth": ["auth", -2, ["noscript", "loading", "stale", "fast", "no_auth", "allow_busy"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "bgsave": ["bgsave", -1, ["admin", "noscript", "no_async_loading"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "bitcount": ["bitcount", -2, ["readonly"], 1, 1, 1, ["@bitmap", "@read", "@slow"], [], [], []], "bitfield": ["bitfield", -2, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], [["bitfield_ro", -2, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []]]], "bitop": ["bitop", -4, ["write", "denyoom"], 2, 3, 1, ["@bitmap", "@slow", "@write"], [], [], []], "bitpos": ["bitpos", -3, ["readonly"], 1, 1, 1, ["@bitmap", "@read", "@slow"], [], [], []], "blmove": ["blmove", 6, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "blmpop": ["blmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "blpop": ["blpop", -3, ["write", "blocking"], 1, 1, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "brpop": ["brpop", -3, ["write", "blocking"], 1, 1, 1, ["@blocking", "@list", "@slow", "@write"], [], [], [["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []]]], "brpoplpush": ["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@blocking", "@list", "@slow", "@write"], [], [], []], "bzmpop": ["bzmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@blocking", "@slow", "@sortedset", "@write"], [], [], []], "bzpopmax": ["bzpopmax", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@blocking", "@fast", "@sortedset", "@write"], [], [], []], "bzpopmin": ["bzpopmin", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@blocking", "@fast", "@sortedset", "@write"], [], [], []], "client getname": ["client|getname", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client": ["client", -1, [], 0, 0, 0, [], [], [], [["client|getname", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|id", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|info", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|list", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@connection", "@dangerous", "@slow"], [], [], []], ["client|setinfo", 4, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["client|setname", 3, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], "client id": ["client|id", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client info": ["client|info", 2, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client list": ["client|list", -2, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@connection", "@dangerous", "@slow"], [], [], []], "client setinfo": ["client|setinfo", 4, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "client setname": ["client|setname", 3, ["noscript", "loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "command": ["command", -1, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], [["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|docs", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|getkeys", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], [["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], ["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|help", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|list", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []]]], "command count": ["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "command info": ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@connection", "@slow"], [], [], []], "config set": ["config|set", -4, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "config": ["config", -1, [], 0, 0, 0, [], [], [], [["config|set", -4, ["admin", "noscript", "loading", "stale"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []]]], "copy": ["copy", -3, ["write", "denyoom"], 1, 2, 1, ["@keyspace", "@slow", "@write"], [], [], []], "dbsize": ["dbsize", 1, ["readonly", "fast"], 0, 0, 0, ["@fast", "@keyspace", "@read"], [], [], []], "decr": ["decr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "decrby": ["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "del": ["del", -2, ["write"], 1, 1, 1, ["@keyspace", "@slow", "@write"], [], [], []], "discard": ["discard", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "dump": ["dump", 2, ["readonly"], 1, 1, 1, ["@keyspace", "@read", "@slow"], [], [], []], "echo": ["echo", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "eval": ["eval", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], ["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []], ["eval_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], "evalsha": ["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@scripting", "@slow"], [], [], []]]], "exec": ["exec", 1, ["noscript", "loading", "stale", "skip_slowlog"], 0, 0, 0, ["@slow", "@transaction"], [], [], []], "exists": ["exists", -2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "expire": ["expire", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], [["expireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []]]], "expireat": ["expireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "expiretime": ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "flushall": ["flushall", -1, ["write"], 0, 0, 0, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []], "flushdb": ["flushdb", -1, ["write"], 0, 0, 0, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []], "geoadd": ["geoadd", -5, ["write", "denyoom"], 1, 1, 1, ["@geo", "@slow", "@write"], [], [], []], "geodist": ["geodist", -4, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geohash": ["geohash", -2, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geopos": ["geopos", -2, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "georadius": ["georadius", -6, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], "georadiusbymember": ["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@geo", "@slow", "@write"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []]]], "georadiusbymember_ro": ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "georadius_ro": ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], []], "geosearch": ["geosearch", -7, ["readonly"], 1, 1, 1, ["@geo", "@read", "@slow"], [], [], [["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@geo", "@slow", "@write"], [], [], []]]], "geosearchstore": ["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@geo", "@slow", "@write"], [], [], []], "get": ["get", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], [["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []], ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["getex", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "getbit": ["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@bitmap", "@fast", "@read"], [], [], []], "getdel": ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "getex": ["getex", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "getrange": ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "getset": ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "hdel": ["hdel", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hello": ["hello", -1, ["noscript", "loading", "stale", "fast", "no_auth", "allow_busy"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "hexists": ["hexists", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hexpire": ["hexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []]]], "hexpireat": ["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hexpiretime": ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hget": ["hget", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], [["hgetall", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], ["hgetf", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hgetex", -5, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hgetdel", -5, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hgetall": ["hgetall", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hincrby": ["hincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hincrbyfloat": ["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hkeys": ["hkeys", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hlen": ["hlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hmget": ["hmget", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hmset": ["hmset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hpersist": ["hpersist", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hpexpire": ["hpexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []]]], "hpexpireat": ["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hpexpiretime": ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hpttl": ["hpttl", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hrandfield": ["hrandfield", -2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hscan": ["hscan", -3, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "hset": ["hset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], [["hsetf", -6, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], ["hsetex", -6, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []]]], "hsetnx": ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hstrlen": ["hstrlen", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "httl": ["httl", -4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@hash", "@read"], [], [], []], "hvals": ["hvals", 2, ["readonly"], 1, 1, 1, ["@hash", "@read", "@slow"], [], [], []], "incr": ["incr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "incrby": ["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []]]], "incrbyfloat": ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "keys": ["keys", 2, ["readonly"], 0, 0, 0, ["@dangerous", "@keyspace", "@read", "@slow"], [], [], []], "lastsave": ["lastsave", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@admin", "@dangerous", "@fast"], [], [], []], "lcs": ["lcs", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "lindex": ["lindex", 3, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "linsert": ["linsert", 5, ["write", "denyoom"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "llen": ["llen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@list", "@read"], [], [], []], "lmove": ["lmove", 5, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []], "lmpop": ["lmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "lpop": ["lpop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "lpos": ["lpos", -3, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "lpush": ["lpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []]]], "lpushx": ["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "lrange": ["lrange", 4, ["readonly"], 1, 1, 1, ["@list", "@read", "@slow"], [], [], []], "lrem": ["lrem", 4, ["write"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "lset": ["lset", 4, ["write", "denyoom"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "ltrim": ["ltrim", 4, ["write"], 1, 1, 1, ["@list", "@slow", "@write"], [], [], []], "mget": ["mget", -2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], []], "move": ["move", 3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "mset": ["mset", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], [["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []], ["msetex", -4, ["write", "denyoom", "movablekeys"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []]]], "msetnx": ["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []], "multi": ["multi", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "persist": ["persist", 2, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "pexpire": ["pexpire", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], [["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []]]], "pexpireat": ["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "pexpiretime": ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "pfadd": ["pfadd", -2, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hyperloglog", "@write"], [], [], []], "pfcount": ["pfcount", -2, ["readonly"], 1, 1, 1, ["@hyperloglog", "@read", "@slow"], [], [], []], "pfmerge": ["pfmerge", -2, ["write", "denyoom"], 1, 2, 1, ["@hyperloglog", "@slow", "@write"], [], [], []], "ping": ["ping", -1, ["fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "psetex": ["psetex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "psubscribe": ["psubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pttl": ["pttl", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "publish": ["publish", 3, ["pubsub", "loading", "stale", "fast"], 0, 0, 0, ["@fast", "@pubsub"], [], [], []], "pubsub": ["pubsub", -2, [], 0, 0, 0, ["@slow"], [], [], [["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []]]], "pubsub channels": ["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub help": ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], "pubsub numpat": ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub numsub": ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub shardchannels": ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pubsub shardnumsub": ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "punsubscribe": ["punsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "randomkey": ["randomkey", 1, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "rename": ["rename", 3, ["write"], 1, 2, 1, ["@keyspace", "@slow", "@write"], [], [], [["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@fast", "@keyspace", "@write"], [], [], []]]], "renamenx": ["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@fast", "@keyspace", "@write"], [], [], []], "restore": ["restore", -4, ["write", "denyoom"], 1, 1, 1, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], [["restore-asking", -4, ["write", "denyoom", "asking"], 1, 1, 1, ["@dangerous", "@keyspace", "@slow", "@write"], [], [], []]]], "rpop": ["rpop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []]]], "rpoplpush": ["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@list", "@slow", "@write"], [], [], []], "rpush": ["rpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], [["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []]]], "rpushx": ["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@list", "@write"], [], [], []], "sadd": ["sadd", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "save": ["save", 1, ["admin", "noscript", "no_async_loading", "no_multi"], 0, 0, 0, ["@admin", "@dangerous", "@slow"], [], [], []], "scan": ["scan", -2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "scard": ["scard", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "script": ["script", -2, [], 0, 0, 0, ["@slow"], [], [], [["script|debug", 3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|kill", 2, ["noscript", "allow_busy"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []]]], "script exists": ["script|exists", -3, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script flush": ["script|flush", -2, ["noscript"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script help": ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "script load": ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@scripting", "@slow"], [], [], []], "sdiff": ["sdiff", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sdiffstore": ["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "select": ["select", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@connection", "@fast"], [], [], []], "set": ["set", -3, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], [["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], []], ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []]]], "setbit": ["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@bitmap", "@slow", "@write"], [], [], []], "setex": ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "setnx": ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@string", "@write"], [], [], []], "setrange": ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@slow", "@string", "@write"], [], [], []], "sinter": ["sinter", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sintercard": ["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sinterstore": ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "sismember": ["sismember", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "smembers": ["smembers", 2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "smismember": ["smismember", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@set"], [], [], []], "smove": ["smove", 4, ["write", "fast"], 1, 2, 1, ["@fast", "@set", "@write"], [], [], []], "sort": ["sort", -2, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@set", "@slow", "@sortedset", "@write"], [], [], [["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@read", "@set", "@slow", "@sortedset"], [], [], []]]], "sort_ro": ["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@dangerous", "@list", "@read", "@set", "@slow", "@sortedset"], [], [], []], "spop": ["spop", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "spublish": ["spublish", 3, ["pubsub", "loading", "stale", "fast"], 1, 1, 1, ["@fast", "@pubsub"], [], [], []], "srandmember": ["srandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "srem": ["srem", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@set", "@write"], [], [], []], "sscan": ["sscan", -3, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "ssubscribe": ["ssubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "strlen": ["strlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@string"], [], [], []], "subscribe": ["subscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "substr": ["substr", 4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@string"], [], [], []], "sunion": ["sunion", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []]]], "sunionstore": ["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@set", "@slow", "@write"], [], [], []], "sunsubscribe": ["sunsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "swapdb": ["swapdb", 3, ["write", "fast"], 0, 0, 0, ["@dangerous", "@fast", "@keyspace", "@write"], [], [], []], "time": ["time", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@fast"], [], [], []], "ttl": ["ttl", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "type": ["type", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@read"], [], [], []], "unlink": ["unlink", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@keyspace", "@write"], [], [], []], "unsubscribe": ["unsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "unwatch": ["unwatch", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "watch": ["watch", -2, ["noscript", "loading", "stale", "fast", "allow_busy"], 1, 1, 1, ["@fast", "@transaction"], [], [], []], "xack": ["xack", -4, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xadd": ["xadd", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xautoclaim": ["xautoclaim", -6, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xclaim": ["xclaim", -6, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xdel": ["xdel", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "xgroup create": ["xgroup|create", -5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], [["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], "xgroup": ["xgroup", -1, [], 0, 0, 0, [], [], [], [["xgroup|create", -5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], [["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], ["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|delconsumer", 5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|destroy", 4, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], ["xgroup|setid", -5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []]]], "xgroup createconsumer": ["xgroup|createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup delconsumer": ["xgroup|delconsumer", 5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup destroy": ["xgroup|destroy", 4, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xgroup setid": ["xgroup|setid", -5, ["write"], 2, 2, 1, ["@slow", "@stream", "@write"], [], [], []], "xinfo consumers": ["xinfo|consumers", 4, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xinfo": ["xinfo", -1, [], 0, 0, 0, [], [], [], [["xinfo|consumers", 4, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], ["xinfo|groups", 3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], ["xinfo|stream", -3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []]]], "xinfo groups": ["xinfo|groups", 3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xinfo stream": ["xinfo|stream", -3, ["readonly"], 2, 2, 1, ["@read", "@slow", "@stream"], [], [], []], "xlen": ["xlen", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@stream"], [], [], []], "xpending": ["xpending", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xrange": ["xrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xread": ["xread", -4, ["readonly", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@read", "@slow", "@stream"], [], [], [["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@slow", "@stream", "@write"], [], [], []]]], "xreadgroup": ["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@blocking", "@slow", "@stream", "@write"], [], [], []], "xrevrange": ["xrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@stream"], [], [], []], "xtrim": ["xtrim", -4, ["write"], 1, 1, 1, ["@slow", "@stream", "@write"], [], [], []], "zadd": ["zadd", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zcard": ["zcard", 2, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zcount": ["zcount", 4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zdiff": ["zdiff", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zdiffstore": ["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zincrby": ["zincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zinter": ["zinter", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zintercard": ["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zinterstore": ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zlexcount": ["zlexcount", 4, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zmpop": ["zmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zmscore": ["zmscore", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zpopmax": ["zpopmax", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zpopmin": ["zpopmin", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], []], "zrandmember": ["zrandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrange": ["zrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zrangebylex": ["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrangebyscore": ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrangestore": ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zrank": ["zrank", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zrem": ["zrem", -3, ["write", "fast"], 1, 1, 1, ["@fast", "@sortedset", "@write"], [], [], [["zremrangebylex", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zremrangebylex": ["zremrangebylex", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zremrangebyrank": ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zremrangebyscore": ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@slow", "@sortedset", "@write"], [], [], []], "zrevrange": ["zrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []]]], "zrevrangebylex": ["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrevrangebyscore": ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zrevrank": ["zrevrank", -3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zscan": ["zscan", -3, ["readonly"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], []], "zscore": ["zscore", 3, ["readonly", "fast"], 1, 1, 1, ["@fast", "@read", "@sortedset"], [], [], []], "zunion": ["zunion", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@slow", "@sortedset"], [], [], [["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []]]], "zunionstore": ["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@slow", "@sortedset", "@write"], [], [], []], "vadd": ["vadd", -1, [], 0, 0, 0, [], [], [], []], "vcard": ["vcard", -1, [], 0, 0, 0, [], [], [], []], "vdim": ["vdim", -1, [], 0, 0, 0, [], [], [], []], "vemb": ["vemb", -1, [], 0, 0, 0, [], [], [], []], "vgetattr": ["vgetattr", -1, [], 0, 0, 0, [], [], [], []], "vinfo": ["vinfo", -1, [], 0, 0, 0, [], [], [], []], "vlinks": ["vlinks", -1, [], 0, 0, 0, [], [], [], []], "vrandmember": ["vrandmember", -1, [], 0, 0, 0, [], [], [], []], "vrange": ["vrange", -4, ["READONLY"], 0, 0, 0, [], [], [], []], "vrem": ["vrem", -1, [], 0, 0, 0, [], [], [], []], "vsetattr": ["vsetattr", -1, [], 0, 0, 0, [], [], [], []], "vsim": ["vsim", -1, [], 0, 0, 0, [], [], [], []], "msetex": ["msetex", -4, ["write", "denyoom", "movablekeys"], 1, 1, 2, ["@slow", "@string", "@write"], [], [], []], "hgetex": ["hgetex", -5, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hsetex": ["hsetex", -6, ["write", "denyoom", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "hgetdel": ["hgetdel", -5, ["write", "fast"], 1, 1, 1, ["@fast", "@hash", "@write"], [], [], []], "xcfgset": ["xcfgset", -2, ["write", "fast"], 1, 1, 1, ["@fast", "@stream", "@write"], [], [], []], "json.del": ["json.del", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.forget": ["json.forget", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.get": ["json.get", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.toggle": ["json.toggle", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.clear": ["json.clear", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.set": ["json.set", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.mset": ["json.mset", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.merge": ["json.merge", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.mget": ["json.mget", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.numincrby": ["json.numincrby", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.nummultby": ["json.nummultby", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.strappend": ["json.strappend", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.strlen": ["json.strlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrappend": ["json.arrappend", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrindex": ["json.arrindex", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrinsert": ["json.arrinsert", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrlen": ["json.arrlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrpop": ["json.arrpop", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.arrtrim": ["json.arrtrim", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.objkeys": ["json.objkeys", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.objlen": ["json.objlen", -1, [], 0, 0, 0, ["@json"], [], [], []], "json.type": ["json.type", -1, [], 0, 0, 0, ["@json"], [], [], []], "ts.create": ["ts.create", -1, [], 0, 0, 0, ["@timeseries"], [], [], [["ts.createrule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []]]], "ts.del": ["ts.del", -1, [], 0, 0, 0, ["@timeseries"], [], [], [["ts.deleterule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []]]], "ts.alter": ["ts.alter", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.add": ["ts.add", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.madd": ["ts.madd", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.incrby": ["ts.incrby", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.decrby": ["ts.decrby", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.createrule": ["ts.createrule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.deleterule": ["ts.deleterule", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.range": ["ts.range", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.revrange": ["ts.revrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mrange": ["ts.mrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mrevrange": ["ts.mrevrange", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.get": ["ts.get", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.mget": ["ts.mget", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.info": ["ts.info", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "ts.queryindex": ["ts.queryindex", -1, [], 0, 0, 0, ["@timeseries"], [], [], []], "bf.reserve": ["bf.reserve", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.add": ["bf.add", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.madd": ["bf.madd", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.insert": ["bf.insert", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.exists": ["bf.exists", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.mexists": ["bf.mexists", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.scandump": ["bf.scandump", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.loadchunk": ["bf.loadchunk", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.info": ["bf.info", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "bf.card": ["bf.card", -1, [], 0, 0, 0, ["@bloom"], [], [], []], "cf.reserve": ["cf.reserve", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.add": ["cf.add", -1, [], 0, 0, 0, ["@cuckoo"], [], [], [["cf.addnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []]]], "cf.addnx": ["cf.addnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.insert": ["cf.insert", -1, [], 0, 0, 0, ["@cuckoo"], [], [], [["cf.insertnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []]]], "cf.insertnx": ["cf.insertnx", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.exists": ["cf.exists", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.mexists": ["cf.mexists", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.del": ["cf.del", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.count": ["cf.count", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.scandump": ["cf.scandump", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.loadchunk": ["cf.loadchunk", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cf.info": ["cf.info", -1, [], 0, 0, 0, ["@cuckoo"], [], [], []], "cms.initbydim": ["cms.initbydim", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.initbyprob": ["cms.initbyprob", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.incrby": ["cms.incrby", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.query": ["cms.query", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.merge": ["cms.merge", -1, [], 0, 0, 0, ["@cms"], [], [], []], "cms.info": ["cms.info", -1, [], 0, 0, 0, ["@cms"], [], [], []], "topk.reserve": ["topk.reserve", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.add": ["topk.add", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.incrby": ["topk.incrby", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.query": ["topk.query", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.count": ["topk.count", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.list": ["topk.list", -1, [], 0, 0, 0, ["@topk"], [], [], []], "topk.info": ["topk.info", -1, [], 0, 0, 0, ["@topk"], [], [], []], "tdigest.create": ["tdigest.create", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.reset": ["tdigest.reset", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.add": ["tdigest.add", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.merge": ["tdigest.merge", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.min": ["tdigest.min", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.max": ["tdigest.max", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.quantile": ["tdigest.quantile", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.cdf": ["tdigest.cdf", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.trimmed_mean": ["tdigest.trimmed_mean", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.rank": ["tdigest.rank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.revrank": ["tdigest.revrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.byrank": ["tdigest.byrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.byrevrank": ["tdigest.byrevrank", -1, [], 0, 0, 0, ["@tdigest"], [], [], []], "tdigest.info": ["tdigest.info", -1, [], 0, 0, 0, ["@tdigest"], [], [], []]}
diff --git a/fakeredis/model/__init__.py b/fakeredis/model/__init__.py
index 0c02c98b..51286671 100644
--- a/fakeredis/model/__init__.py
+++ b/fakeredis/model/__init__.py
@@ -38,6 +38,14 @@
"TDigest",
]
+try:
+ import numpy as np # noqa: F401
+ from ._vectorset import VectorSet, Vector # noqa: F401
+
+ __all__.extend(["VectorSet", "Vector"])
+except ImportError:
+ pass
+
try:
import probables # noqa: F401
from ._filters import ScalableCuckooFilter, ScalableBloomFilter # noqa: F401
diff --git a/fakeredis/model/_vectorset.py b/fakeredis/model/_vectorset.py
new file mode 100644
index 00000000..a9f0edff
--- /dev/null
+++ b/fakeredis/model/_vectorset.py
@@ -0,0 +1,242 @@
+import json
+import math
+import re
+import struct
+from typing import List, Dict, Any, Literal, Optional, Iterator, Self, Union
+
+import numpy as np
+from jsonpath_ng import JSONPath
+from jsonpath_ng.exceptions import JsonPathParserError
+from jsonpath_ng.ext import parse
+
+from fakeredis import _msgs as msgs
+from fakeredis._helpers import SimpleError
+
+QUANTIZATION_TYPE = Literal["noquant", "bin", "int8"]
+
+
+def _update_to_jsonpath_format(path: Union[bytes, str]) -> str:
+ path_str = path.decode() if isinstance(path, bytes) else path
+ path_str = path_str.replace("and", "&").replace("or", "|").replace("not", "!").replace(".", "@.")
+
+ # Replace `v in [x, y, z]` with `(v=~'x|y|z')`
+ def expand_in(m: re.Match) -> str:
+ var = m.group(1)
+ items = [item.strip().replace("'", "") for item in m.group(2).split(",")]
+ return f"({var}=~'{'|'.join(items)}')"
+
+ path_str = re.sub(r"(\S+)\s+in\s+\[([^]]+)]", expand_in, path_str)
+
+ return f"$[?({path_str})]"
+
+
+def _parse_jsonfilter(path: Union[str, bytes]) -> JSONPath:
+ path_str: str = _update_to_jsonpath_format(path)
+ try:
+ return parse(path_str)
+ except JsonPathParserError:
+ raise SimpleError(msgs.JSON_PATH_DOES_NOT_EXIST.format(path_str))
+
+
+def quantize_int8(x):
+ qmin = -(2.0**7) if (x < 0).any() else 0 # Signed or unsigned range
+ qmax = 2.0**7 - 1 if (x < 0).any() else 2.0**8 - 1
+
+ min_val, max_val = x.min(), x.max()
+
+ # Calculate the scale factor
+ scale = (max_val - min_val) / (qmax - qmin)
+
+ # Calculate the initial zero point and clamp it to the valid range
+ initial_zero_point = qmin - min_val / scale
+ zero_point = int(np.clip(initial_zero_point, qmin, qmax))
+
+ # Quantize the values and round them
+ q_x = zero_point + x / scale
+ q_x = np.clip(q_x, qmin, qmax).round().astype(np.int8) # Use np.int8 for the final data type
+
+ return q_x, scale, zero_point
+
+
+class Vector:
+ def __init__(
+ self, name: bytes, values: List[float], attributes: Optional[bytes], quantization: QUANTIZATION_TYPE, ef: int
+ ) -> None:
+ self.name = name
+ self.values = values
+ self.attributes = attributes
+ self.quantization = quantization
+ self.l2_norm = sum(v * v for v in values) ** 0.5
+ if self.quantization == "bin":
+ self.values = [1 if v > 0 else -1 for v in self.values]
+
+ def __repr__(self):
+ return f"Vector(name={self.name}, values={self.values}, attributes={self.attributes}, quantization={self.quantization})"
+
+ def __hash__(self):
+ return hash(self.name)
+
+ @classmethod
+ def from_vector_values(cls, values: List[float]) -> Self:
+ return cls("", values, b"", "int8", 0)
+
+ def raw(self) -> List[Any]:
+ raw_bytes = struct.pack(f"{len(self.values)}f", *self.values)
+ if self.quantization == "int8":
+ norm_values = np.array(self.values) / self.l2_norm if self.l2_norm != 0 else np.array(self.values)
+ range_val = float(np.max(np.abs(norm_values)))
+ return [self.quantization.encode(), raw_bytes, self.l2_norm, range_val]
+ if self.quantization == "bin":
+ return [self.quantization.encode(), raw_bytes, self.l2_norm]
+
+ return [b"f32", raw_bytes, self.l2_norm]
+
+ def similarity(self, other: Self) -> float:
+ me = np.array(self.values)
+ o = np.array(other.values)
+ denominator = self.l2_norm * other.l2_norm
+ if denominator == 0:
+ return 0.0
+ return float(np.dot(me, o)) / denominator
+
+ def accept_filter(self, filter_expression: Optional[bytes]) -> bool:
+ if filter_expression is None:
+ return True
+ if self.attributes is None:
+ return False
+ json_obj = json.loads(self.attributes)
+ return len(_parse_jsonfilter(filter_expression).find([json_obj])) > 0
+
+
+class VectorSet:
+ def __init__(self, dimensions: int):
+ self._dimensions = dimensions
+ self._vectors: Dict[bytes, Vector] = dict()
+ self._links: Dict[bytes, int] = dict()
+ self._quant_type: Optional[str] = None
+ self._node_uid_counter: int = 0
+ self._max_level: int = 0
+ self._node_levels: Dict[bytes, int] = dict()
+ self._node_links: Dict[bytes, Dict[int, List[bytes]]] = dict()
+
+ @staticmethod
+ def _compute_level(node_index: int, m: int) -> int:
+ if m <= 1:
+ return 0
+ return int(math.log(node_index + 1) / math.log(m))
+
+ @property
+ def dimensions(self) -> int:
+ return self._dimensions
+
+ @property
+ def card(self) -> int:
+ return len(self._vectors)
+
+ def vector_names(self) -> List[bytes]:
+ return list(self._vectors.keys())
+
+ def exists(self, name: bytes) -> bool:
+ return name in self._vectors
+
+ def add(self, vector: Vector, numlinks: int) -> None:
+ if self._quant_type is None:
+ self._quant_type = vector.quantization
+
+ node_index = self._node_uid_counter
+ self._node_uid_counter += 1
+
+ level = self._compute_level(node_index, numlinks)
+ self._node_levels[vector.name] = level
+ if level > self._max_level:
+ self._max_level = level
+
+ # Build links for this node at each of its levels
+ self._node_links[vector.name] = {}
+ query = np.array(vector.values)
+ for lvl in range(level + 1):
+ candidates = [name for name, level in self._node_levels.items() if level >= lvl and name != vector.name]
+ if candidates:
+ scored = []
+ for name in candidates:
+ cand = self._vectors[name]
+ cand_arr = np.array(cand.values)
+ norm = vector.l2_norm * cand.l2_norm
+ sim = float(np.dot(query, cand_arr)) / norm if norm > 0 else 0.0
+ scored.append((name, sim))
+ scored.sort(key=lambda x: x[1], reverse=True)
+ self._node_links[vector.name][lvl] = [n for n, _ in scored[:numlinks]]
+ else:
+ self._node_links[vector.name][lvl] = []
+
+ self._vectors[vector.name] = vector
+ self._links[vector.name] = numlinks
+
+ def remove(self, name: bytes) -> int:
+ if name not in self._vectors:
+ return 0
+ del self._vectors[name]
+ del self._links[name]
+ self._node_levels.pop(name, None)
+ if name in self._node_links:
+ del self._node_links[name]
+ for levels_links in self._node_links.values():
+ for neighbors in levels_links.values():
+ if name in neighbors:
+ neighbors.remove(name)
+ return 1
+
+ def info(self) -> Dict[bytes, Any]:
+ quant = self._quant_type or b"fp32"
+ # Normalize quantization type name for the info response
+ if quant == "noquant":
+ quant = b"f32"
+ return {
+ b"quant-type": quant.encode() if isinstance(quant, str) else quant,
+ b"vector-dim": self._dimensions,
+ b"size": len(self._vectors),
+ b"max-level": self._max_level,
+ b"vset-uid": 1,
+ b"hnsw-max-node-uid": self._node_uid_counter,
+ }
+
+ def links(self, name: bytes) -> Optional[Dict[int, List[bytes]]]:
+ if name not in self._vectors:
+ return None
+ return self._node_links.get(name, {0: []})
+
+ def range(
+ self,
+ min_value: Optional[bytes],
+ include_min: bool,
+ max_value: Optional[bytes],
+ include_max: bool,
+ count: Optional[int],
+ ) -> List[bytes]:
+ if count is not None and count < 0:
+ count = None
+ res: List[bytes] = []
+ for name in self._vectors.keys():
+ if (min_value is None or name > min_value or (include_min and name == min_value)) and (
+ max_value is None or name < max_value or (include_max and name == max_value)
+ ):
+ res.append(name)
+ if count is not None and len(res) >= count:
+ break
+ return res
+
+ def __contains__(self, k: bytes) -> bool:
+ return k in self._vectors
+
+ def __getitem__(self, k: bytes) -> Vector:
+ if k not in self._vectors:
+ raise KeyError(f"Vector with name {k} does not exist.")
+ return self._vectors[k]
+
+ def __iter__(self) -> Iterator[Vector]:
+ return iter(self._vectors.values())
+
+ def get(self, k: bytes) -> Optional[Vector]:
+ if k in self._vectors:
+ return self._vectors[k]
+ return None
diff --git a/fakeredis/stack/__init__.py b/fakeredis/stack/__init__.py
index ea860589..f4efcac5 100644
--- a/fakeredis/stack/__init__.py
+++ b/fakeredis/stack/__init__.py
@@ -2,10 +2,19 @@
from ._timeseries_mixin import TimeSeriesCommandsMixin
from ._topk_mixin import TopkCommandsMixin # noqa: F401
+try:
+ import numpy # noqa: F401
+ from ._vectorset_mixin import VectorSetCommandsMixin # noqa: F401
+except ImportError:
+
+ class VectorSetCommandsMixin:
+ pass
+
+
try:
from jsonpath_ng.ext import parse # noqa: F401
from redis.commands.json.path import Path # noqa: F401
- from ._json_mixin import JSONCommandsMixin, JSONObject # noqa: F401
+ from ._json_mixin import JSONCommandsMixin # noqa: F401
except ImportError as e:
if e.name == "fakeredis.stack._json_mixin":
raise e
@@ -37,10 +46,10 @@ class CMSCommandsMixin: # type: ignore # noqa: E303
__all__ = [
"TopkCommandsMixin",
"JSONCommandsMixin",
- "JSONObject",
"BFCommandsMixin",
"CFCommandsMixin",
"CMSCommandsMixin",
"TDigestCommandsMixin",
"TimeSeriesCommandsMixin",
+ "VectorSetCommandsMixin",
]
diff --git a/fakeredis/stack/_json_mixin.py b/fakeredis/stack/_json_mixin.py
index c3de988d..067b3387 100644
--- a/fakeredis/stack/_json_mixin.py
+++ b/fakeredis/stack/_json_mixin.py
@@ -15,8 +15,7 @@
from fakeredis._commands import Key, command, delete_keys, CommandItem, Int, Float
from fakeredis._helpers import SimpleString
from fakeredis.model import ZSet, ClientInfo
-
-JsonType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]
+from fakeredis._typing import JsonType
def _format_path(path: Union[bytes, str]) -> str:
@@ -75,8 +74,7 @@ def decode(cls, value: bytes) -> Any:
@classmethod
def encode(cls, value: Any) -> Optional[bytes]:
- """Serialize the supplied Python object into a valid, JSON-formatted
- byte-encoded string."""
+ """Serialize the supplied Python object into a valid, JSON-formatted byte-encoded string."""
return json.dumps(value, default=str).encode() if value is not None else None
diff --git a/fakeredis/stack/_vectorset_mixin.py b/fakeredis/stack/_vectorset_mixin.py
new file mode 100644
index 00000000..b1b74a39
--- /dev/null
+++ b/fakeredis/stack/_vectorset_mixin.py
@@ -0,0 +1,307 @@
+import itertools
+import random
+import struct
+from collections import OrderedDict
+from typing import Any, List, Optional, Union, Dict
+
+from fakeredis import _msgs as msgs
+from fakeredis._commands import Key, command, CommandItem, StringTest
+from fakeredis._helpers import SimpleError, casematch
+from fakeredis.model import VectorSet, Vector
+
+VSET_ERR_NOTEXIST = "ERR key does not exist"
+
+
+class VectorSetCommandsMixin:
+ """`CommandsMixin` for enabling VectorSet compatibility in `fakeredis`."""
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+
+ @command(name="VCARD", fixed=(Key(VectorSet),), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vcard(self, key: CommandItem) -> Optional[int]:
+ if key.value is None:
+ return None
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ return key.value.card
+
+ @command(name="VDIM", fixed=(Key(VectorSet),), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vdim(self, key: CommandItem) -> int:
+ if key.value is None:
+ raise SimpleError(VSET_ERR_NOTEXIST)
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ return key.value.dimensions
+
+ @command(name="VGETATTR", fixed=(Key(VectorSet), bytes), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vgetattr(self, key: CommandItem, member: bytes) -> Optional[bytes]:
+ if key.value is None:
+ return None
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ if member not in key.value:
+ return None
+ return key.value[member].attributes
+
+ @command(name="VSETATTR", fixed=(Key(VectorSet), bytes, bytes), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vsetattr(self, key: CommandItem, member: bytes, attr: bytes) -> int:
+ if key.value is None:
+ return 0
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ if member not in key.value:
+ return 0
+ key.value[member].attributes = attr
+ key.update(key.value)
+ return 1
+
+ @command(name="VADD", fixed=(Key(VectorSet),), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vadd(self, key: CommandItem, *args: bytes) -> int:
+ i = 0
+ numlinks, reduce, cas, vector_values, name, attributes, quantization, ef = [None] * 8
+
+ while i < len(args):
+ if casematch(args[i], b"ef") and i + 1 < len(args):
+ ef = int(args[i + 1])
+ i += 2
+ elif casematch(args[i], b"m") and i + 1 < len(args):
+ numlinks = int(args[i + 1])
+ i += 2
+ elif casematch(args[i], b"cas"):
+ cas = True # unused for now
+ i += 1
+ elif casematch(args[i], b"reduce") and i + 1 < len(args):
+ reduce = int(args[i + 1])
+ i += 2
+ elif casematch(args[i], b"fp32") and i + 2 < len(args):
+ byte_array = args[i + 1]
+ # convert byte array to list of floats
+ vector_values = list(struct.unpack(f"{len(byte_array) // 4}f", byte_array))
+ name = args[i + 2]
+ i += 3
+ elif casematch(args[i], b"bin") or casematch(args[i], b"q8") or casematch(args[i], b"noquant"):
+ if quantization is not None:
+ raise SimpleError("ERR multiple quantization types specified")
+ quantization = args[i].lower().decode()
+ if quantization == "q8":
+ quantization = "int8"
+ i += 1
+ elif casematch(args[i], b"values") and i + 1 < len(args):
+ num_values = int(args[i + 1])
+ i += 2
+ if i + num_values > len(args): # VALUES num_values values element
+ raise SimpleError(msgs.WRONG_ARGS_MSG6.format("VADD"))
+ vector_values = [float(v) for v in args[i : i + num_values]]
+ name = args[i + num_values]
+ i += num_values + 1
+ elif casematch(args[i], b"setattr") and i + 1 < len(args):
+ attributes = args[i + 1]
+ i += 2
+ else:
+ raise SimpleError("ERR syntax error in 'VADD' command")
+ cas = cas or False
+ if reduce is not None and key.value is not None:
+ raise SimpleError("ERR cannot add projection to existing set without projection")
+ if reduce is not None and reduce < 0:
+ raise SimpleError("ERR invalid vector specification")
+ vector_set = key.value or VectorSet(reduce or len(vector_values))
+ dimensions = vector_set.dimensions
+
+ if len(vector_values) != dimensions:
+ # If reduce is specified, we allow vectors with more dimensions and just ignore the extra values.
+ vector_values = vector_values[:dimensions]
+
+ if vector_set.exists(name):
+ return 0
+
+ vector = Vector(name, vector_values, attributes, quantization or "int8", ef)
+ vector_set.add(vector, numlinks or 16)
+ key.update(vector_set)
+ return 1
+
+ @command(name="VEMB", fixed=(Key(VectorSet), bytes), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vemb(self, key: CommandItem, element: bytes, *args: bytes) -> List[float]:
+ if key.value is None:
+ return None
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ if element not in key.value:
+ return None
+ if len(args) > 1:
+ raise SimpleError("ERR wrong number of arguments for 'VEMB' command")
+ raw = False
+ if len(args) > 0 and casematch(args[0], b"raw"):
+ raw = True
+ vector: Vector = key.value[element]
+
+ # Return raw format if requested
+ if raw:
+ return vector.raw()
+ # Return the vector values as a list of floats
+ return vector.values
+
+ @command(name="VRANDMEMBER", fixed=(Key(VectorSet),), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vrandmember(self, key: CommandItem, *args: bytes) -> Optional[Union[bytes, List[bytes]]]:
+ if key.value is None:
+ return None if len(args) == 0 else []
+ try:
+ count = 1 if len(args) == 0 else int(args[0])
+ except ValueError:
+ raise SimpleError("ERR COUNT value is not an integer")
+ vector_set: VectorSet = key.value
+ vector_names = vector_set.vector_names()
+ if count < 0: # Allow repetitions
+ res = random.choices(sorted(vector_names), k=-count)
+ else: # Unique values from hash
+ count = min(count, len(vector_names))
+ res = random.sample(sorted(vector_names), count)
+ return res[0] if len(args) == 0 else res
+
+ @command(name="VREM", fixed=(Key(VectorSet), bytes), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vrem(self, key: CommandItem, member: bytes) -> int:
+ if key.value is None:
+ return 0
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ return key.value.remove(member)
+
+ @command(
+ name="VRANGE",
+ fixed=(Key(VectorSet), StringTest, StringTest),
+ repeat=(bytes,),
+ flags=msgs.FLAG_DO_NOT_CREATE,
+ )
+ def vrange(self, key: CommandItem, _min: StringTest, _max: StringTest, *args: bytes) -> List[bytes]:
+ if len(args) > 1:
+ raise SimpleError(msgs.WRONG_ARGS_MSG6.format("VRANGE"))
+ if key.value is None:
+ return []
+ vset = key.value
+ if not isinstance(vset, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ count = None
+ if len(args) == 1:
+ count = int(args[0])
+ if count == 0:
+ return []
+ res = vset.range(_min.value, _min.inclusive, _max.value, _max.inclusive, count)
+ return res
+
+ @command(name="VSIM", fixed=(Key(VectorSet),), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vsim(self, key: CommandItem, *args: bytes) -> Union[List[bytes], Dict[bytes, float]]:
+ """
+ VSIM key (ELE | FP32 | VALUES num) (vector | element) [WITHSCORES] [WITHATTRIBS] [COUNT num]
+ [EPSILON delta] [EF search-exploration-factor] [FILTER expression] [FILTER-EF max-filtering-effort]
+ [TRUTH] [NOTHREAD]
+ """
+ if key.value is None:
+ return []
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ vector_set: VectorSet = key.value
+ vector: Optional[Vector] = None # The vector to compare against.
+ with_scores, with_attributes, count, epsilon, filter_expression = False, False, 10, None, None
+ i = 0
+ while i < len(args):
+ if casematch(args[i], b"ele") and i + 1 < len(args):
+ if vector is not None:
+ raise SimpleError("ERR ELE | FP32 | VALUES num")
+ vector = key.value.get(args[i + 1])
+ if vector is None:
+ raise SimpleError("element not found in set")
+ i += 2
+ elif casematch(args[i], b"fp32") and i + 1 < len(args):
+ if vector is not None:
+ raise SimpleError("ERR ELE | FP32 | VALUES num")
+ byte_array = args[i + 1]
+ vector_values = list(struct.unpack(f"{len(byte_array) // 4}f", byte_array))
+ vector = Vector.from_vector_values(vector_values)
+ i += 2
+ elif casematch(args[i], b"values") and i + 1 < len(args):
+ if vector is not None:
+ raise SimpleError("ERR ELE | FP32 | VALUES num")
+ num_values = int(args[i + 1])
+ i += 2
+ if i + num_values > len(args): # VALUES num_values values element
+ raise SimpleError(msgs.WRONG_ARGS_MSG6.format("VADD"))
+ vector_values = [float(v) for v in args[i : i + num_values]]
+ vector = Vector.from_vector_values(vector_values)
+ i += num_values
+ elif casematch(args[i], b"withscores"):
+ with_scores = True
+ i += 1
+ elif casematch(args[i], b"withattribs"):
+ with_attributes = True
+ i += 1
+ elif casematch(args[i], b"count") and i + 1 < len(args):
+ count = int(args[i + 1])
+ i += 2
+ elif casematch(args[i], b"epsilon") and i + 1 < len(args):
+ epsilon = float(args[i + 1])
+ i += 2
+ elif casematch(args[i], b"ef") and i + 1 < len(args):
+ ef = int(args[i + 1]) # noqa: F841
+ i += 2
+ elif casematch(args[i], b"filter") and i + 1 < len(args):
+ filter_expression = args[i + 1]
+ i += 2
+ elif casematch(args[i], b"filter-ef") and i + 1 < len(args):
+ filter_expression_ef = args[i + 1] # noqa: F841
+ i += 2
+ elif casematch(args[i], b"truth"):
+ i += 1
+ elif casematch(args[i], b"nothread"):
+ i += 1
+ else:
+ raise SimpleError(msgs.SYNTAX_ERROR_MSG)
+
+ if vector is None:
+ raise SimpleError(VSET_ERR_NOTEXIST)
+ res: Dict[Vector, float] = {v: v.similarity(vector) for v in vector_set if v.accept_filter(filter_expression)}
+ if epsilon is not None:
+ res = {k: v for k, v in res.items() if v >= 1 - epsilon}
+ res = OrderedDict(itertools.islice(sorted(res.items(), key=lambda t: t[1], reverse=True), count))
+ if with_scores and with_attributes:
+ if self._client_info.protocol_version == 2:
+ return list(itertools.chain.from_iterable([[k.name, v, k.attributes] for k, v in res.items()]))
+ return {k.name: [v, k.attributes] for k, v in res.items()}
+ if with_scores:
+ return {k.name: v for k, v in res.items()}
+ if with_attributes:
+ return {k.name: k.attributes for k in res}
+ return [k.name for k in res]
+
+ @command(name="VINFO", fixed=(Key(VectorSet),), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vinfo(self, key: CommandItem):
+ if key.value is None:
+ return None
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ info = key.value.info()
+ return info
+
+ @command(name="VLINKS", fixed=(Key(VectorSet), bytes), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+ def vlinks(self, key: CommandItem, elem: bytes, *args: bytes):
+ if key.value is None:
+ return None
+ if not isinstance(key.value, VectorSet):
+ raise SimpleError(msgs.WRONGTYPE_MSG)
+ vset: VectorSet = key.value
+ if elem not in vset:
+ return None
+ with_scores = len(args) > 0 and casematch(args[0], b"withscores")
+ node_links = vset.links(elem)
+ levels = sorted(node_links.keys())
+ if not with_scores:
+ # Both RESP2 and RESP3: list of lists of bytes names per layer
+ return [node_links[lvl] for lvl in levels]
+ query_vector = vset[elem]
+ result = []
+ for lvl in levels:
+ layer_dict = {}
+ for name in node_links[lvl]:
+ if name in vset:
+ layer_dict[name] = vset[name].similarity(query_vector)
+ result.append(layer_dict)
+ return result
diff --git a/pyproject.toml b/pyproject.toml
index 6312793d..23596a92 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -57,6 +57,10 @@ probabilistic = ["pyprobables>=0.6"]
valkey = [
"valkey>=6 ; python_version >= '3.8'",
]
+vectorset = [
+ "numpy>=2.4.0 ; python_version >= '3.11'",
+ "jsonpath_ng>=1.6 ; python_version >= '3.11'",
+]
[project.urls]
Homepage = "https://github.com/cunla/fakeredis-py"
@@ -70,6 +74,7 @@ dev = [
"ruff>=0.15",
"mypy>=1.15 ; python_version >= '3.10'",
"pre-commit>=4.2 ; python_version >= '3.10'",
+ "pyyaml>=6 ; python_version >= '3.10'",
]
test = [
"coverage>=7.10 ; python_version >= '3.9' and python_version < '3.14'",
diff --git a/scripts/generate_supported_commands_doc.py b/scripts/generate_supported_commands_doc.py
index 4817ff8e..786ff3aa 100644
--- a/scripts/generate_supported_commands_doc.py
+++ b/scripts/generate_supported_commands_doc.py
@@ -7,6 +7,7 @@
from dataclasses import dataclass
import requests
+import yaml
from fakeredis._commands import SUPPORTED_COMMANDS
@@ -123,13 +124,10 @@ def download_command_markdown(command: str) -> dict:
lines = f.readlines()
metadata = {}
if lines[0].strip() == "---":
- for line in lines[1:]:
+ for ind, line in enumerate(lines[1:], 1):
if line.strip() == "---":
break
- if ":" not in line:
- continue
- key, value = line.split(":", 1)
- metadata[key.strip()] = value.strip().strip('"')
+ metadata = yaml.load("".join(lines[1:ind]), Loader=yaml.SafeLoader)
return metadata
diff --git a/test/test_hypothesis/__init__.py b/test/test_hypothesis/__init__.py
index 6f9f4152..cb151177 100644
--- a/test/test_hypothesis/__init__.py
+++ b/test/test_hypothesis/__init__.py
@@ -8,13 +8,15 @@
"TestTransaction",
"TestZSet",
"TestZSetNoScores",
+ "TestJoint",
]
-from test.test_hypothesis.test_connection import TestConnection
-from test.test_hypothesis.test_hash import TestHash
-from test.test_hypothesis.test_list import TestList
-from test.test_hypothesis.test_server import TestServer
-from test.test_hypothesis.test_set import TestSet
-from test.test_hypothesis.test_string import TestString
-from test.test_hypothesis.test_transaction import TestTransaction
-from test.test_hypothesis.test_zset import TestZSet, TestZSetNoScores
+from .test_connection import TestConnection
+from .test_hash import TestHash
+from .test_list import TestList
+from .test_server import TestServer
+from .test_set import TestSet
+from .test_string import TestString
+from .test_transaction import TestTransaction
+from .test_zset import TestZSet, TestZSetNoScores
+from .test_hypothesis_joint import TestJoint
diff --git a/test/test_hypothesis/test_hypothesis_joint.py b/test/test_hypothesis/test_hypothesis_joint.py
new file mode 100644
index 00000000..f5d2df2a
--- /dev/null
+++ b/test/test_hypothesis/test_hypothesis_joint.py
@@ -0,0 +1,36 @@
+import hypothesis.strategies as st
+
+from .test_connection import TestConnection as _TestConnection
+from .test_server import TestServer as _TestServer
+from .test_zset import TestZSet as _TestZSet
+from .test_set import TestSet as _TestSet
+from .test_list import TestList as _TestList
+from .test_hash import TestHash as _TestHash
+from .base import BaseTest, common_commands, commands
+from .test_string import TestString as _TestString, string_commands
+
+bad_commands = (
+ # redis-py splits the command on spaces, and hangs if that ends up being an empty list
+ commands(st.text().filter(lambda x: bool(x.split())), st.lists(st.binary() | st.text()))
+)
+
+
+class TestJoint(BaseTest):
+ create_command_strategy = (
+ _TestString.create_command_strategy
+ | _TestHash.create_command_strategy
+ | _TestList.create_command_strategy
+ | _TestSet.create_command_strategy
+ | _TestZSet.create_command_strategy
+ )
+ command_strategy = (
+ _TestServer.server_commands
+ | _TestConnection.connection_commands
+ | string_commands
+ | _TestHash.hash_commands
+ | _TestList.list_commands
+ | _TestSet.set_commands
+ | _TestZSet.zset_commands
+ | common_commands
+ | bad_commands
+ )
diff --git a/test/test_hypothesis_joint.py b/test/test_hypothesis_joint.py
deleted file mode 100644
index bca59dab..00000000
--- a/test/test_hypothesis_joint.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import hypothesis.strategies as st
-
-from . import test_hypothesis as tests
-from .test_hypothesis.base import BaseTest, common_commands, commands
-from .test_hypothesis.test_string import string_commands
-
-bad_commands = (
- # redis-py splits the command on spaces, and hangs if that ends up being an empty list
- commands(st.text().filter(lambda x: bool(x.split())), st.lists(st.binary() | st.text()))
-)
-
-
-class TestJoint(BaseTest):
- create_command_strategy = (
- tests.TestString.create_command_strategy
- | tests.TestHash.create_command_strategy
- | tests.TestList.create_command_strategy
- | tests.TestSet.create_command_strategy
- | tests.TestZSet.create_command_strategy
- )
- command_strategy = (
- tests.TestServer.server_commands
- | tests.TestConnection.connection_commands
- | string_commands
- | tests.TestHash.hash_commands
- | tests.TestList.list_commands
- | tests.TestSet.set_commands
- | tests.TestZSet.zset_commands
- | common_commands
- | bad_commands
- )
diff --git a/test/test_mixins/test_acl_commands.py b/test/test_mixins/test_acl_commands.py
index 18f48548..7be9afb6 100644
--- a/test/test_mixins/test_acl_commands.py
+++ b/test/test_mixins/test_acl_commands.py
@@ -20,9 +20,13 @@
"hpexpire",
"hpexpiretime",
"httl",
+ "hgetdel",
+ "msetex",
+ "xcfgset",
}
+@pytest.mark.min_server("8.4")
def test_acl_cat(r: redis.Redis, real_server_details: ServerDetails):
fakeredis_categories = get_categories()
fakeredis_categories = {asbytes(cat) for cat in fakeredis_categories}
diff --git a/test/test_mixins/test_vset.py b/test/test_mixins/test_vset.py
new file mode 100644
index 00000000..86445413
--- /dev/null
+++ b/test/test_mixins/test_vset.py
@@ -0,0 +1,867 @@
+import json
+import random
+from typing import List
+
+import pytest
+
+from test import testtools
+
+pytest.importorskip("numpy")
+
+import numpy as np
+import redis
+from redis.commands.vectorset.commands import QuantizationOptions
+
+pytestmark = []
+pytestmark.extend(
+ [
+ pytest.mark.min_server("8"),
+ ]
+)
+
+
+def test_vgetattr_non_existing_element(r: redis.Redis):
+ # Test vgetattr on a non-existing element
+
+ assert r.vset().vgetattr("myset", "non_existing_element") is None
+
+ # Test vgetattr on an existing element with no attributes
+ r.vset().vadd("myset", [1, 2, 3], "elem1")
+ attrs = r.vset().vgetattr("myset", "elem1")
+ assert attrs is None
+
+ # Test vgetattr on an existing element with attributes
+ attrs_dict = {"key1": "value1", "key2": "value2"}
+ r.vset().vadd("myset", [4, 5, 6], "elem2", attributes=attrs_dict)
+ attrs = r.vset().vgetattr("myset", "elem2")
+ assert attrs == attrs_dict
+
+
+def test_vadd_same_vector_twice(r: redis.Redis):
+ float_array = [1, 4.32, 0.11]
+ resp = r.vset().vadd("myset", float_array, "elem1")
+ assert resp == 1
+ float_array = [1, 2, 3]
+ resp = r.vset().vadd("myset", float_array, "elem1")
+ assert resp == 0
+ resp = r.vset().vrandmember("myset")
+ assert resp == b"elem1"
+
+
+def test_add_elem_with_values(r: redis.Redis):
+ float_array = [1, 4.32, 0.11]
+ resp = r.vset().vadd("myset", float_array, "elem1")
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+ with pytest.raises(redis.DataError):
+ r.vset().vadd("myset_invalid_data", None, "elem1")
+
+ with pytest.raises(redis.DataError):
+ r.vset().vadd("myset_invalid_data", [12, 45], None, reduce_dim=3)
+
+
+def test_add_elem_with_vector(r: redis.Redis):
+ float_array = [1, 4.32, 0.11]
+ # Convert the list of floats to a byte array in fp32 format
+ byte_array = _to_fp32_blob_array(float_array)
+ resp = r.vset().vadd("myset", byte_array, "elem1")
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+
+def test_add_elem_reduced_dim(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9]
+ resp = r.vset().vadd("myset", float_array, "elem1", reduce_dim=3)
+ assert resp == 1
+
+ dim = r.vset().vdim("myset")
+ assert dim == 3
+
+
+def test_add_elem_cas(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9]
+ resp = r.vset().vadd("myset", vector=float_array, element="elem1", cas=True)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+
+def test_add_elem_no_quant(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9]
+ resp = r.vset().vadd("myset", vector=float_array, element="elem1", quantization=QuantizationOptions.NOQUANT)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization(float_array, emb, tolerance=0.0)
+
+
+def test_add_elem_bin_quant(r: redis.Redis):
+ float_array = [1, 4.32, 0.0, 0.05, -2.9]
+ resp = r.vset().vadd("myset", vector=float_array, element="elem1", quantization=QuantizationOptions.BIN)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization([1, 1, -1, 1, -1], emb, tolerance=0.0)
+
+
+def test_add_elem_q8_quant(r: redis.Redis):
+ float_array = [1, 4.32, 10.0, -21, -2.9]
+ resp = r.vset().vadd("myset", vector=float_array, element="elem1", quantization=QuantizationOptions.Q8)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem1")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+
+def test_add_elem_ef(r: redis.Redis):
+ r.vset().vadd("myset", vector=[5, 55, 65, -20, 30], element="elem1")
+ r.vset().vadd("myset", vector=[-40, -40.32, 10.0, -4, 2.9], element="elem2")
+
+ float_array = [1, 4.32, 10.0, -21, -2.9]
+ resp = r.vset().vadd("myset", float_array, "elem3", ef=1)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem3")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+ sim = r.vset().vsim("myset", input="elem3", with_scores=True)
+ assert len(sim) == 3
+
+
+def test_add_elem_with_attr(r: redis.Redis):
+ float_array = [1, 4.32, 10.0, -21, -2.9]
+ attrs_dict = {"key1": "value1", "key2": "value2"}
+ resp = r.vset().vadd("myset", vector=float_array, element="elem3", attributes=attrs_dict)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem3")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+ attr_saved = r.vset().vgetattr("myset", "elem3")
+ assert attr_saved == attrs_dict
+
+ resp = r.vset().vadd("myset", vector=float_array, element="elem4", attributes={})
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem4")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+ attr_saved = r.vset().vgetattr("myset", "elem4")
+ assert attr_saved is None
+
+ resp = r.vset().vadd("myset", vector=float_array, element="elem5", attributes=json.dumps(attrs_dict))
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem5")
+ assert _validate_quantization(float_array, emb, tolerance=0.1)
+
+ attr_saved = r.vset().vgetattr("myset", "elem5")
+ assert attr_saved == attrs_dict
+
+
+def test_add_elem_with_numlinks(r: redis.Redis):
+ elements_count = 100
+ vector_dim = 10
+ for i in range(elements_count):
+ float_array = [random.randint(0, 10) for x in range(vector_dim)]
+ r.vset().vadd("myset", float_array, f"elem{i}", numlinks=8)
+
+ float_array = [1, 4.32, 0.11, 0.5, 0.9, 0.1, 0.2, 0.3, 0.4, 0.5]
+ resp = r.vset().vadd("myset", float_array, "elem_numlinks", numlinks=8)
+ assert resp == 1
+
+ emb = r.vset().vemb("myset", "elem_numlinks")
+ assert _validate_quantization(float_array, emb, tolerance=0.5)
+
+ numlinks_all_layers = r.vset().vlinks("myset", "elem_numlinks")
+ for neighbours_list_for_layer in numlinks_all_layers:
+ assert len(neighbours_list_for_layer) <= 8
+
+
+def test_vsim_count(r: redis.Redis):
+ elements_count = 30
+ vector_dim = 800
+ for i in range(elements_count):
+ float_array = [random.uniform(0, 10) for x in range(vector_dim)]
+ r.vset().vadd("myset", float_array, f"elem{i}", numlinks=64)
+
+ vsim = r.vset().vsim("myset", input="elem1")
+ assert len(vsim) == 10
+ assert isinstance(vsim, list)
+ assert isinstance(vsim[0], bytes)
+
+ vsim = r.vset().vsim("myset", input="elem1", count=5)
+ assert len(vsim) == 5
+ assert isinstance(vsim, list)
+ assert isinstance(vsim[0], bytes)
+
+ vsim = r.vset().vsim("myset", input="elem1", count=50)
+ assert len(vsim) == 30
+ assert isinstance(vsim, list)
+ assert isinstance(vsim[0], bytes)
+
+ vsim = r.vset().vsim("myset", input="elem1", count=15)
+ assert len(vsim) == 15
+ assert isinstance(vsim, list)
+ assert isinstance(vsim[0], bytes)
+
+
+def test_vsim_with_scores(r: redis.Redis):
+ elements_count = 20
+ vector_dim = 50
+ for i in range(elements_count):
+ float_array = [random.uniform(0, 10) for x in range(vector_dim)]
+ r.vset().vadd("myset", float_array, f"elem{i}", numlinks=64)
+
+ vsim = r.vset().vsim("myset", input="elem1", with_scores=True)
+ assert len(vsim) == 10
+ assert isinstance(vsim, dict)
+ assert isinstance(vsim[b"elem1"], float)
+ assert vsim[b"elem1"] >= 0.99
+
+
+def test_vsim_with_different_vector_input_types(r: redis.Redis):
+ elements_count = 10
+ vector_dim = 5
+ for i in range(elements_count):
+ float_array = [random.uniform(0, 10) for x in range(vector_dim)]
+ attributes = {"index": i, "elem_name": f"elem_{i}"}
+ r.vset().vadd("myset", float_array, f"elem_{i}", numlinks=4, attributes=attributes)
+ sim = r.vset().vsim("myset", input="elem_1")
+ assert len(sim) == 10
+ assert isinstance(sim, list)
+
+ float_array = [1, 4.32, 0.0, 0.05, -2.9]
+ sim_to_float_array = r.vset().vsim("myset", input=float_array)
+ assert len(sim_to_float_array) == 10
+ assert isinstance(sim_to_float_array, list)
+
+ fp32_vector = _to_fp32_blob_array(float_array)
+ sim_to_fp32_vector = r.vset().vsim("myset", input=fp32_vector)
+ assert len(sim_to_fp32_vector) == 10
+ assert isinstance(sim_to_fp32_vector, list)
+ assert sim_to_float_array == sim_to_fp32_vector
+
+ with pytest.raises(redis.DataError):
+ r.vset().vsim("myset", input=None)
+
+
+def test_vsim_unexisting(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9]
+ r.vset().vadd("myset", vector=float_array, element="elem1", cas=True)
+
+ with pytest.raises(redis.ResponseError) as exc_info:
+ r.vset().vsim("myset", input="elem_not_existing")
+
+ assert str(exc_info.value) == "element not found in set"
+ sim = r.vset().vsim("myset_not_existing", input="elem1")
+ assert sim == []
+
+
+def test_vsim_with_filter(r: redis.Redis):
+ elements_count = 50
+ vector_dim = 800
+ for i in range(elements_count):
+ float_array = [random.uniform(10, 20) for x in range(vector_dim)]
+ attributes = {"index": i, "elem_name": f"elem_{i}"}
+ r.vset().vadd("myset", float_array, f"elem_{i}", numlinks=4, attributes=attributes)
+ float_array = [-random.uniform(10, 20) for x in range(vector_dim)]
+ attributes = {"index": elements_count, "elem_name": "elem_special"}
+ r.vset().vadd("myset", float_array, "elem_special", numlinks=4, attributes=attributes)
+ sim = r.vset().vsim("myset", input="elem_1", filter=".index > 10")
+ assert len(sim) == 10
+ assert isinstance(sim, list)
+ for elem in sim:
+ assert int(elem.split(b"_")[1]) > 10
+
+ sim = r.vset().vsim(
+ "myset", input="elem_1", filter=".index > 10 and .index < 15 and .elem_name in ['elem_12', 'elem_17']"
+ )
+ assert len(sim) == 1
+ assert isinstance(sim, list)
+ assert sim[0] == b"elem_12"
+
+ sim = r.vset().vsim(
+ "myset", input="elem_1", filter=".index > 25 and .elem_name in ['elem_12', 'elem_17', 'elem_19']", ef=100
+ )
+ assert len(sim) == 0
+ assert isinstance(sim, list)
+
+ # todo filter_ef
+
+ # sim = r.vset().vsim(
+ # "myset",
+ # input="elem_1",
+ # filter=".index > 28 and .elem_name in ['elem_12', 'elem_17', 'elem_special']",
+ # filter_ef=1,
+ # )
+ # assert len(sim) == 0, f"Expected 0 results, but got {len(sim)} with filter_ef=1, sim: {sim}"
+ # assert isinstance(sim, list)
+
+ sim = r.vset().vsim(
+ "myset",
+ input="elem_1",
+ filter=".index > 28 and .elem_name in ['elem_12', 'elem_17', 'elem_special']",
+ filter_ef=500,
+ )
+ assert len(sim) == 1
+ assert isinstance(sim, list)
+
+
+#
+# def test_vsim_truth_no_thread_enabled(r: redis.Redis):
+# elements_count = 5000
+# vector_dim = 50
+# for i in range(1, elements_count + 1):
+# float_array = [random.uniform(10 * i, 1000 * i) for x in range(vector_dim)]
+# r.vset().vadd("myset", float_array, f"elem_{i}")
+#
+# r.vset().vadd("myset", [-22 for _ in range(vector_dim)], "elem_man_2")
+#
+# sim_without_truth = r.vset().vsim("myset", input="elem_man_2", with_scores=True)
+# sim_truth = r.vset().vsim("myset", input="elem_man_2", with_scores=True, truth=True)
+#
+# assert len(sim_without_truth) == 10
+# assert len(sim_truth) == 10
+#
+# assert isinstance(sim_without_truth, dict)
+# assert isinstance(sim_truth, dict)
+#
+# results_scores = list(zip([v for _, v in sim_truth.items()], [v for _, v in sim_without_truth.items()]))
+#
+# found_better_match = False
+# for score_with_truth, score_without_truth in results_scores:
+# if score_with_truth < score_without_truth:
+# assert False, "Score with truth [{score_with_truth}] < score without truth [{score_without_truth}]"
+# elif score_with_truth > score_without_truth:
+# found_better_match = True
+#
+# assert found_better_match
+#
+# sim_no_thread = r.vset().vsim("myset", input="elem_man_2", with_scores=True, no_thread=True)
+#
+# assert len(sim_no_thread) == 10
+# assert isinstance(sim_no_thread, dict)
+
+
+def test_vdim(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9, 0.1, 0.2]
+ r.vset().vadd("myset", float_array, "elem1")
+
+ dim = r.vset().vdim("myset")
+ assert dim == len(float_array)
+
+ r.vset().vadd("myset_reduced", float_array, "elem1", reduce_dim=4)
+ reduced_dim = r.vset().vdim("myset_reduced")
+ assert reduced_dim == 4
+
+ with pytest.raises(redis.ResponseError):
+ r.vset().vdim("myset_unexisting")
+
+
+def test_vdim_errors(r: redis.Redis):
+ float_array = [1, 4.32, 0.11, 0.5, 0.9, 0.1, 0.2]
+ r.vset().vadd("myset", float_array, "elem1")
+
+ dim = r.vset().vdim("myset")
+ assert dim == len(float_array)
+ with pytest.raises(redis.ResponseError) as ctx:
+ r.vset().vadd("myset", float_array, "elem2", reduce_dim=4)
+ assert str(ctx.value) == "cannot add projection to existing set without projection"
+
+ float_array = [1, 4.32, 0.11, 0.5, 0.9, 0.1, 0.2]
+ with pytest.raises(redis.ResponseError) as ctx:
+ r.vset().vadd("myset1", float_array, "elem1", reduce_dim=-4)
+ assert str(ctx.value) == "invalid vector specification"
+
+
+def test_vcard(r: redis.Redis):
+ n = 20
+ for i in range(n):
+ float_array = [random.uniform(0, 10) for x in range(1, 8)]
+ r.vset().vadd("myset", float_array, f"elem{i}")
+
+ card = r.vset().vcard("myset")
+ assert card == n
+
+ with pytest.raises(redis.ResponseError):
+ r.vset().vdim("myset_unexisting")
+
+
+def test_vrem(r: redis.Redis):
+ n = 3
+ for i in range(n):
+ float_array = [random.uniform(0, 10) for x in range(1, 8)]
+ vadd_res = r.vset().vadd("myset", float_array, f"elem{i}")
+ assert vadd_res == 1
+
+ resp = r.vset().vrem("myset", "elem2")
+ assert resp == 1
+
+ card = r.vset().vcard("myset")
+ assert card == n - 1
+
+ resp = r.vset().vrem("myset", "elem2")
+ assert resp == 0
+
+ card = r.vset().vcard("myset")
+ assert card == n - 1
+
+ resp = r.vset().vrem("myset_unexisting", "elem1")
+ assert resp == 0
+
+
+def test_vemb_bin_quantization(r: redis.Redis):
+ e = [1, 4.32, 0.0, 0.05, -2.9]
+ r.vset().vadd("myset", e, "elem", quantization=QuantizationOptions.BIN)
+ emb_no_quant = r.vset().vemb("myset", "elem")
+ assert emb_no_quant == [1, 1, -1, 1, -1]
+
+ emb_no_quant_raw = r.vset().vemb("myset", "elem", raw=True)
+ assert emb_no_quant_raw["quantization"] == b"bin"
+ assert isinstance(emb_no_quant_raw["raw"], bytes)
+ assert isinstance(emb_no_quant_raw["l2"], float)
+ assert emb_no_quant_raw["l2"] == pytest.approx(5.29, rel=0.01)
+ assert "range" not in emb_no_quant_raw
+
+
+def test_vemb_q8_quantization(r: redis.Redis):
+ e = [1, 10.32, 0.0, 2.05, -12.5]
+ r.vset().vadd("myset", e, "elem", quantization=QuantizationOptions.Q8)
+
+ emb_q8_quant = r.vset().vemb("myset", "elem")
+ assert _validate_quantization(e, emb_q8_quant, tolerance=0.1)
+
+ emb_q8_quant_raw = r.vset().vemb("myset", "elem", raw=True)
+ assert emb_q8_quant_raw["quantization"] == b"int8"
+ assert isinstance(emb_q8_quant_raw["raw"], bytes)
+ assert isinstance(emb_q8_quant_raw["l2"], float)
+ assert emb_q8_quant_raw["l2"] == pytest.approx(16.369, rel=0.01)
+ assert isinstance(emb_q8_quant_raw["range"], float)
+ assert emb_q8_quant_raw["range"] == pytest.approx(0.76, rel=0.01)
+
+
+def test_vemb_no_quantization(r: redis.Redis):
+ e = [1, 10.32, 0.0, 2.05, -12.5]
+ r.vset().vadd("myset", e, "elem", quantization=QuantizationOptions.NOQUANT)
+
+ emb_no_quant = r.vset().vemb("myset", "elem")
+ assert _validate_quantization(e, emb_no_quant, tolerance=0.1)
+
+ emb_no_quant_raw = r.vset().vemb("myset", "elem", raw=True)
+ assert emb_no_quant_raw["quantization"] == b"f32"
+ assert isinstance(emb_no_quant_raw["raw"], bytes)
+ assert isinstance(emb_no_quant_raw["l2"], float)
+ assert "range" not in emb_no_quant_raw
+
+
+def test_vemb_default_quantization(r: redis.Redis):
+ e = [1, 5.32, 0.0, 0.25, -5]
+ r.vset().vadd("myset", vector=e, element="elem")
+
+ emb_default_quant = r.vset().vemb("myset", "elem")
+ assert _validate_quantization(e, emb_default_quant, tolerance=0.1)
+
+ emb_default_quant_raw = r.vset().vemb("myset", "elem", raw=True)
+ assert emb_default_quant_raw["quantization"] == b"int8"
+ assert isinstance(emb_default_quant_raw["raw"], bytes)
+ assert isinstance(emb_default_quant_raw["l2"], float)
+ assert isinstance(emb_default_quant_raw["range"], float)
+
+
+def test_vemb_fp32_quantization(r: redis.Redis):
+ float_array_fp32 = [1, 4.32, 0.11]
+ # Convert the list of floats to a byte array in fp32 format
+ byte_array = _to_fp32_blob_array(float_array_fp32)
+ r.vset().vadd("myset", byte_array, "elem")
+
+ emb_fp32_quant = r.vset().vemb("myset", "elem")
+ assert _validate_quantization(float_array_fp32, emb_fp32_quant, tolerance=0.1)
+
+ emb_fp32_quant_raw = r.vset().vemb("myset", "elem", raw=True)
+ assert emb_fp32_quant_raw["quantization"] == b"int8"
+ assert isinstance(emb_fp32_quant_raw["raw"], bytes)
+ assert isinstance(emb_fp32_quant_raw["l2"], float)
+ assert isinstance(emb_fp32_quant_raw["range"], float)
+
+
+def test_vemb_unexisting(r: redis.Redis):
+ emb_not_existing = r.vset().vemb("not_existing", "elem")
+ assert emb_not_existing is None
+
+ e = [1, 5.32, 0.0, 0.25, -5]
+ r.vset().vadd("myset", vector=e, element="elem")
+ emb_elem_not_existing = r.vset().vemb("myset", "not_existing")
+ assert emb_elem_not_existing is None
+
+
+def test_vlinks(r: redis.Redis):
+ elements_count = 100
+ vector_dim = 800
+ for i in range(elements_count):
+ float_array = [random.uniform(0, 10) for x in range(vector_dim)]
+ r.vset().vadd("myset", float_array, f"elem{i}", numlinks=8)
+
+ element_links_all_layers = r.vset().vlinks("myset", "elem1")
+ assert len(element_links_all_layers) >= 1
+ for neighbours_list_for_layer in element_links_all_layers:
+ assert isinstance(neighbours_list_for_layer, list)
+ for neighbour in neighbours_list_for_layer:
+ assert isinstance(neighbour, bytes)
+
+ elem_links_all_layers_with_scores = r.vset().vlinks("myset", "elem1", with_scores=True)
+ assert len(elem_links_all_layers_with_scores) >= 1
+ for neighbours_dict_for_layer in elem_links_all_layers_with_scores:
+ assert isinstance(neighbours_dict_for_layer, dict)
+ for neighbour_key, score_value in neighbours_dict_for_layer.items():
+ assert isinstance(neighbour_key, bytes)
+ assert isinstance(score_value, float)
+
+ float_array = [0.75, 0.25, 0.5, 0.1, 0.9]
+ r.vset().vadd("myset_one_elem_only", float_array, "elem1")
+ elem_no_neighbours_with_scores = r.vset().vlinks("myset_one_elem_only", "elem1", with_scores=True)
+ assert len(elem_no_neighbours_with_scores) >= 1
+ for neighbours_dict_for_layer in elem_no_neighbours_with_scores:
+ assert isinstance(neighbours_dict_for_layer, dict)
+ assert len(neighbours_dict_for_layer) == 0
+
+ elem_no_neighbours_no_scores = r.vset().vlinks("myset_one_elem_only", "elem1")
+ assert len(elem_no_neighbours_no_scores) >= 1
+ for neighbours_list_for_layer in elem_no_neighbours_no_scores:
+ assert isinstance(neighbours_list_for_layer, list)
+ assert len(neighbours_list_for_layer) == 0
+
+ unexisting_element_links = r.vset().vlinks("myset", "unexisting_elem")
+ assert unexisting_element_links is None
+
+ unexisting_vset_links = r.vset().vlinks("myset_unexisting", "elem1")
+ assert unexisting_vset_links is None
+
+ unexisting_element_links = r.vset().vlinks("myset", "unexisting_elem", with_scores=True)
+ assert unexisting_element_links is None
+
+ unexisting_vset_links = r.vset().vlinks("myset_unexisting", "elem1", with_scores=True)
+ assert unexisting_vset_links is None
+
+
+def test_vinfo(r: redis.Redis):
+ elements_count = 100
+ vector_dim = 800
+ for i in range(elements_count):
+ float_array = [random.uniform(0, 10) for x in range(vector_dim)]
+ r.vset().vadd("myset", float_array, f"elem{i}", numlinks=8, quantization=QuantizationOptions.BIN)
+
+ vset_info = r.vset().vinfo("myset")
+ assert vset_info[b"quant-type"] == b"bin"
+ assert vset_info[b"vector-dim"] == vector_dim
+ assert vset_info[b"size"] == elements_count
+ assert vset_info[b"max-level"] > 0
+ assert vset_info[b"hnsw-max-node-uid"] == elements_count
+
+ unexisting_vset_info = r.vset().vinfo("myset_unexisting")
+ assert unexisting_vset_info is None
+
+
+def test_vset_vget_attributes(r: redis.Redis):
+ float_array = [1, 4.32, 0.11]
+ attributes = {"key1": "value1", "key2": "value2"}
+
+ # validate vgetattrs when no attributes are set with vadd
+ resp = r.vset().vadd("myset", float_array, "elem1")
+ assert resp == 1
+
+ attrs = r.vset().vgetattr("myset", "elem1")
+ assert attrs is None
+
+ # validate vgetattrs when attributes are set with vadd
+ resp = r.vset().vadd("myset_with_attrs", float_array, "elem1", attributes=attributes)
+ assert resp == 1
+
+ attrs = r.vset().vgetattr("myset_with_attrs", "elem1")
+ assert attrs == attributes
+
+ # Set attributes and get attributes
+ resp = r.vset().vsetattr("myset", "elem1", attributes)
+ assert resp == 1
+ attr_saved = r.vset().vgetattr("myset", "elem1")
+ assert attr_saved == attributes
+
+ # Set attributes to None
+ resp = r.vset().vsetattr("myset", "elem1", None)
+ assert resp == 1
+ attr_saved = r.vset().vgetattr("myset", "elem1")
+ assert attr_saved is None
+
+ # Set attributes to empty dict
+ resp = r.vset().vsetattr("myset", "elem1", {})
+ assert resp == 1
+ attr_saved = r.vset().vgetattr("myset", "elem1")
+ assert attr_saved is None
+
+ # Set attributes provided as string
+ resp = r.vset().vsetattr("myset", "elem1", json.dumps(attributes))
+ assert resp == 1
+ attr_saved = r.vset().vgetattr("myset", "elem1")
+ assert attr_saved == attributes
+
+ # Set attributes to unexisting element
+ resp = r.vset().vsetattr("myset", "elem2", attributes)
+ assert resp == 0
+ attr_saved = r.vset().vgetattr("myset", "elem2")
+ assert attr_saved is None
+
+ # Set attributes to unexisting vset
+ resp = r.vset().vsetattr("myset_unexisting", "elem1", attributes)
+ assert resp == 0
+ attr_saved = r.vset().vgetattr("myset_unexisting", "elem1")
+ assert attr_saved is None
+
+
+def test_vrandmember(r: redis.Redis):
+ elements = ["elem1", "elem2", "elem3"]
+ for elem in elements:
+ float_array = [random.uniform(0, 10) for x in range(1, 8)]
+ r.vset().vadd("myset", float_array, element=elem)
+
+ random_member = r.vset().vrandmember("myset")
+ assert random_member.decode() in elements
+
+ members_list: List[bytes] = r.vset().vrandmember("myset", count=2)
+ assert len(members_list) == 2
+ assert all(member.decode() in elements for member in members_list)
+
+ # Test with count greater than the number of elements
+ members_list = r.vset().vrandmember("myset", count=10)
+ assert len(members_list) == len(elements)
+ assert all(member.decode() in elements for member in members_list)
+
+ # Test with negative count
+ members_list = r.vset().vrandmember("myset", count=-2)
+ assert len(members_list) == 2
+ assert all(member.decode() in elements for member in members_list)
+
+ # Test with count equal to the number of elements
+ members_list = r.vset().vrandmember("myset", count=len(elements))
+ assert len(members_list) == len(elements)
+ assert all(member.decode() in elements for member in members_list)
+
+ # Test with count equal to 0
+ members_list = r.vset().vrandmember("myset", count=0)
+ assert members_list == []
+
+ # Test with count equal to 1
+ members_list = r.vset().vrandmember("myset", count=1)
+ assert len(members_list) == 1
+ assert members_list[0].decode() in elements
+
+ # Test with count equal to -1
+ members_list = r.vset().vrandmember("myset", count=-1)
+ assert len(members_list) == 1
+ assert members_list[0].decode() in elements
+
+ # Test with unexisting vset & without count
+ members_list = r.vset().vrandmember("myset_unexisting")
+ assert members_list is None
+
+ # Test with unexisting vset & count
+ members_list = r.vset().vrandmember("myset_unexisting", count=5)
+ assert members_list == []
+
+
+def test_vrandmember_wrong_type(r: redis.Redis):
+ # Test with non vset value
+ r.set("not_a_vset", "some_value")
+ with pytest.raises(redis.ResponseError) as excinfo:
+ r.vset().vrandmember("not_a_vset")
+ assert excinfo.value.args[0] == "WRONGTYPE Operation against a key holding the wrong kind of value"
+
+
+def test_randmember_bad_count_type(r: redis.Redis):
+ # Test with bad count type
+ elements = ["elem1", "elem2", "elem3"]
+ for elem in elements:
+ float_array = [random.uniform(0, 10) for x in range(1, 8)]
+ r.vset().vadd("myset", float_array, element=elem)
+ with pytest.raises(redis.ResponseError) as excinfo:
+ r.vset().vrandmember("myset", count="not_an_integer")
+ assert excinfo.value.args[0] == "COUNT value is not an integer"
+
+
+@pytest.mark.fake
+def test_vset_commands_without_decoding_responses(r: redis.Redis):
+ # test vadd
+ elements = ["elem1", "elem2", "elem3"]
+ for elem in elements:
+ float_array = [random.uniform(0.5, 10) for x in range(0, 8)]
+ resp = r.vset().vadd("myset", float_array, element=elem)
+ assert resp == 1
+
+ # test vemb
+ emb = r.vset().vemb("myset", "elem1")
+ assert len(emb) == 8
+ assert isinstance(emb, list)
+ assert all(isinstance(x, float) for x in emb), f"Expected float values, got {emb}"
+
+ emb_raw = r.vset().vemb("myset", "elem1", raw=True)
+ assert emb_raw["quantization"] == b"int8"
+ assert isinstance(emb_raw["raw"], bytes)
+ assert isinstance(emb_raw["l2"], float)
+ assert isinstance(emb_raw["range"], float)
+
+ # test vsim
+ vsim = r.vset().vsim("myset", input="elem1")
+ assert len(vsim) == 3
+ assert isinstance(vsim, list)
+ assert isinstance(vsim[0], bytes)
+
+ # test vsim with scores
+ vsim_with_scores = r.vset().vsim("myset", input="elem1", with_scores=True)
+ assert len(vsim_with_scores) == 3
+ assert isinstance(vsim_with_scores, dict)
+ assert isinstance(vsim_with_scores[b"elem1"], float)
+
+ # test vsim with scores with attributes
+ vsim_with_scores = r.vset().vsim("myset", input="elem1", with_scores=True, with_attribs=True)
+ assert len(vsim_with_scores) == 3
+ assert isinstance(vsim_with_scores, dict)
+ assert len(vsim_with_scores) == 3
+ assert isinstance(vsim_with_scores, dict)
+ assert isinstance(vsim_with_scores[b"elem1"], dict)
+ assert vsim_with_scores[b"elem1"]["attributes"] is None
+ assert isinstance(vsim_with_scores[b"elem1"]["score"], float)
+
+ # test vsim with attributes
+ vsim_with_scores = r.vset().vsim("myset", input="elem1", with_attribs=True)
+ assert len(vsim_with_scores) == 3
+ assert isinstance(vsim_with_scores, dict)
+ assert len(vsim_with_scores) == 3
+ assert isinstance(vsim_with_scores, dict)
+ assert vsim_with_scores[b"elem1"] is None
+
+ # test vlinks - no scores
+ element_links_all_layers = r.vset().vlinks("myset", "elem1")
+ assert len(element_links_all_layers) >= 1
+ for neighbours_list_for_layer in element_links_all_layers:
+ assert isinstance(neighbours_list_for_layer, list)
+ for neighbour in neighbours_list_for_layer:
+ assert isinstance(neighbour, bytes)
+ # test vlinks with scores
+ elem_links_all_layers_with_scores = r.vset().vlinks("myset", "elem1", with_scores=True)
+ assert len(elem_links_all_layers_with_scores) >= 1
+ for neighbours_dict_for_layer in elem_links_all_layers_with_scores:
+ assert isinstance(neighbours_dict_for_layer, dict)
+ for neighbour_key, score_value in neighbours_dict_for_layer.items():
+ assert isinstance(neighbour_key, bytes)
+ assert isinstance(score_value, float)
+
+ # test vinfo
+ vset_info = r.vset().vinfo("myset")
+ assert vset_info[b"quant-type"] == b"int8"
+ assert vset_info[b"vector-dim"] == 8
+ assert vset_info[b"size"] == len(elements)
+ assert vset_info[b"max-level"] >= 0
+ assert vset_info[b"hnsw-max-node-uid"] == len(elements)
+
+ # test vgetattr
+ attributes = {"key1": "value1", "key2": "value2"}
+ r.vset().vsetattr("myset", "elem1", attributes)
+ attrs = r.vset().vgetattr("myset", "elem1")
+ assert attrs == attributes
+
+ # test vrandmember
+ random_member = r.vset().vrandmember("myset")
+ assert isinstance(random_member, bytes)
+ assert random_member.decode("utf-8") in elements
+
+ members_list = r.vset().vrandmember("myset", count=2)
+ assert len(members_list) == 2
+ assert all(member.decode("utf-8") in elements for member in members_list)
+
+
+def _to_fp32_blob_array(float_array):
+ """
+ Convert a list of floats to a byte array in fp32 format.
+ """
+ # Convert the list of floats to a NumPy array with dtype np.float32
+ arr = np.array(float_array, dtype=np.float32)
+ # Convert the NumPy array to a byte array
+ byte_array = arr.tobytes()
+ return byte_array
+
+
+def _validate_quantization(original, quantized, tolerance=0.1):
+ original = np.array(original, dtype=np.float32)
+ quantized = np.array(quantized, dtype=np.float32)
+
+ max_diff = np.max(np.abs(original - quantized))
+ return max_diff <= tolerance
+
+
+@testtools.run_test_if_redispy_ver("gte", "7.2")
+@pytest.mark.min_server("8.4")
+def test_vrange_basic(r: redis.Redis):
+ """Test basic VRANGE functionality with lexicographical ordering."""
+ # Add elements with different names
+ elements = [b"apple", b"banana", b"cherry", b"date", b"elderberry"]
+ for elem in elements:
+ r.vset().vadd(b"myset", [1.0, 2.0, 3.0], elem)
+
+ # Test full range
+ result = r.vset().vrange(b"myset", "-", "+")
+ assert result == elements
+ assert len(result) == 5
+
+ # Test inclusive range
+ result = r.vset().vrange(b"myset", "[banana", "[date")
+ assert result == [b"banana", b"cherry", b"date"]
+
+ # Test exclusive range
+ result = r.vset().vrange("myset", "(banana", "(date")
+ assert result == [b"cherry"]
+
+
+@testtools.run_test_if_redispy_ver("gte", "7.2")
+@pytest.mark.min_server("8.4")
+def test_vrange_error(r: redis.Redis):
+ r.set("not_a_vset", "some_value")
+ with pytest.raises(redis.ResponseError) as excinfo:
+ r.vset().vrange("not_a_vset", "-", "+")
+ assert excinfo.value.args[0] == "WRONGTYPE Operation against a key holding the wrong kind of value"
+
+ res = r.vset().vrange("x", "-", "+")
+ assert res == []
+
+
+@testtools.run_test_if_redispy_ver("gte", "7.2")
+@pytest.mark.min_server("8.4")
+def test_vrange_with_count(r: redis.Redis):
+ """Test VRANGE with count parameter."""
+ # Add elements
+ elements = [b"a", b"b", b"c", b"d", b"e", b"f", b"g"]
+ for elem in elements:
+ r.vset().vadd("myset", [1.0, 2.0], elem)
+
+ # Test with positive count
+ result = r.vset().vrange("myset", "-", "+", count=3)
+ assert len(result) == 3
+ assert result == [b"a", b"b", b"c"]
+
+ # Test with count larger than set size
+ result = r.vset().vrange("myset", "-", "+", count=100)
+ assert len(result) == 7
+ assert result == elements
+
+ # Test with count = 0
+ result = r.vset().vrange("myset", "-", "+", count=0)
+ assert result == []
+
+ # Test with negative count (should return all)
+ result = r.vset().vrange("myset", "-", "+", count=-1)
+ assert len(result) == 7
+ assert result == elements
diff --git a/tox.ini b/tox.ini
index 8a6b0ea2..30c33b82 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,7 +12,7 @@ usedevelop = True
passenv = DOCKER_HOST
commands =
uv sync --all-extras
- podman run -d -p 6390:6379 --name redis8fakeredis redis:8.4.0
+ podman run -d -p 6390:6379 --name redis8fakeredis redis:8.6.2
uv run pytest -v
podman stop redis8fakeredis
podman rm redis8fakeredis
diff --git a/uv.lock b/uv.lock
index 03356d4f..bd706a88 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,8 @@ version = 1
revision = 3
requires-python = ">=3.7"
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
"python_full_version == '3.8.*'",
"python_full_version < '3.8'",
@@ -28,7 +29,8 @@ name = "async-timeout"
version = "5.0.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
"python_full_version == '3.8.*'",
]
@@ -436,7 +438,8 @@ name = "coverage"
version = "7.13.5"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
wheels = [
@@ -626,7 +629,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -677,11 +680,16 @@ valkey = [
{ name = "valkey", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" },
{ name = "valkey", version = "6.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
+vectorset = [
+ { name = "jsonpath-ng", marker = "python_full_version >= '3.11'" },
+ { name = "numpy", marker = "python_full_version >= '3.11'" },
+]
[package.dev-dependencies]
dev = [
{ name = "mypy", marker = "python_full_version >= '3.10'" },
{ name = "pre-commit", marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml", marker = "python_full_version >= '3.10'" },
{ name = "ruff" },
]
docs = [
@@ -705,8 +713,10 @@ test = [
[package.metadata]
requires-dist = [
+ { name = "jsonpath-ng", marker = "python_full_version >= '3.11' and extra == 'vectorset'", specifier = ">=1.6" },
{ name = "jsonpath-ng", marker = "extra == 'json'", specifier = ">=1.6" },
{ name = "lupa", marker = "extra == 'lua'", specifier = ">=2.1" },
+ { name = "numpy", marker = "python_full_version >= '3.11' and extra == 'vectorset'", specifier = ">=2.4.0" },
{ name = "pyprobables", marker = "extra == 'bf'", specifier = ">=0.6" },
{ name = "pyprobables", marker = "extra == 'cf'", specifier = ">=0.6" },
{ name = "pyprobables", marker = "extra == 'probabilistic'", specifier = ">=0.6" },
@@ -717,12 +727,13 @@ requires-dist = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'", specifier = ">=4.7" },
{ name = "valkey", marker = "python_full_version >= '3.8' and extra == 'valkey'", specifier = ">=6" },
]
-provides-extras = ["lua", "json", "bf", "cf", "probabilistic", "valkey"]
+provides-extras = ["lua", "json", "bf", "cf", "probabilistic", "valkey", "vectorset"]
[package.metadata.requires-dev]
dev = [
{ name = "mypy", marker = "python_full_version >= '3.10'", specifier = ">=1.15" },
{ name = "pre-commit", marker = "python_full_version >= '3.10'", specifier = ">=4.2" },
+ { name = "pyyaml", marker = "python_full_version >= '3.10'", specifier = ">=6" },
{ name = "ruff", specifier = ">=0.15" },
]
docs = [
@@ -771,7 +782,8 @@ name = "hypothesis"
version = "6.151.10"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
@@ -830,7 +842,8 @@ name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
@@ -1222,6 +1235,85 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
]
+[[package]]
+name = "numpy"
+version = "2.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" },
+ { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" },
+ { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" },
+ { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" },
+ { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" },
+ { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" },
+ { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" },
+ { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" },
+ { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" },
+ { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" },
+ { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" },
+ { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" },
+ { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" },
+ { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" },
+ { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" },
+ { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" },
+ { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" },
+ { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" },
+ { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" },
+ { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" },
+ { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" },
+ { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" },
+ { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" },
+]
+
[[package]]
name = "packaging"
version = "26.0"
@@ -1401,7 +1493,8 @@ name = "pyprobables"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/23/21/842f9c9039de95f00fc007e9d3215da21f5c4884956c9d7cc71d9ebe0014/pyprobables-0.7.0.tar.gz", hash = "sha256:77fef358cb70bffc2756639c30d182e7713041b99f788dfb2c5a0c5b146631ff", size = 37403, upload-time = "2026-02-08T21:01:42.536Z" }
wheels = [
@@ -1434,7 +1527,8 @@ name = "pytest"
version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
@@ -1472,7 +1566,8 @@ name = "pytest-asyncio"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
dependencies = [
{ name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*'" },
@@ -1708,7 +1803,8 @@ name = "redis"
version = "7.4.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
]
dependencies = [
{ name = "async-timeout", version = "5.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.11.3'" },
@@ -1850,7 +1946,8 @@ name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
@@ -1887,7 +1984,8 @@ name = "valkey"
version = "6.1.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.10'",
+ "python_full_version >= '3.11'",
+ "python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
dependencies = [