Skip to content

Commit 5ee9e09

Browse files
authored
✨ Feature: support custom redis key prefix for cache (#193)
1 parent 14d6932 commit 5ee9e09

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

docs/usage/configuration.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,13 @@ Available built-in cache strategies:
9999

100100
github = GitHub(
101101
cache_strategy=RedisCacheStrategy(
102-
client=Redis(host="localhost", port=6379)
102+
client=Redis(host="localhost", port=6379), prefix="githubkit:"
103103
)
104104
)
105105
```
106106

107+
The `prefix` option is used to set the key prefix in Redis. You should add a `:` at the end of the prefix if you want to use namespace like key format. Both `githubkit` and `hishel` will use the prefix to store the cache data.
108+
107109
Note that using this sync only cache strategy will cause the `GitHub` instance to be sync only.
108110

109111
- `AsyncRedisCacheStrategy`: Cache the data in Redis (Async only).
@@ -115,11 +117,13 @@ Available built-in cache strategies:
115117

116118
github = GitHub(
117119
cache_strategy=AsyncRedisCacheStrategy(
118-
client=Redis(host="localhost", port=6379)
120+
client=Redis(host="localhost", port=6379), prefix="githubkit:"
119121
)
120122
)
121123
```
122124

125+
The `prefix` option is used to set the key prefix in Redis. You should add a `:` at the end of the prefix if you want to use namespace like key format. Both `githubkit` and `hishel` will use the prefix to store the cache data.
126+
123127
Note that using this async only cache strategy will cause the `GitHub` instance to be async only.
124128

125129
### `http_cache`

githubkit/cache/base.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import timedelta
33
from typing import Optional
44

5-
from hishel import AsyncBaseStorage, BaseStorage
5+
from hishel import AsyncBaseStorage, BaseStorage, Controller
66

77

88
class BaseCache(abc.ABC):
@@ -28,16 +28,33 @@ async def aset(self, key: str, value: str, ex: timedelta) -> None:
2828
class BaseCacheStrategy(abc.ABC):
2929
@abc.abstractmethod
3030
def get_cache_storage(self) -> BaseCache:
31+
"""Get the cache storage instance used in sync context
32+
33+
raise CacheUnsupportedError if the strategy does not support sync usage
34+
"""
3135
raise NotImplementedError
3236

3337
@abc.abstractmethod
3438
def get_async_cache_storage(self) -> AsyncBaseCache:
39+
"""Get the cache storage instance used in async context
40+
41+
raise CacheUnsupportedError if the strategy does not support async usage
42+
"""
3543
raise NotImplementedError
3644

45+
def get_hishel_controller(self) -> Optional[Controller]:
46+
"""Get the hishel controller instance
47+
48+
Return `None` to use the default controller
49+
"""
50+
return None
51+
3752
@abc.abstractmethod
3853
def get_hishel_storage(self) -> BaseStorage:
54+
"""Get the hishel storage instance used in sync context"""
3955
raise NotImplementedError
4056

4157
@abc.abstractmethod
4258
def get_async_hishel_storage(self) -> AsyncBaseStorage:
59+
"""Get the hishel storage instance used in async context"""
4360
raise NotImplementedError

githubkit/cache/redis.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from datetime import timedelta
2+
from functools import partial
23
from typing import TYPE_CHECKING, Any, NoReturn, Optional
34
from typing_extensions import override
45

5-
from hishel import AsyncBaseStorage, AsyncRedisStorage, RedisStorage
6+
from hishel import AsyncBaseStorage, AsyncRedisStorage, Controller, RedisStorage
67

78
from githubkit.exception import CacheUnsupportedError
9+
from githubkit.utils import hishel_key_generator_with_prefix
810

911
from .base import AsyncBaseCache, BaseCache, BaseCacheStrategy
1012

@@ -25,47 +27,69 @@ def _ensure_str_or_none(value: Any) -> Optional[str]:
2527

2628

2729
class RedisCache(BaseCache):
28-
def __init__(self, client: "Redis") -> None:
30+
def __init__(self, client: "Redis", prefix: Optional[str] = None) -> None:
2931
self.client = client
32+
self.prefix = prefix
33+
34+
def _get_key(self, key: str) -> str:
35+
if self.prefix is not None:
36+
return f"{self.prefix}{key}"
37+
return key
3038

3139
@override
3240
def get(self, key: str) -> Optional[str]:
33-
data = self.client.get(key)
41+
data = self.client.get(self._get_key(key))
3442
return _ensure_str_or_none(data)
3543

3644
@override
3745
def set(self, key: str, value: str, ex: timedelta) -> None:
38-
self.client.set(key, value, ex)
46+
self.client.set(self._get_key(key), value, ex)
3947

4048

4149
class AsyncRedisCache(AsyncBaseCache):
42-
def __init__(self, client: "AsyncRedis") -> None:
50+
def __init__(self, client: "AsyncRedis", prefix: Optional[str] = None) -> None:
4351
self.client = client
52+
self.prefix = prefix
53+
54+
def _get_key(self, key: str) -> str:
55+
if self.prefix is not None:
56+
return f"{self.prefix}{key}"
57+
return key
4458

4559
@override
4660
async def aget(self, key: str) -> Optional[str]:
47-
data = await self.client.get(key)
61+
data = await self.client.get(self._get_key(key))
4862
return _ensure_str_or_none(data)
4963

5064
@override
5165
async def aset(self, key: str, value: str, ex: timedelta) -> None:
52-
await self.client.set(key, value, ex)
66+
await self.client.set(self._get_key(key), value, ex)
5367

5468

5569
class RedisCacheStrategy(BaseCacheStrategy):
56-
def __init__(self, client: "Redis") -> None:
70+
def __init__(self, client: "Redis", prefix: Optional[str] = None) -> None:
5771
self.client = client
72+
self.prefix = prefix
5873

5974
@override
6075
def get_cache_storage(self) -> RedisCache:
61-
return RedisCache(self.client)
76+
return RedisCache(self.client, self.prefix)
6277

6378
@override
6479
def get_async_cache_storage(self) -> NoReturn:
6580
raise CacheUnsupportedError(
6681
"Sync redis cache strategy does not support async usage"
6782
)
6883

84+
@override
85+
def get_hishel_controller(self) -> Optional[Controller]:
86+
if self.prefix is not None:
87+
return Controller(
88+
key_generator=partial(
89+
hishel_key_generator_with_prefix, prefix=self.prefix
90+
)
91+
)
92+
6993
@override
7094
def get_hishel_storage(self) -> RedisStorage:
7195
return RedisStorage(client=self.client)
@@ -78,8 +102,9 @@ def get_async_hishel_storage(self) -> NoReturn:
78102

79103

80104
class AsyncRedisCacheStrategy(BaseCacheStrategy):
81-
def __init__(self, client: "AsyncRedis") -> None:
105+
def __init__(self, client: "AsyncRedis", prefix: Optional[str] = None) -> None:
82106
self.client = client
107+
self.prefix = prefix
83108

84109
@override
85110
def get_cache_storage(self) -> NoReturn:
@@ -89,7 +114,7 @@ def get_cache_storage(self) -> NoReturn:
89114

90115
@override
91116
def get_async_cache_storage(self) -> AsyncRedisCache:
92-
return AsyncRedisCache(self.client)
117+
return AsyncRedisCache(self.client, self.prefix)
93118

94119
@override
95120
def get_hishel_storage(self) -> NoReturn:

githubkit/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def _create_sync_client(self) -> httpx.Client:
220220
transport = hishel.CacheTransport(
221221
httpx.HTTPTransport(),
222222
storage=self.config.cache_strategy.get_hishel_storage(),
223+
controller=self.config.cache_strategy.get_hishel_controller(),
223224
)
224225
else:
225226
transport = httpx.HTTPTransport()
@@ -244,6 +245,7 @@ def _create_async_client(self) -> httpx.AsyncClient:
244245
transport = hishel.AsyncCacheTransport(
245246
httpx.AsyncHTTPTransport(),
246247
storage=self.config.cache_strategy.get_async_hishel_storage(),
248+
controller=self.config.cache_strategy.get_hishel_controller(),
247249
)
248250
else:
249251
transport = httpx.AsyncHTTPTransport()

githubkit/utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from enum import Enum
22
import inspect
3-
from typing import Any, Generic, Literal, TypeVar, final
3+
from typing import Any, Generic, Literal, Optional, TypeVar, final
44

5+
from hishel._utils import generate_key
6+
import httpcore
57
from pydantic import BaseModel
68

79
from .compat import PYDANTIC_V2, custom_validation, type_validate_python
@@ -97,3 +99,9 @@ def __get_pydantic_core_schema__(
9799
),
98100
),
99101
)
102+
103+
104+
def hishel_key_generator_with_prefix(
105+
request: httpcore.Request, body: Optional[bytes], prefix: str = ""
106+
) -> str:
107+
return prefix + generate_key(request, b"" if body is None else body)

0 commit comments

Comments
 (0)