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.
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):
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:
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:
redis-cli --raw codon.add 19 23
redis-cli --raw SET greeting hello
redis-cli --raw codon.upper greeting
redis-cli --raw GET greeting
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.
| API | Description |
|---|---|
len(args), args[index], for value in args | Argument 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] = value | Set 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], iteration | Array 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
- Deploy the module with its runtime libraries and a restart plan.
- Use the command reference to add flags and key declarations.
- Call Redis from an application for work that should not block the server's command thread.