|
| 1 | +--- |
| 2 | +applyTo: '**' |
| 3 | +--- |
| 4 | +# Adding New Redis Commands to Upstash Redis Python Package |
| 5 | + |
| 6 | +## Package Structure Overview |
| 7 | + |
| 8 | +The Upstash Redis Python package is organized as follows: |
| 9 | + |
| 10 | +``` |
| 11 | +upstash_redis/ |
| 12 | +├── __init__.py # Package exports |
| 13 | +├── client.py # Synchronous Redis client |
| 14 | +├── commands.py # Command implementations |
| 15 | +├── commands.pyi # Type hints for commands |
| 16 | +├── errors.py # Custom exceptions |
| 17 | +├── format.py # Response formatters |
| 18 | +├── http.py # HTTP client for Redis REST API |
| 19 | +├── typing.py # Type definitions |
| 20 | +├── utils.py # Utility functions |
| 21 | +└── asyncio/ |
| 22 | + ├── __init__.py |
| 23 | + └── client.py # Asynchronous Redis client |
| 24 | +
|
| 25 | +tests/ |
| 26 | +├── commands/ # Command-specific tests organized by category |
| 27 | +│ ├── hash/ # Hash command tests |
| 28 | +│ ├── json/ # JSON command tests |
| 29 | +│ ├── list/ # List command tests |
| 30 | +│ ├── set/ # Set command tests |
| 31 | +│ ├── sortedSet/ # Sorted set command tests |
| 32 | +│ ├── string/ # String command tests |
| 33 | +│ └── asyncio/ # Async versions of tests |
| 34 | +``` |
| 35 | + |
| 36 | +## Steps to Add a New Redis Command |
| 37 | + |
| 38 | +### 1. Implement the Command in `commands.py` |
| 39 | + |
| 40 | +Add your command method to the `Commands` class: |
| 41 | + |
| 42 | +```python |
| 43 | +# filepath: upstash_redis/commands.py |
| 44 | +def your_new_command(self, key: str, *args, **kwargs) -> CommandsProtocol: |
| 45 | + """ |
| 46 | + Description of your command. |
| 47 | + |
| 48 | + Args: |
| 49 | + key: The Redis key |
| 50 | + *args: Additional arguments |
| 51 | + **kwargs: Additional keyword arguments |
| 52 | + |
| 53 | + Returns: |
| 54 | + CommandsProtocol: Command object for execution |
| 55 | + """ |
| 56 | + return self._execute_command("YOUR_REDIS_COMMAND", key, *args, **kwargs) |
| 57 | +``` |
| 58 | + |
| 59 | +### 2. Add Type Hints in `commands.pyi` |
| 60 | + |
| 61 | +Update the type stub file with your command signature: |
| 62 | + |
| 63 | +```python |
| 64 | +# filepath: upstash_redis/commands.pyi |
| 65 | +def your_new_command(self, key: str, *args, **kwargs) -> CommandsProtocol: ... |
| 66 | +``` |
| 67 | + |
| 68 | +### 3. Update Client Classes (if needed) |
| 69 | + |
| 70 | +If your command requires special handling, update both sync and async clients: |
| 71 | + |
| 72 | +```python |
| 73 | +# filepath: upstash_redis/client.py |
| 74 | +# Add any client-specific logic if needed |
| 75 | + |
| 76 | +# filepath: upstash_redis/asyncio/client.py |
| 77 | +# Add async version if special handling is needed |
| 78 | +``` |
| 79 | + |
| 80 | +### 4. Add Response Formatting (if needed) |
| 81 | + |
| 82 | +If your command returns data that needs special formatting, add a formatter in `format.py`: |
| 83 | + |
| 84 | +```python |
| 85 | +# filepath: upstash_redis/format.py |
| 86 | +def format_your_command_response(response: Any) -> YourReturnType: |
| 87 | + """Format the response from your Redis command.""" |
| 88 | + # Implementation here |
| 89 | + pass |
| 90 | +``` |
| 91 | + |
| 92 | +### 5. Write Comprehensive Tests |
| 93 | + |
| 94 | +Create test files in the appropriate category folder: |
| 95 | + |
| 96 | +```python |
| 97 | +# filepath: tests/commands/{category}/test_your_new_command.py |
| 98 | +import pytest |
| 99 | +from upstash_redis import Redis |
| 100 | + |
| 101 | +def test_your_new_command_basic(): |
| 102 | + """Test basic functionality of your new command.""" |
| 103 | + redis = Redis.from_env() |
| 104 | + result = redis.your_new_command("test_key", "arg1", "arg2") |
| 105 | + # Add assertions |
| 106 | + |
| 107 | +def test_your_new_command_edge_cases(): |
| 108 | + """Test edge cases and error conditions.""" |
| 109 | + # Add edge case tests |
| 110 | + |
| 111 | +# If async support is needed: |
| 112 | +# filepath: tests/commands/asyncio/test_your_new_command.py |
| 113 | +import pytest |
| 114 | +from upstash_redis.asyncio import Redis as AsyncRedis |
| 115 | + |
| 116 | +@pytest.mark.asyncio |
| 117 | +async def test_your_new_command_async(): |
| 118 | + """Test async version of your new command.""" |
| 119 | + redis = AsyncRedis.from_env() |
| 120 | + result = await redis.your_new_command("test_key", "arg1", "arg2") |
| 121 | + # Add assertions |
| 122 | +``` |
| 123 | + |
| 124 | +### 6. Update Package Exports (if needed) |
| 125 | + |
| 126 | +If you're adding a new public class or function, update `__init__.py`: |
| 127 | + |
| 128 | +```python |
| 129 | +# filepath: upstash_redis/__init__.py |
| 130 | +from upstash_redis.your_new_module import YourNewClass |
| 131 | + |
| 132 | +__all__ = ["AsyncRedis", "Redis", "YourNewClass"] |
| 133 | +``` |
| 134 | + |
| 135 | +## Command Categories and Organization |
| 136 | + |
| 137 | +Commands are typically organized into these categories: |
| 138 | + |
| 139 | +- **String**: Basic key-value operations (`GET`, `SET`, etc.) |
| 140 | +- **Hash**: Hash field operations (`HGET`, `HSET`, etc.) |
| 141 | +- **List**: List operations (`LPUSH`, `RPOP`, etc.) |
| 142 | +- **Set**: Set operations (`SADD`, `SREM`, etc.) |
| 143 | +- **Sorted Set**: Sorted set operations (`ZADD`, `ZREM`, etc.) |
| 144 | +- **JSON**: JSON operations (`JSON.GET`, `JSON.SET`, etc.) |
| 145 | +- **Generic**: Key management (`DEL`, `EXISTS`, etc.) |
| 146 | +- **Server**: Server management commands |
| 147 | + |
| 148 | +## Testing Guidelines |
| 149 | + |
| 150 | +1. **Test file naming**: `test_{command_name}.py` |
| 151 | +2. **Test function naming**: `test_{command_name}_{scenario}` |
| 152 | +3. **Include both positive and negative test cases** |
| 153 | +4. **Test with different data types and edge cases** |
| 154 | +5. **Add async tests if the command supports async operations** |
| 155 | +6. **Use appropriate fixtures from `conftest.py`** |
| 156 | + |
| 157 | +## Example: Adding a New Hash Command |
| 158 | + |
| 159 | +Here's a complete example of adding a hypothetical `HMERGE` command: |
| 160 | + |
| 161 | +```python |
| 162 | +# filepath: upstash_redis/commands.py |
| 163 | +def hmerge(self, key: str, source_key: str) -> CommandsProtocol: |
| 164 | + """ |
| 165 | + Merge hash from source_key into key. |
| 166 | + |
| 167 | + Args: |
| 168 | + key: Destination hash key |
| 169 | + source_key: Source hash key to merge from |
| 170 | + |
| 171 | + Returns: |
| 172 | + CommandsProtocol: Command for execution |
| 173 | + """ |
| 174 | + return self._execute_command("HMERGE", key, source_key) |
| 175 | +``` |
| 176 | + |
| 177 | +```python |
| 178 | +# filepath: tests/commands/hash/test_hmerge.py |
| 179 | +import pytest |
| 180 | +from upstash_redis import Redis |
| 181 | + |
| 182 | +def test_hmerge_basic(redis_client): |
| 183 | + """Test basic HMERGE functionality.""" |
| 184 | + redis = redis_client |
| 185 | + |
| 186 | + # Setup |
| 187 | + redis.hset("hash1", {"field1": "value1", "field2": "value2"}) |
| 188 | + redis.hset("hash2", {"field3": "value3", "field2": "overwrite"}) |
| 189 | + |
| 190 | + # Execute |
| 191 | + result = redis.hmerge("hash1", "hash2") |
| 192 | + |
| 193 | + # Verify |
| 194 | + merged_hash = redis.hgetall("hash1") |
| 195 | + assert merged_hash["field1"] == "value1" |
| 196 | + assert merged_hash["field2"] == "overwrite" # Should be overwritten |
| 197 | + assert merged_hash["field3"] == "value3" # Should be added |
| 198 | +``` |
| 199 | + |
| 200 | +## Running Tests |
| 201 | + |
| 202 | +To run tests for your new command: |
| 203 | + |
| 204 | +```bash |
| 205 | +# Run specific test file |
| 206 | +pytest tests/commands/hash/test_your_command.py -v |
| 207 | + |
| 208 | +# Run all tests in a category |
| 209 | +pytest tests/commands/hash/ -v |
| 210 | + |
| 211 | +# Run all tests |
| 212 | +pytest tests/ -v |
| 213 | +``` |
| 214 | + |
| 215 | +Follow this structure and you'll have a well-integrated Redis command that follows the package's conventions and patterns. |
0 commit comments