Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .claude/command-specification-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# $COMMAND_NAME command specification

## Supported version

Add supported Redis version here. For example: Redis >= 6.2.0

## Command description

Add a description of the command here.

## Command API

Specify an API for the command in the format that official docs uses. For example:

```
$COMMAND_NAME $key $member [NX|XX] [CH] [INCR]
```

## Redis-CLI examples

Add relevant Redis-CLI examples here.

## Test plan

Specify how you want to test the command in terms of integration testing. For example:

- Test only with required arguments, assert that single value returned
- Test with required arguments and optional XX modifier, ensure that 1 returned
- ...
87 changes: 87 additions & 0 deletions .claude/commands/add-new-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
description: Adds support for a new Redis command from a given specification. Check command-specification-template.md.
argument-hint: [path-to-specification]
---

# Execute: Add new Redis command support

## Plan to Execute

Read specification file: `$ARGUMENTS`

## Execution Instructions

### 1. Preparations

- Follow instructions from `.agent/instructions.md`
- Go through the guide `specs/redis_commands_guide.md`

### 2. Read and Understand

- Read the ENTIRE specification carefully
- Go through Command Description and identify command type (string, list, set, etc.)
- Go through the Command API:
- Identify required and optional arguments
- Identify how to match Redis command arguments type to Python types
- Identify return value and possible response types
- Check relevant Redis-Cli examples, if provided
- Review the Test Plan

### 3. Execute Tasks in Order

#### a. Navigate to the task
- Identify the files and action required
- Read existing related files if modifying

#### b. Implement the command
- Add new command method within matching trait object (for example: `redis/commands/core.py` for core Redis commands)
- Ensure overloading is implemented for sync and async API
- Follow Arguments definition section from `specs/redis_commands_guide.md` before defining command arguments
- Ensure arguments and response types consider bytes representation
- Ensure response RESP2 and RESP3 compatibility via `response_callbacks`

#### c. Verify as you go
- After each file change, check syntax
- Ensure imports are correct
- Verify types are properly defined
- Verify that response schema is similar for RESP2 and RESP3

### 4. Implement Testing Plan

After completing implementation tasks:

- Identify matching test file or create new one if needed
- Implement all test cases as separate test methods
- Ensure adding version constraint if specified in the specification
- Ensure tests cover edge cases

### 5. Run tests

- Run newly added test cases using `pytest` with RESP2 and RESP3 protocol specified via `--protocol` option
- Ensure that the same test cases passed with both protocols
- Get back to the Implementation stage if any test failed

### 6. Final Verification

Before completing:

- ✅ All tasks from plan completed
- ✅ All tests created and passing
- ✅ Code follows project conventions
- ✅ Documentation added/updated as needed

## Output Report

Provide summary:

### Completed Tasks
- List of all tasks completed
- Files created (with paths)
- Files modified (with paths)

### Tests Added
- Test files created
- Test cases implemented
- Test results


20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ If you don't know where to start, consider improving
documentation, bug triaging, and writing tutorials are all examples of
helpful contributions that mean less work for you.

## AI-driven contributions

Redis-py defines a list of custom Claude commands (skills) that may simplify your contribution by enriching the agent's
context with necessary information and repository best practices. Commands are well-structured instructions for common
recurring tasks (e.g., adding support for a new Redis command API).

The list of available commands is available via:

**Claude CLI**

```
claude /skills
```

**Augment CLI**

```
auggie command list
```

## Your First Contribution

Unsure where to begin contributing? You can start by looking through
Expand Down
72 changes: 72 additions & 0 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6801,6 +6801,78 @@ def xlen(self, name: KeyT) -> int | Awaitable[int]:
"""
return self.execute_command("XLEN", name, keys=[name])

@overload
def xnack(
self: SyncClientProtocol,
name: KeyT,
groupname: GroupT,
mode: Literal["SILENT", "FAIL", "FATAL"],
*ids: StreamIdT,
retrycount: int | None = None,
force: bool = False,
) -> int: ...

@overload
def xnack(
self: AsyncClientProtocol,
name: KeyT,
groupname: GroupT,
mode: Literal["SILENT", "FAIL", "FATAL"],
*ids: StreamIdT,
retrycount: int | None = None,
force: bool = False,
) -> Awaitable[int]: ...

def xnack(
self,
name: KeyT,
groupname: GroupT,
mode: Literal["SILENT", "FAIL", "FATAL"],
*ids: StreamIdT,
retrycount: int | None = None,
force: bool = False,
) -> int | Awaitable[int]:
"""
Negatively acknowledges one or more messages in a consumer group's
Pending Entries List (PEL).

Args:
name: name of the stream.
groupname: name of the consumer group.
mode: the nacking mode. One of SILENT, FAIL, or FATAL.
SILENT: consumer shutting down; decrements delivery counter.
FAIL: consumer unable to process; delivery counter unchanged.
FATAL: invalid/malicious message; delivery counter set to max.
*ids: one or more message IDs to NACK.
retrycount: optional integer >= 0. Overrides the mode's implicit
delivery counter adjustment with an exact value.
force: if True, creates a new unowned PEL entry for any ID not
already in the group's PEL.

