From 6b3a1d773a3ee9efa21b73ad71d2220944de617d Mon Sep 17 00:00:00 2001 From: "techwithdunamix@gmail.com" Date: Wed, 1 Apr 2026 19:13:01 +0100 Subject: [PATCH 1/3] refactor(redis): remove unused dependency functions - Removed RedisOperationDepend, RedisKeyDepend, and RedisCacheDepend functions - Simplified the Redis dependency module by keeping only the RedisDepend function - This refactoring reduces complexity and removes redundant code --- nexios_contrib/redis/dependency.py | 182 +---------------------------- 1 file changed, 2 insertions(+), 180 deletions(-) diff --git a/nexios_contrib/redis/dependency.py b/nexios_contrib/redis/dependency.py index 00fe26e..96ced65 100644 --- a/nexios_contrib/redis/dependency.py +++ b/nexios_contrib/redis/dependency.py @@ -4,6 +4,7 @@ This module provides dependency injection utilities for accessing Redis client and performing Redis operations in route handlers. """ + from __future__ import annotations import asyncio @@ -43,187 +44,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) From d94d7de2f6e7d9969d5ef8da7bce6d4b32bec1fd Mon Sep 17 00:00:00 2001 From: "techwithdunamix@gmail.com" Date: Wed, 1 Apr 2026 19:24:12 +0100 Subject: [PATCH 2/3] refactor(redis): simplify dependency injection and remove unnecessary classes --- nexios_contrib/redis/README.md | 77 +-------- nexios_contrib/redis/__init__.py | 23 +-- nexios_contrib/redis/dependency.py | 3 +- tests/test_redis/test_redis_dependencies.py | 175 +------------------- 4 files changed, 7 insertions(+), 271 deletions(-) diff --git a/nexios_contrib/redis/README.md b/nexios_contrib/redis/README.md index d371da4..101c02f 100644 --- a/nexios_contrib/redis/README.md +++ b/nexios_contrib/redis/README.md @@ -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 @@ -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 @@ -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() @@ -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__": diff --git a/nexios_contrib/redis/__init__.py b/nexios_contrib/redis/__init__.py index 38347a4..1d1741a 100644 --- a/nexios_contrib/redis/__init__.py +++ b/nexios_contrib/redis/__init__.py @@ -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 diff --git a/nexios_contrib/redis/dependency.py b/nexios_contrib/redis/dependency.py index 96ced65..dcecbac 100644 --- a/nexios_contrib/redis/dependency.py +++ b/nexios_contrib/redis/dependency.py @@ -7,8 +7,7 @@ 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 diff --git a/tests/test_redis/test_redis_dependencies.py b/tests/test_redis/test_redis_dependencies.py index 30dbdcc..fe609e5 100644 --- a/tests/test_redis/test_redis_dependencies.py +++ b/tests/test_redis/test_redis_dependencies.py @@ -10,7 +10,7 @@ from nexios.dependencies import Context from nexios_contrib.redis.dependency import ( - RedisDepend, RedisOperationDepend, RedisKeyDepend, RedisCacheDepend + RedisDepend ) @@ -48,180 +48,7 @@ async def get_value(request: Request,response, redis=RedisDepend()): assert response.status_code == 200 assert response.json() == {"key": "mykey", "value": "test_value"} mock_redis.get.assert_called_with("mykey") - def test_redis_operation_depend_get(self, test_client_with_redis, mock_redis): - """Test RedisOperationDepend with GET operation.""" - app = test_client_with_redis.app - - @app.get("/direct-get/{key}") - async def direct_get( - request: Request, - response: Response, - value=RedisOperationDepend("get", "test_key") - ): - # Value should be the result of redis.get("test_key") - return {"value": value} - - mock_redis.get.return_value = "direct_value" - - response = test_client_with_redis.get("/direct-get/test_key") - assert response.status_code == 200 - assert response.json() == {"value": "direct_value"} - mock_redis.get.assert_called_with("test_key") - - - - - - - def test_redis_key_depend_get_default(self, test_client_with_redis, mock_redis): - """Test RedisKeyDepend with GET operation and default value.""" - app = test_client_with_redis.app - - @app.get("/user/{user_id}") - async def get_user( - request: Request, - response: Response, - user_data=RedisKeyDepend("user:123", "get", {"name": "Unknown"}) - ): - return {"user": user_data} - - # Test with existing key - mock_redis.get.return_value = "John Doe" - response = test_client_with_redis.get("/user/123") - assert response.status_code == 200 - assert response.json() == {"user": "John Doe"} - - # Test with non-existing key (returns None) - mock_redis.get.return_value = None - response = test_client_with_redis.get("/user/123") - assert response.status_code == 200 - assert response.json() == {"user": {"name": "Unknown"}} - - - def test_redis_key_depend_exists(self, test_client_with_redis, mock_redis): - """Test RedisKeyDepend with EXISTS operation.""" - app = test_client_with_redis.app - - @app.get("/check/{key}") - async def check_key( - request: Request, - response: Response, - exists=RedisKeyDepend("check_key", "exists", 0) - ): - return {"exists": bool(exists)} - - # Test existing key - mock_redis.exists.return_value = 1 - response = test_client_with_redis.get("/check/test") - assert response.status_code == 200 - assert response.json() == {"exists": True} - - # Test non-existing key - mock_redis.exists.return_value = 0 - response = test_client_with_redis.get("/check/test") - assert response.status_code == 200 - assert response.json() == {"exists": False} - - def test_redis_key_depend_ttl(self, test_client_with_redis, mock_redis): - """Test RedisKeyDepend with TTL operation.""" - app = test_client_with_redis.app - - @app.get("/ttl/{key}") - async def get_ttl( - request: Request, - response: Response, - ttl=RedisKeyDepend("ttl_key", "ttl", -1) - ): - return {"ttl": ttl} - - # Test key with TTL - mock_redis.ttl.return_value = 300 - response = test_client_with_redis.get("/ttl/test") - assert response.status_code == 200 - assert response.json() == {"ttl": 300} - - # Test key without TTL - mock_redis.ttl.return_value = -1 - response = test_client_with_redis.get("/ttl/test") - assert response.status_code == 200 - assert response.json() == {"ttl": -1} - - def test_redis_key_depend_error_fallback(self, test_client_with_redis, mock_redis): - """Test RedisKeyDepend error handling with fallback to default.""" - app = test_client_with_redis.app - - @app.get("/error-test") - async def error_test( - request: Request, - response: Response, - value=RedisKeyDepend("error_key", "get", "fallback_value") - ): - return {"value": value} - - # Mock Redis to raise an exception - from nexios_contrib.redis.client import RedisOperationError - mock_redis.get.side_effect = RedisOperationError("Connection error") - - response = test_client_with_redis.get("/error-test") - assert response.status_code == 200 - assert response.json() == {"value": "fallback_value"} - def test_redis_cache_depend_basic(self, test_client_with_redis, mock_redis): - """Test RedisCacheDepend basic functionality.""" - app = test_client_with_redis.app - - @app.get("/cached/{param}") - async def cached_operation( - request: Request, - response:Response, - result=RedisCacheDepend("expensive:test", ttl=600) - ): - return {"result": result} - - # Test with cached value - mock_redis.get.return_value = "cached_result" - response = test_client_with_redis.get("/cached/test") - assert response.status_code == 200 - assert response.json() == {"result": "cached_result"} - mock_redis.get.assert_called_with("expensive:test") - - # Test without cached value - mock_redis.get.return_value = None - response = test_client_with_redis.get("/cached/test") - assert response.status_code == 200 - assert response.json() == {"result": "cached_expensive:test"} - - def test_multiple_redis_dependencies(self, test_client_with_redis, mock_redis): - """Test multiple Redis dependencies in single route.""" - app = test_client_with_redis.app - - @app.get("/multi-deps") - async def multi_deps( - request, - response, - redis=RedisDepend(), - counter_value=RedisOperationDepend("get", "counter"), - user_exists=RedisKeyDepend("user:123", "exists", 0), - cached_data=RedisCacheDepend("cache:data", ttl=300) - ): - return { - "redis_available": redis is not None, - "counter": counter_value, - "user_exists": bool(user_exists), - "cached": cached_data - } - - # Mock Redis responses - mock_redis.get.side_effect = ["5", None] # counter value, then cache miss - mock_redis.exists.return_value = 1 - - response = test_client_with_redis.get("/multi-deps") - assert response.status_code == 200 - data = response.json() - assert data["redis_available"] is True - assert data["counter"] == "5" - assert data["user_exists"] is True - assert data["cached"] == "cached_cache:data" def test_redis_depend_with_context(self, app_with_mock_redis, mock_redis): """Test Redis dependencies with explicit context.""" From 408415400802c13f576091a9d8d47c8f995233fb Mon Sep 17 00:00:00 2001 From: "techwithdunamix@gmail.com" Date: Wed, 1 Apr 2026 19:44:32 +0100 Subject: [PATCH 3/3] refactor(redis): update route handlers to use function arguments --- tests/test_redis/test_redis_dependencies.py | 3 +-- tests/test_redis/test_redis_integration.py | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_redis/test_redis_dependencies.py b/tests/test_redis/test_redis_dependencies.py index fe609e5..c802113 100644 --- a/tests/test_redis/test_redis_dependencies.py +++ b/tests/test_redis/test_redis_dependencies.py @@ -37,8 +37,7 @@ def test_redis_depend_in_route(self, test_client_with_redis, mock_redis): app = test_client_with_redis.app @app.get("/cache/{key}") - async def get_value(request: Request,response, redis=RedisDepend()): - key = request.path_params["key"] + async def get_value(request: Request,response,key, redis=RedisDepend()): value = await redis.get(key) return {"key": key, "value": value} diff --git a/tests/test_redis/test_redis_integration.py b/tests/test_redis/test_redis_integration.py index d4bea4c..91ef480 100644 --- a/tests/test_redis/test_redis_integration.py +++ b/tests/test_redis/test_redis_integration.py @@ -133,8 +133,7 @@ def test_redis_hash_operations_route(self, test_client_with_redis, mock_redis): app = test_client_with_redis.app @app.post("/user/{user_id}/profile") - async def update_profile(request: Request, response,redis=Depend(get_redis)): - user_id = request.path_params["user_id"] + async def update_profile(request: Request, response,user_id,redis=Depend(get_redis)): data = await request.json profile_key = f"profile:{user_id}" @@ -145,8 +144,7 @@ async def update_profile(request: Request, response,redis=Depend(get_redis)): return {"user_id": user_id, "status": "updated"} @app.get("/user/{user_id}/profile") - async def get_profile(request: Request, response,redis=Depend(get_redis)): - user_id = request.path_params["user_id"] + async def get_profile(request: Request, response,user_id,redis=Depend(get_redis)): profile_key = f"profile:{user_id}" profile = await redis.hgetall(profile_key)