Skip to main content
Markdown

Redis Server Commands

Extend Redis with commands written in Codon. A handler receives an argument view and a Redis context, then returns a native value or protocol response. The SDK compiles handlers into a shared library that Redis loads as a module.

Server commands suit short operations close to stored data, such as transforming a value or combining several Redis calls. The typed API manages the native boundary while command declarations specify arguments, keys, and access flags.

Before you begin: install the all-SDK distribution and Redis 7.2+ tools. Use a disposable local server; the example changes a key named greeting. You will build and load codon_demo.so.

Write and Load Commands​

Save the following as commands.codon. It defines three commands: add two numbers, uppercase a stored string, and echo a binary value.

commands.codon
from exaloop.databases.redis import Module

module = Module("codon_demo")

@module.command("codon.add", arity=2, flags="readonly fast")
def add(context, args):
return args.int(0) + args.int(1)

@module.command("codon.upper", arity=1, flags="write deny-oom", keys=(1, 1, 1))
def upper(context, args):
key = args[0]
value = context.get(key)
if value is None:
return False
context[key] = value.decode("utf-8").upper()
return True

@module.command("codon.echo", arity=1, flags="readonly fast")
def echo(context, args):
return args[0]

The decorator declares the command name, argument count, and access behavior. Inside the handler, args.int(0) parses an integer while args[0] returns raw bytes. The upper handler additionally identifies its first argument as a key that it writes; key declarations are required for permission checks and introspection.

Compile the handlers, then start a development server on an unused local port (6379 in this example):

Terminal
exaloop --kind redis-module build commands.codon -o "$PWD/build/codon_demo.so"
redis-server --bind 127.0.0.1 --loadmodule "$PWD/build/codon_demo.so"
Use the SDK builder directly

With Codon 0.20.3 and Python 3.9+ available:

Terminal
python3 /path/to/sdk/redis/build.py commands.codon \
--codon /absolute/path/to/codon -o "$PWD/build/codon_demo.so"

In another terminal, try the commands:

Terminal
redis-cli --raw codon.add 19 23
redis-cli --raw SET greeting hello
redis-cli --raw codon.upper greeting
redis-cli --raw GET greeting
Output
42
OK
1
HELLO

You can also load a module into a running server with MODULE LOAD, where administrative module loading is enabled. That permission should be restricted to trusted administrators: loading a module executes native code in the server process.

Registration​

Create one Module per library and give it a name unique to the server. The arity=2 declaration on codon.add asks Redis to reject a call without exactly two arguments, while readonly fast describes a short operation that does not change stored data.

Choose flags that describe what the command actually does. In particular, a command that writes data needs the write flag. The registration reference covers the complete decorator signature.

Declare the Keys a Command Uses​

Redis uses key declarations for permission checks and command introspection. For codon.upper, keys=(1, 1, 1) identifies the first argument as the only key. These positions count from one, even though args[0] reads that argument inside the handler.

Declare every key the command will use, including keys accessed through nested Redis calls. A read/modify/write operation needs read/write access, not just permission to overwrite a value.

Arguments, Keys, and Calls​

Arguments are binary by default, preserving Redis values exactly. Use args.text(index) for UTF-8 or a numeric accessor for numbers. The context's mapping syntax is convenient for string keys; context.get(key) lets you handle a missing key without catching KeyError.

APIDescription
len(args), args[index], for value in argsArgument count and owned binary values; negative indexes work
args.text(index)Decode UTF-8; invalid encoding raises
args.int(index)Native Redis signed 64-bit integer parser; invalid/overflowing input raises
args.float(index)Native Redis double parser; invalid input raises
context.get(key)Owned bytes, or None for a missing string key
context[key]Owned bytes; missing keys raise KeyError
context.set(key, value), context[key] = valueSet a string value and remove its TTL, like SET
context.call(command, *args)Invoke a Redis command and return a scoped Reply; Redis errors raise
context.callv(command, args)Invoke a Redis command with a runtime-sized iterable of arguments
reply.is_null, reply.int(), reply.bytes(), reply.text()Explicit typed access to a command reply
len(reply), reply[index], iterationArray reply access, including negative indexes

Use direct key access for strings and context.call() for other Redis operations. For example, context.call("LRANGE", key, 0, -1) reads a list, while context.call("INCR", key).int() increments a counter and returns its new value. Nested calls still follow the client's ACL permissions and Redis's script-mode restrictions; they do not grant extra privileges.

Replies and Errors​

Return a number, string, bytes, or a list/tuple to send a response to the client. You can also return a command reply directly, or use Status("OK") and Error("ERR message") for explicit protocol responses.

An exception becomes a Redis error, but it does not undo earlier writes. Validate the inputs before changing data when a command performs several steps.

Persistence and State​

Writes to Redis keys participate in Redis replication and persistence. Codon module globals do not, so keep durable or shared state in keys. A handler's context and replies are only valid during that call; retain copied values rather than those handles.

Next Steps​