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 = [