Skip to content
Merged
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
77 changes: 4 additions & 73 deletions nexios_contrib/redis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Redis integration for [Nexios](https://nexios-labs.github.io/nexios/) web framew
## Features

- 🚀 **High-Performance Redis Client**: Async Redis client with connection pooling and health checks
- 🔄 **Dependency Injection**: Multiple DI patterns for clean, testable code
- 🔄 **Dependency Injection**: Simplified DI pattern for clean, testable code
- 🏗️ **Connection Management**: Automatic connection lifecycle management
- 📊 **Data Structure Support**: Strings, Hashes, Lists, Sets, JSON operations
- ⚙️ **Configuration**: Environment-based and programmatic configuration
Expand Down Expand Up @@ -136,77 +136,7 @@ async def get_user(
return {"user": user_data or {"id": user_id, "name": "Unknown"}}
```

### Operation-Level Injection

```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisOperationDepend

app = NexiosApp()

@app.get("/counter/{name}")
async def get_counter(
request: Request,
response: Response,
name: str,
counter_value: int = RedisOperationDepend("get", f"counter:{name}")
):
# counter_value is already the result of redis.get(f"counter:{name}")
return {"counter": name, "value": counter_value or 0}

@app.post("/counter/{name}/incr")
async def increment_counter(
request: Request,
response: Response,
name: str,
new_value: int = RedisOperationDepend("incr", f"counter:{name}", 1)
):
# new_value is already the result of redis.incr(f"counter:{name}", 1)
return {"counter": name, "value": new_value}
```

### Key-Based Injection

```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisKeyDepend

app = NexiosApp()

@app.get("/user/{user_id}")
async def get_user(
request: Request,
response: Response,
user_id: str,
user_data: dict = RedisKeyDepend(
f"user:{{user_id}}", # Uses path parameter
"json_get",
{"name": "Unknown"} # Default value
)
):
# user_data is already fetched from Redis with fallback
return {"user": user_data}
```

### Cache Pattern

```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisCacheDepend

app = NexiosApp()

@app.get("/expensive-computation/{param}")
async def expensive_computation(
request: Request,
response: Response,
param: str,
result: str = RedisCacheDepend(f"expensive:{param}", ttl=600)
):
# In a real implementation, this would check cache first
# and compute/fallback if not found
return {"param": param, "result": f"computed_{param}"}
```

## Redis Operations

Expand Down Expand Up @@ -470,7 +400,7 @@ async def test_with_real_redis():
from nexios import NexiosApp
from nexios.http import Request, Response
from nexios_contrib.redis import (
init_redis, RedisDepend, RedisOperationDepend
init_redis, RedisDepend
)

app = NexiosApp()
Expand Down Expand Up @@ -507,8 +437,9 @@ async def record_visit(
request: Request,
response: Response,
user_id: str,
visit_count: int = RedisOperationDepend("incr", f"user:{user_id}:visits", 1)
redis: RedisClient = RedisDepend()
):
visit_count = await redis.incr(f"user:{user_id}:visits", 1)
return {"user_id": user_id, "visits": visit_count}

if __name__ == "__main__":
Expand Down
23 changes: 1 addition & 22 deletions nexios_contrib/redis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,8 @@
if TYPE_CHECKING:
from nexios.dependencies import Context


__version__ = "0.1.0"
# __all__ = [
# "RedisConfig",
# "RedisClient",
# "RedisConnectionError",
# "init_redis",
# "get_redis",
# "get_redis_client",
# "redis_get",
# "redis_set",
# "redis_delete",
# "redis_exists",
# "redis_expire",
# "redis_ttl",
# "redis_incr",
# "redis_decr",
# "redis_json_get",
# "redis_json_set",
# "RedisDepend",
# "RedisOperationDepend",
# "RedisKeyDepend",
# "RedisCacheDepend",
# ]

# Global Redis client instance
_redis_client: Optional[RedisClient] = None
Expand Down
185 changes: 3 additions & 182 deletions nexios_contrib/redis/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
This module provides dependency injection utilities for accessing
Redis client and performing Redis operations in route handlers.
"""

from __future__ import annotations

import asyncio
from typing import Any, Literal, Optional, Union
from typing import Any, Optional, Union
from nexios.dependencies import Depend, Context
from nexios.http import Request

Expand Down Expand Up @@ -43,187 +43,8 @@ async def get_cached_data(
return {"value": value}
```
"""

def _wrap(context: Context = Context()) -> RedisClient:
return get_redis(context)

return Depend(_wrap)


def RedisOperationDepend(
operation: Literal[
"get", "set", "delete", "exists", "expire", "ttl", "incr", "decr",
"json_get", "json_set", "hget", "hset", "hgetall", "lpush", "rpush",
"lpop", "rpop", "llen", "sadd", "smembers", "srem", "scard",
"keys", "flushdb", "execute"
],
*args: Any,
**kwargs: Any
) -> Any:
"""
Dependency injection function for Redis operations.

This function allows injecting the result of common Redis operations
directly into route handlers.

Args:
operation: The Redis operation to perform ('get', 'set', 'exists', etc.)
*args: Positional arguments for the operation
**kwargs: Keyword arguments for the operation

Returns:
Any: Dependency injection wrapper function that returns operation result.

Example:
```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisOperationDepend

app = NexiosApp()

@app.get("/cache/{key}")
async def get_cached_data(
request: Request,
cached_value: str = RedisOperationDepend("get", request.path_params["key"])
):
# cached_value is already the result of redis.get(key)
return {"value": cached_value}
```

@app.post("/counter/{name}")
async def increment_counter(
request: Request,
new_value: int = RedisOperationDepend("incr", "counter", 1)
):
# new_value is already the result of redis.incr("counter", 1)
return {"counter": new_value}
```
"""
async def _wrap(context: Context = Context()) -> Any:
redis = get_redis(context)

# Get the Redis operation method
if not hasattr(redis, operation):
raise AttributeError(f"Redis client has no operation '{operation}'")

method = getattr(redis, operation)

# Call the operation with provided arguments
if asyncio.iscoroutinefunction(method):
return await method(*args, **kwargs)
else:
return method(*args, **kwargs)

return Depend(_wrap)


def RedisKeyDepend(
key: str,
operation: Literal[
"get", "json_get", "exists", "ttl", "expire", "incr", "decr",
"hget", "hgetall", "llen", "scard"
] = "get",
default: Any = None,
) -> Any:
"""
Dependency injection function for Redis key operations.

This function provides a convenient way to inject Redis values
for specific keys with fallback defaults.

Args:
key: The Redis key to operate on
operation: The operation to perform ('get', 'set', 'exists', etc.)
default: Default value if key doesn't exist (for 'get' operation)

Returns:
Any: Dependency injection wrapper function.

Example:
```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisKeyDepend

app = NexiosApp()

@app.get("/user/{user_id}")
async def get_user(
user_id: str,
user_data: dict = RedisKeyDepend(f"user:{{user_id}}", "json_get", {"name": "Unknown"})
):
# user_data is the result of redis.json_get(f"user:{user_id}") or {"name": "Unknown"}
return {"user": user_data}
```
"""
async def _wrap(context: Context = Context()) -> Any:
redis = get_redis(context)

try:
# Format the key with context if it contains placeholders
formatted_key = key.format(**context.__dict__) if hasattr(context, '__dict__') else key

if operation == "get":
result = await redis.get(formatted_key)
return result if result is not None else default
elif operation == "json_get":
result = await redis.json_get(formatted_key)
return result if result is not None else default
elif operation == "exists":
return await redis.exists(formatted_key)
else:
# For other operations, return the method result
method = getattr(redis, operation)
if asyncio.iscoroutinefunction(method):
return await method(formatted_key)
else:
return method(formatted_key)
except Exception:
return default

return Depend(_wrap)


def RedisCacheDepend(key: str, ttl: int = 300) -> Any:
"""
Dependency injection function for cached values.

This function provides a simple caching mechanism where values
are cached for a specified time-to-live period.

Args:
key: The cache key
ttl: Time-to-live in seconds

Returns:
Any: Dependency injection wrapper function.

Example:
```python
from nexios import NexiosApp
from nexios_contrib.redis import RedisCacheDepend

app = NexiosApp()

@app.get("/expensive-operation/{param}")
async def expensive_operation(
param: str,
result: str = RedisCacheDepend(f"expensive:{param}", ttl=600)
):
# If not cached, this would be computed and cached
# For demo, we'll just return a value
return {"result": f"computed_{param}"}
```
"""
async def _wrap(context: Context = Context()) -> Any:
redis = get_redis(context)

# For this example, we'll just get the key
# In a real implementation, you might want to check cache first
# and compute/fallback if not found
cached_value = await redis.get(key)
if cached_value:
return cached_value

# Return a placeholder - in real usage, this would trigger computation
return f"cached_{key}"

return Depend(_wrap)
Loading
Loading