Returns:
The number of messages successfully NACKed.

For more information, see https://redis.io/commands/xnack
"""
if not ids:
raise DataError("XNACK requires at least one message ID")

if mode not in {"SILENT", "FAIL", "FATAL"}:
raise DataError("XNACK mode must be one of: SILENT, FAIL, FATAL")

pieces: list = [name, groupname, mode, "IDS", len(ids)]
pieces.extend(ids)

if retrycount is not None:
if retrycount < 0:
raise DataError("XNACK retrycount must be >= 0")
pieces.extend([b"RETRYCOUNT", retrycount])

if force:
pieces.append(b"FORCE")
Comment thread
vladvildanov marked this conversation as resolved.

return self.execute_command("XNACK", *pieces)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XNACK is a non-existent hallucinated Redis command

High Severity

XNACK is not an actual Redis command — it's an unimplemented feature request (redis/redis#5934, opened 2019, still labeled "needs-design"). The command's syntax, modes (SILENT, FAIL, FATAL), IDS count format, RETRYCOUNT, and FORCE options all appear to be AI-fabricated. Redis 8.8.0 (used in @skip_if_server_version_lt) doesn't exist either; the latest release is 8.6. This adds non-functional dead code that will always fail against any real Redis server.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fee6e93. Configure here.


@overload
def xpending(
self: SyncClientProtocol, name: KeyT, groupname: GroupT
Expand Down
91 changes: 91 additions & 0 deletions specs/redis_commands_guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Redis commands guide

## Commands API specification

The Redis API is specified in the [Redis documentation](https://redis.io/commands). This is the source of truth
for all command-related information. However, Redis is a living project and new commands are added all the time.

A new command may not yet be available in the documentation. In this case, the developer needs to create a new
command specification from the `.claude/command-specification-template.md` template.

## Files structure

```
redis/
├── commands/ # Base directory
│ ├── module/ # Module commands directory
│ │ ├── commands.py # Module commands public API
│ │ └── file.py # Custom helpers
│ │... # Other modules
│ ├── __init__.py # Module exports
│ ├── cluster.py # Cluster commands public API
│ ├── core.py # Core commands public API
│ ├── helpers.py # Helpers for commands modules
│ ├── redismodules.py # Trait for all Redis modules
│ └── sentinel.py # Sentinel commands public API
```

## Commands Public API

Commands public API exposed through the different types of clients (Standalone, Cluster, Sentinel), inheriting trait
objects defined in the directories according to the Files structure section above. Each command implementation at the
end calls generic `execute_command(*args, **kwargs)` defined by `CommandProtocol`.

SDK expose sync and async commands API through methods overloading, see `.agent/sync_async_type_hints_overload_guide.md`
for more information.

### Binary representation

Our SDK can be configured with `decode_response=False` option which means that command response will be returned
as bytes, as well as we allow to pass bytes arguments instead of strings. For this purpose, we define a custom types
like `KeyT`, `EncodableT`, etc. in `redis/typing.py`.

## Protocols compatibility

SDK supports two types of Redis wire protocol: RESP2 and RESP3. And aims to provide a compatibility between them
for seamless user experience. However, there are some differences in types that defined by these protocols.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types that defined --> types that are defined


Because, RESP3 introduce new types that wasn't previously supported by RESP2, we're aiming for forward compatibility
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types that wasn't --> types that weren't

and ensure that all new types supported by RESP3 can be used with RESP2. In most cases the semantic of RESP3 can be
easily recognized and converting existing RESP2 response in RESP3 is a matter of parsing strategy.

All new commands should be added with RESP3 in mind and by default should hide protocol incompatibility from user.

To understand how does Redis types defined by RESP2 and RESP3 protocol maps to Python types,
see `redis/_parsers/resp2.py` and `redis/_parsers/resp3.py`.

Parsers are responsible for RESP protocol parsing. However, protocols compatibility is achieved by 2nd layer
parsing as `response_callbacks` defined in `redis/_parsers/helpers.py` and can be extended/updated on client level.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For modules the callbacks are defined usually in module's utils.py file


## Arguments definition

Some of the Redis commands may have a complex API structure, so we need to make it user-friendly and apply
some transformation within specific command method. For example, some commands may need to have
a COUNT argument for aggregated types followed by number of arguments and arguments itself. In this case
we hide this complexity from user and instead expose argument as list in public API and transform it
to Redis-friendly format.

```python
def scan(
self,
cursor: int = 0,
count: int | None = None,
_type: str | None = None,
**kwargs,
):
pieces: list = [cursor]

if count is not None:
pieces.extend([b"COUNT", count])
```

In terms of required and optional arguments we follow the specification and trying to reflect it as close
as possible.

### Testing

Command tests are located in `tests/test_*command_type*.py` and `tests/test_asyncio/test_*command_type*.py` for async
commands. So it's important to identify command type upfront to resolve correct test file.

We usually provide only integration testing for commands with defined version constraint (if required). It's controlled
by custom annotations `@skip_if_server_version_lt()` and `@skip_if_server_version_gte()` defined in `tests/conftest.py`.
Loading
Loading