diff --git a/docs/about/changelog.md b/docs/about/changelog.md index fd89af3a..44c4aa38 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -9,7 +9,8 @@ description: Change log of all fakeredis releases ### 🚀 Features -- Implement BITFIELD command #247 +- Implement `BITFIELD` command #247 +- Implement `COMMAND`, `COMMAND INFO`, `COMMAND COUNT` #248 ## v2.19.0 diff --git a/docs/redis-commands/Redis.md b/docs/redis-commands/Redis.md index 9c213f15..c25fb72c 100644 --- a/docs/redis-commands/Redis.md +++ b/docs/redis-commands/Redis.md @@ -1,11 +1,23 @@ # Redis commands -## `server` commands (8/70 implemented) +## `server` commands (11/70 implemented) ### [BGSAVE](https://redis.io/commands/bgsave/) Asynchronously saves the database(s) to disk. +### [COMMAND](https://redis.io/commands/command/) + +Returns detailed information about all commands. + +### [COMMAND COUNT](https://redis.io/commands/command-count/) + +Returns a count of commands. + +### [COMMAND INFO](https://redis.io/commands/command-info/) + +Returns information about one, multiple or all commands. + ### [DBSIZE](https://redis.io/commands/dbsize/) Returns the number of keys in the database. @@ -94,14 +106,6 @@ Returns the authenticated username of the current connection. Asynchronously rewrites the append-only file to disk. -#### [COMMAND](https://redis.io/commands/command/) (not implemented) - -Returns detailed information about all commands. - -#### [COMMAND COUNT](https://redis.io/commands/command-count/) (not implemented) - -Returns a count of commands. - #### [COMMAND DOCS](https://redis.io/commands/command-docs/) (not implemented) Returns documentary information about one, multiple or all commands. @@ -114,10 +118,6 @@ Extracts the key names from an arbitrary command. Extracts the key names and access flags for an arbitrary command. -#### [COMMAND INFO](https://redis.io/commands/command-info/) (not implemented) - -Returns information about one, multiple or all commands. - #### [COMMAND LIST](https://redis.io/commands/command-list/) (not implemented) Returns a list of command names. @@ -625,7 +625,7 @@ Resets the connection. Counts the number of set bits (population counting) in a string. -#### [BITFIELD](https://redis.io/commands/bitfield/) +### [BITFIELD](https://redis.io/commands/bitfield/) Performs arbitrary bitfield integer operations on strings. @@ -647,7 +647,7 @@ Sets or clears the bit at offset of the string value. Creates the key if it does ### Unsupported bitmap commands -> To implement support for a command, see [here](../../guides/implement-command/) +> To implement support for a command, see [here](../../guides/implement-command/) #### [BITFIELD_RO](https://redis.io/commands/bitfield_ro/) (not implemented) diff --git a/docs/requirements.txt b/docs/requirements.txt index c417a30d..411fb777 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ mkdocs==1.5.3 -mkdocs-material==9.4.4 +mkdocs-material==9.4.6 diff --git a/fakeredis/commands.json b/fakeredis/commands.json new file mode 100644 index 00000000..6bab40d9 --- /dev/null +++ b/fakeredis/commands.json @@ -0,0 +1 @@ +{"append": ["append", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "bgsave": ["bgsave", -1, ["admin", "noscript", "no_async_loading"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "bitcount": ["bitcount", -2, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "bitfield": ["bitfield", -2, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], "bitop": ["bitop", -4, ["write", "denyoom"], 2, 3, 1, ["@write", "@bitmap", "@slow"], [], [], []], "bitpos": ["bitpos", -3, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "blmove": ["blmove", 6, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blmpop": ["blmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blpop": ["blpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "brpop": ["brpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "brpoplpush": ["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "bzmpop": ["bzmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@sortedset", "@slow", "@blocking"], [], [], []], "bzpopmax": ["bzpopmax", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "bzpopmin": ["bzpopmin", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "command": ["command", -1, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], "command count": ["command count", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], "command info": ["command info", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], "dbsize": ["dbsize", 1, ["readonly", "fast"], 0, 0, 0, ["@keyspace", "@read", "@fast"], [], [], []], "decr": ["decr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "decrby": ["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "del": ["del", -2, ["write"], 1, 1, 1, ["@keyspace", "@write", "@slow"], [], [], []], "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, ["@fast", "@connection"], [], [], []], "eval": ["eval", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []], "evalsha": ["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []], "exec": ["exec", 1, ["noscript", "loading", "stale", "skip_slowlog"], 0, 0, 0, ["@slow", "@transaction"], [], [], []], "exists": ["exists", -2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "expire": ["expire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "expireat": ["expireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "flushall": ["flushall", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "flushdb": ["flushdb", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "geoadd": ["geoadd", -5, ["write", "denyoom"], 1, 1, 1, ["@write", "@geo", "@slow"], [], [], []], "geodist": ["geodist", -4, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geohash": ["geohash", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geopos": ["geopos", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius": ["georadius", -6, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], []], "georadiusbymember": ["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], []], "georadiusbymember_ro": ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius_ro": ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geosearch": ["geosearch", -7, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geosearchstore": ["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@write", "@geo", "@slow"], [], [], []], "get": ["get", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "getbit": ["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []], "getdel": ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getex": ["getex", -2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getrange": ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "getset": ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "hdel": ["hdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hexists": ["hexists", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hget": ["hget", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hgetall": ["hgetall", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hincrby": ["hincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hincrbyfloat": ["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hkeys": ["hkeys", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hlen": ["hlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmget": ["hmget", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmset": ["hmset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hrandfield": ["hrandfield", -2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hscan": ["hscan", -3, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hset": ["hset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hsetnx": ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hstrlen": ["hstrlen", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hvals": ["hvals", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "incr": ["incr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "incrby": ["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "incrbyfloat": ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "keys": ["keys", 2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow", "@dangerous"], [], [], []], "lastsave": ["lastsave", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@admin", "@fast", "@dangerous"], [], [], []], "lcs": ["lcs", -3, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "lindex": ["lindex", 3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "linsert": ["linsert", 5, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "llen": ["llen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@list", "@fast"], [], [], []], "lmove": ["lmove", 5, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "lmpop": ["lmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lpop": ["lpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lpos": ["lpos", -3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lpush": ["lpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lpushx": ["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lrange": ["lrange", 4, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lrem": ["lrem", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lset": ["lset", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "ltrim": ["ltrim", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "mget": ["mget", -2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "move": ["move", 3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "mset": ["mset", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []], "msetnx": ["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []], "multi": ["multi", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "persist": ["persist", 2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pexpire": ["pexpire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pexpireat": ["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pfadd": ["pfadd", -2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hyperloglog", "@fast"], [], [], []], "pfcount": ["pfcount", -2, ["readonly"], 1, 1, 1, ["@read", "@hyperloglog", "@slow"], [], [], []], "pfmerge": ["pfmerge", -2, ["write", "denyoom"], 1, 2, 1, ["@write", "@hyperloglog", "@slow"], [], [], []], "ping": ["ping", -1, ["fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "psetex": ["psetex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "psubscribe": ["psubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pttl": ["pttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "publish": ["publish", 3, ["pubsub", "loading", "stale", "fast"], 0, 0, 0, ["@pubsub", "@fast"], [], [], []], "pubsub": ["pubsub", -2, [], 0, 0, 0, ["@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", "@write", "@slow"], [], [], []], "renamenx": ["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@keyspace", "@write", "@fast"], [], [], []], "restore": ["restore", -4, ["write", "denyoom"], 1, 1, 1, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "rpop": ["rpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "rpoplpush": ["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "rpush": ["rpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "rpushx": ["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "sadd": ["sadd", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "save": ["save", 1, ["admin", "noscript", "no_async_loading", "no_multi"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "scan": ["scan", -2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "scard": ["scard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "script": ["script", -2, [], 0, 0, 0, ["@slow"], [], [], []], "script exists": ["script exists", -3, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], "script flush": ["script flush", -2, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], "script help": ["script help", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], "script load": ["script load", 3, ["noscript", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], "sdiff": ["sdiff", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sdiffstore": ["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "select": ["select", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "set": ["set", -3, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "setbit": ["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], "setex": ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "setnx": ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "setrange": ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "sinter": ["sinter", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sintercard": ["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sinterstore": ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sismember": ["sismember", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smembers": ["smembers", 2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "smismember": ["smismember", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smove": ["smove", 4, ["write", "fast"], 1, 2, 1, ["@write", "@set", "@fast"], [], [], []], "sort": ["sort", -2, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@set", "@sortedset", "@list", "@slow", "@dangerous"], [], [], []], "spop": ["spop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "spublish": ["spublish", 3, ["pubsub", "loading", "stale", "fast"], 1, 1, 1, ["@pubsub", "@fast"], [], [], []], "srandmember": ["srandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "srem": ["srem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "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, ["@read", "@string", "@fast"], [], [], []], "subscribe": ["subscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "substr": ["substr", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "sunion": ["sunion", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sunionstore": ["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sunsubscribe": ["sunsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "swapdb": ["swapdb", 3, ["write", "fast"], 0, 0, 0, ["@keyspace", "@write", "@fast", "@dangerous"], [], [], []], "time": ["time", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@fast"], [], [], []], "ttl": ["ttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "type": ["type", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "unlink": ["unlink", -2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "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, ["@write", "@stream", "@fast"], [], [], []], "xadd": ["xadd", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xautoclaim": ["xautoclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xclaim": ["xclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xdel": ["xdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xgroup create": ["xgroup create", -5, ["write", "denyoom"], 2, 2, 1, ["@write", "@stream", "@slow"], [], [], []], "xgroup createconsumer": ["xgroup createconsumer", 5, ["write", "denyoom"], 2, 2, 1, ["@write", "@stream", "@slow"], [], [], []], "xgroup delconsumer": ["xgroup delconsumer", 5, ["write"], 2, 2, 1, ["@write", "@stream", "@slow"], [], [], []], "xgroup destroy": ["xgroup destroy", 4, ["write"], 2, 2, 1, ["@write", "@stream", "@slow"], [], [], []], "xgroup setid": ["xgroup setid", -5, ["write"], 2, 2, 1, ["@write", "@stream", "@slow"], [], [], []], "xinfo consumers": ["xinfo consumers", 4, ["readonly"], 2, 2, 1, ["@read", "@stream", "@slow"], [], [], []], "xinfo groups": ["xinfo groups", 3, ["readonly"], 2, 2, 1, ["@read", "@stream", "@slow"], [], [], []], "xinfo stream": ["xinfo stream", -3, ["readonly"], 2, 2, 1, ["@read", "@stream", "@slow"], [], [], []], "xlen": ["xlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@stream", "@fast"], [], [], []], "xpending": ["xpending", -3, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xrange": ["xrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xread": ["xread", -4, ["readonly", "blocking", "movablekeys"], 0, 0, 1, ["@read", "@stream", "@slow", "@blocking"], [], [], []], "xreadgroup": ["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@write", "@stream", "@slow", "@blocking"], [], [], []], "xrevrange": ["xrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xtrim": ["xtrim", -4, ["write"], 1, 1, 1, ["@write", "@stream", "@slow"], [], [], []], "zadd": ["zadd", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zcard": ["zcard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zcount": ["zcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zdiff": ["zdiff", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zdiffstore": ["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zincrby": ["zincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zinter": ["zinter", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zintercard": ["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zinterstore": ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zlexcount": ["zlexcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zmpop": ["zmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zmscore": ["zmscore", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zpopmax": ["zpopmax", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zpopmin": ["zpopmin", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zrandmember": ["zrandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrange": ["zrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangebylex": ["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangebyscore": ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangestore": ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrank": ["zrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zrem": ["zrem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zremrangebylex": ["zremrangebylex", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyrank": ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyscore": ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrevrange": ["zrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrangebylex": ["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrangebyscore": ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrank": ["zrevrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zscan": ["zscan", -3, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zscore": ["zscore", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zunion": ["zunion", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zunionstore": ["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "json.del": ["json.del", -1, [], 0, 0, 0, [], [], [], []], "json.forget": ["json.forget", -1, [], 0, 0, 0, [], [], [], []], "json.get": ["json.get", -1, [], 0, 0, 0, [], [], [], []], "json.toggle": ["json.toggle", -1, [], 0, 0, 0, [], [], [], []], "json.clear": ["json.clear", -1, [], 0, 0, 0, [], [], [], []], "json.set": ["json.set", -1, [], 0, 0, 0, [], [], [], []], "json.mset": ["json.mset", -1, [], 0, 0, 0, [], [], [], []], "json.merge": ["json.merge", -1, [], 0, 0, 0, [], [], [], []], "json.mget": ["json.mget", -1, [], 0, 0, 0, [], [], [], []], "json.numincrby": ["json.numincrby", -1, [], 0, 0, 0, [], [], [], []], "json.nummultby": ["json.nummultby", -1, [], 0, 0, 0, [], [], [], []], "json.strappend": ["json.strappend", -1, [], 0, 0, 0, [], [], [], []], "json.strlen": ["json.strlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrappend": ["json.arrappend", -1, [], 0, 0, 0, [], [], [], []], "json.arrindex": ["json.arrindex", -1, [], 0, 0, 0, [], [], [], []], "json.arrinsert": ["json.arrinsert", -1, [], 0, 0, 0, [], [], [], []], "json.arrlen": ["json.arrlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrpop": ["json.arrpop", -1, [], 0, 0, 0, [], [], [], []], "json.arrtrim": ["json.arrtrim", -1, [], 0, 0, 0, [], [], [], []], "json.objkeys": ["json.objkeys", -1, [], 0, 0, 0, [], [], [], []], "json.objlen": ["json.objlen", -1, [], 0, 0, 0, [], [], [], []], "json.type": ["json.type", -1, [], 0, 0, 0, [], [], [], []], "bf.reserve": ["bf.reserve", -1, [], 0, 0, 0, [], [], [], []], "bf.add": ["bf.add", -1, [], 0, 0, 0, [], [], [], []], "bf.madd": ["bf.madd", -1, [], 0, 0, 0, [], [], [], []], "bf.insert": ["bf.insert", -1, [], 0, 0, 0, [], [], [], []], "bf.exists": ["bf.exists", -1, [], 0, 0, 0, [], [], [], []], "bf.mexists": ["bf.mexists", -1, [], 0, 0, 0, [], [], [], []], "bf.scandump": ["bf.scandump", -1, [], 0, 0, 0, [], [], [], []], "bf.loadchunk": ["bf.loadchunk", -1, [], 0, 0, 0, [], [], [], []], "bf.info": ["bf.info", -1, [], 0, 0, 0, [], [], [], []], "bf.card": ["bf.card", -1, [], 0, 0, 0, [], [], [], []]} \ No newline at end of file diff --git a/fakeredis/commands_mixins/server_mixin.py b/fakeredis/commands_mixins/server_mixin.py index 8a49d655..e5ad715d 100644 --- a/fakeredis/commands_mixins/server_mixin.py +++ b/fakeredis/commands_mixins/server_mixin.py @@ -1,15 +1,43 @@ +import json +import os import time -from typing import Any +from typing import Any, List, Optional, Dict from fakeredis import _msgs as msgs -from fakeredis._commands import command, DbIndex +from fakeredis._commands import command, DbIndex, SUPPORTED_COMMANDS from fakeredis._helpers import OK, SimpleError, casematch, BGSAVE_STARTED, Database +_COMMAND_INFO: Dict[bytes, List[Any]] = None + + +def convert_obj(obj: Any) -> Any: + if isinstance(obj, str): + return obj.encode() + if isinstance(obj, list): + return [convert_obj(x) for x in obj] + if isinstance(obj, dict): + return {convert_obj(k): convert_obj(obj[k]) for k in obj} + return obj + + +def _load_command_info() -> None: + global _COMMAND_INFO + if _COMMAND_INFO is None: + with open(os.path.join(os.path.dirname(__file__), '..', 'commands.json')) as f: + _COMMAND_INFO = convert_obj(json.load(f)) + class ServerCommandsMixin: _server: Any _db: Database + @staticmethod + def _get_command_info(cmd: str) -> Optional[List[Any]]: + _load_command_info() + if cmd not in SUPPORTED_COMMANDS or cmd.encode() not in _COMMAND_INFO: + return None + return _COMMAND_INFO[cmd.encode()] + @command((), (bytes,), flags=msgs.FLAG_NO_SCRIPT) def bgsave(self, *args): if len(args) > 1 or (len(args) == 1 and not casematch(args[0], b"schedule")): @@ -60,3 +88,19 @@ def swapdb(self, index1, index2): db2 = self._server.dbs[index2] db1.swap(db2) return OK + + @command(name="COMMAND INFO", fixed=(), repeat=(bytes,)) + def command_info(self, *commands): + res = [self._get_command_info(cmd) for cmd in commands] + return res + + @command(name="COMMAND COUNT", fixed=(), repeat=()) + def command_count(self): + _load_command_info() + return len(_COMMAND_INFO) + + @command(name="COMMAND", fixed=(), repeat=()) + def command(self): + _load_command_info() + res = [self._get_command_info(cmd.decode()) for cmd in _COMMAND_INFO] + return res diff --git a/scripts/create_issues.py b/scripts/create_issues.py index 93443ebd..6c07003d 100644 --- a/scripts/create_issues.py +++ b/scripts/create_issues.py @@ -5,15 +5,17 @@ - Set environment variable `GITHUB_TOKEN` to a github token with permissions to create issues. - Another option is to create `.env` file with `GITHUB_TOKEN`. """ +import json import os +import requests from dotenv import load_dotenv from github import Github -from supported import download_redis_commands, implemented_commands - load_dotenv() # take environment variables from .env. +THIS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) + IGNORE_GROUPS = { 'suggestion', 'tdigest', 'scripting', 'cf', 'topk', 'hyperloglog', 'graph', 'timeseries', 'connection', @@ -48,8 +50,22 @@ def commands_groups( return implemented, unimplemented +def download_redis_commands() -> dict: + from supported2 import METADATA + cmds = {} + for filename, url in METADATA: + full_filename = os.path.join(THIS_DIR, filename) + if not os.path.exists(full_filename): + contents = requests.get(url).content + open(full_filename, 'wb').write(contents) + curr_cmds = json.load(open(full_filename)) + cmds = cmds | {k.lower(): v for k, v in curr_cmds.items()} + return cmds + + def get_unimplemented_and_implemented_commands() -> tuple[dict[str, list[str]], dict[str, list[str]]]: """Returns 2 dictionaries, one of unimplemented commands and another of implemented commands""" + from supported2 import implemented_commands commands = download_redis_commands() implemented_commands_set = implemented_commands() implemented_dict, unimplemented_dict = commands_groups(commands, implemented_commands_set) diff --git a/scripts/generate_command_info.py b/scripts/generate_command_info.py new file mode 100644 index 00000000..a2349caf --- /dev/null +++ b/scripts/generate_command_info.py @@ -0,0 +1,105 @@ +import json +import os +from collections import namedtuple +from typing import Any, List, Dict + +import requests + +from fakeredis._commands import SUPPORTED_COMMANDS + +THIS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) +CommandsMeta = namedtuple('CommandsMeta', ['local_filename', 'stack', 'title', 'url', ]) +METADATA = [ + CommandsMeta('.commands.json', 'Redis', 'Redis', + 'https://raw.githubusercontent.com/redis/redis-doc/master/commands.json', ), + CommandsMeta('.json.commands.json', 'RedisJson', 'JSON', + 'https://raw.githubusercontent.com/RedisJSON/RedisJSON/master/commands.json', ), + CommandsMeta('.graph.commands.json', 'RedisGraph', 'Graph', + 'https://raw.githubusercontent.com/RedisGraph/RedisGraph/master/commands.json', ), + CommandsMeta('.ts.commands.json', 'RedisTimeSeries', 'Time Series', + 'https://raw.githubusercontent.com/RedisTimeSeries/RedisTimeSeries/master/commands.json', ), + CommandsMeta('.ft.commands.json', 'RedisSearch', 'Search', + 'https://raw.githubusercontent.com/RediSearch/RediSearch/master/commands.json', ), + CommandsMeta('.bloom.commands.json', 'RedisBloom', 'Probabilistic', + 'https://raw.githubusercontent.com/RedisBloom/RedisBloom/master/commands.json', ), +] + + +def download_single_stack_commands(filename, url) -> dict: + full_filename = os.path.join(THIS_DIR, filename) + if not os.path.exists(full_filename): + contents = requests.get(url).content + open(full_filename, 'wb').write(contents) + curr_cmds = json.load(open(full_filename)) + cmds = {k.lower(): v for k, v in curr_cmds.items()} + return cmds + + +def implemented_commands() -> set: + res = set(SUPPORTED_COMMANDS.keys()) + if 'json.type' not in res: + raise ValueError('Make sure jsonpath_ng is installed to get accurate documentation') + return res + + +def dict_deep_get(d: Dict[Any, Any], *keys, default_value: Any = None) -> Any: + res = d + for key in keys: + if isinstance(res, list) and isinstance(key, int): + res = res[key] + else: + res = res.get(key, None) + if res is None: + return default_value + return default_value if res is None else res + + +def key_specs_array(cmd_info: Dict[str, Any]) -> List[Any]: + return [] + + +def get_command_info(cmd_name: str, cmd_info: Dict[str, Any]) -> List[Any]: + """Returns a list + 1 Name // + 2 Arity // + 3 Flags // + 4 First key // + 5 Last key // + 6 Step // + 7 ACL categories (as of Redis 6.0) // + 8 Tips (as of Redis 7.0) // + 9 Key specifications (as of Redis 7.0) + 10 Subcommands (as of Redis 7.0) + """ + first_key = dict_deep_get(cmd_info, 'key_specs', 0, 'begin_search', 'spec', 'index', default_value=0) + last_key = dict_deep_get(cmd_info, 'key_specs', -1, 'begin_search', 'spec', 'index', default_value=0) + step = dict_deep_get(cmd_info, 'key_specs', 0, 'find_keys', 'spec', 'keystep', default_value=0) + tips = [] # todo + subcommands = [] # todo + res = [ + cmd_name.lower(), + cmd_info.get("arity", -1), + cmd_info.get("command_flags", []), + first_key, + last_key, + step, + cmd_info.get("acl_categories", []), + tips, + key_specs_array(cmd_info), + subcommands, + ] + return res + + +if __name__ == '__main__': + implemented = implemented_commands() + command_info_dict: Dict[str, List[Any]] = dict() + for cmd_meta in METADATA: + cmds = download_single_stack_commands(cmd_meta.local_filename, cmd_meta.url) + for cmd in cmds: + if cmd not in implemented: + continue + command_info_dict[cmd] = get_command_info(cmd, cmds[cmd]) + print(command_info_dict[cmd]) + with open(os.path.join(os.path.dirname(__file__), '..', 'fakeredis', 'commands.json'), 'w') as f: + json.dump(command_info_dict, f) diff --git a/scripts/supported.py b/scripts/supported.py deleted file mode 100755 index ed10d201..00000000 --- a/scripts/supported.py +++ /dev/null @@ -1,104 +0,0 @@ -import json -import os - -import requests - -from fakeredis._commands import SUPPORTED_COMMANDS - -THIS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) -COMMAND_FILES = [ - ('.commands.json', 'https://raw.githubusercontent.com/redis/redis-doc/master/commands.json'), - ('.json.commands.json', 'https://raw.githubusercontent.com/RedisJSON/RedisJSON/master/commands.json'), - ('.graph.commands.json', 'https://raw.githubusercontent.com/RedisGraph/RedisGraph/master/commands.json'), - ('.ts.commands.json', 'https://raw.githubusercontent.com/RedisTimeSeries/RedisTimeSeries/master/commands.json'), - ('.ft.commands.json', 'https://raw.githubusercontent.com/RediSearch/RediSearch/master/commands.json'), - ('.bloom.commands.json', 'https://raw.githubusercontent.com/RedisBloom/RedisBloom/master/commands.json'), -] - -TARGET_FILES = { - 'unimplemented': 'docs/redis-commands/unimplemented_commands.md', - 'implemented': 'docs/redis-commands/implemented_commands.md', -} - - -def download_redis_commands() -> dict: - cmds = {} - for filename, url in COMMAND_FILES: - full_filename = os.path.join(THIS_DIR, filename) - if not os.path.exists(full_filename): - contents = requests.get(url).content - open(full_filename, 'wb').write(contents) - curr_cmds = json.load(open(full_filename)) - cmds = cmds | {k.lower(): v for k, v in curr_cmds.items()} - return cmds - - -def implemented_commands() -> set: - res = set(SUPPORTED_COMMANDS.keys()) - if 'json.type' not in res: - raise ValueError('Make sure jsonpath_ng is installed to get accurate documenentation') - return res - - -def commands_groups( - all_commands: dict, implemented_set: set -) -> tuple[dict[str, list[str]], dict[str, list[str]]]: - implemented, unimplemented = dict(), dict() - for cmd in all_commands: - group = all_commands[cmd]['group'] - unimplemented.setdefault(group, []) - implemented.setdefault(group, []) - if cmd in implemented_set: - implemented[group].append(cmd) - else: - unimplemented[group].append(cmd) - return implemented, unimplemented - - -def generate_markdown_files( - all_commands: dict, - unimplemented: dict, - implemented: dict) -> None: - def print_groups(dictionary: dict, f): - for group in dictionary: - f.write(f'## {group} commands\n\n') - for cmd in dictionary[group]: - f.write(f"### [{cmd.upper()}](https://redis.io/commands/{cmd.replace(' ', '-')}/)\n\n") - f.write(f"{all_commands[cmd]['summary']}\n\n") - f.write("\n") - - supported_commands_file = open(TARGET_FILES['implemented'], 'w') - supported_commands_file.write("""# Supported commands - -Here is a list of all redis [implemented commands](#implemented-commands) and a -list of [unimplemented commands](#unimplemented-commands). - -------\n\n -""") - print_groups(implemented, supported_commands_file) - - unimplemented_cmds_file = open(TARGET_FILES['unimplemented'], 'w') - unimplemented_cmds_file.write("""# Unimplemented Commands -All the redis commands are implemented in fakeredis with these exceptions:\n\n""") - print_groups(unimplemented, unimplemented_cmds_file) - - -def get_unimplemented_and_implemented_commands() -> tuple[dict[str, list[str]], dict[str, list[str]]]: - """Returns 2 dictionaries, one of unimplemented commands and another of implemented commands - - """ - commands = download_redis_commands() - implemented_commands_set = implemented_commands() - implemented_dict, unimplemented_dict = commands_groups(commands, implemented_commands_set) - groups = sorted(implemented_dict.keys(), key=lambda x: len(unimplemented_dict[x])) - for group in groups: - unimplemented_count = len(unimplemented_dict[group]) - total_count = len(implemented_dict.get(group)) + unimplemented_count - print(f'{group} has {unimplemented_count}/{total_count} unimplemented commands') - return unimplemented_dict, implemented_dict - - -if __name__ == '__main__': - commands = download_redis_commands() - unimplemented_dict, implemented_dict = get_unimplemented_and_implemented_commands() - generate_markdown_files(commands, unimplemented_dict, implemented_dict) diff --git a/test/test_mixins/test_server_commands.py b/test/test_mixins/test_server_commands.py index 4497b405..f0badcaf 100644 --- a/test/test_mixins/test_server_commands.py +++ b/test/test_mixins/test_server_commands.py @@ -5,6 +5,8 @@ import redis from redis.exceptions import ResponseError +from fakeredis._commands import SUPPORTED_COMMANDS + def test_swapdb(r, create_redis): r1 = create_redis(1) @@ -41,6 +43,16 @@ def test_lastsave(r: redis.Redis): assert isinstance(r.lastsave(), datetime) +def test_command(r: redis.Redis): + commands_dict = r.command() + one_word_commands = {cmd for cmd in SUPPORTED_COMMANDS if ' ' not in cmd} + assert one_word_commands - set(commands_dict.keys()) == set() + + +def test_command_info(r: redis.Redis): + assert r.command_count() >= len(SUPPORTED_COMMANDS) + + @pytest.mark.slow def test_bgsave_timestamp_update(r: redis.Redis): early_timestamp = r.lastsave()