@@ -102,79 +102,233 @@ Full benchmarks available in `tests/benchmark.py`.
102102
103103## API Reference
104104
105+ ### Key templates & custom keys
106+
107+ All caching decorators share the same key concept:
108+
109+ - ` key: str ` – String template or literal
110+ - ` key: Callable[..., str] ` – Function that returns a string key
111+
112+ Supported patterns:
113+
114+ 1 . ** Positional placeholder** – first positional argument:
115+
116+ ``` python
117+ @TTLCache.cached (" user:{} " , ttl = 60 )
118+ def get_user (user_id : int ):
119+ ...
120+
121+ get_user(42 ) # key -> "user:42"
122+ ```
123+
124+ 2 . ** Named placeholder** – keyword arguments by name:
125+
126+ ``` python
127+ @TTLCache.cached (" user:{user_id} " , ttl = 60 )
128+ def get_user (* , user_id : int ):
129+ ...
130+
131+ get_user(user_id = 42 ) # key -> "user:42"
132+ ```
133+
134+ 3 . ** Named with extra kwargs** – only the named part is used for the key:
135+
136+ ``` python
137+ @SWRCache.cached (" i18n:{lang} " , ttl = 60 , stale_ttl = 30 )
138+ def load_i18n (lang : str , region : str | None = None ):
139+ ...
140+
141+ load_i18n(lang = " en" , region = " US" ) # key -> "i18n:en"
142+ ```
143+
144+ 4 . ** Default arguments + robust key lambda** – recommended for complex/default cases:
145+
146+ ``` python
147+ @SWRCache.cached (
148+ key = lambda * a , ** k : f " i18n:all: { k.get(' lang' , a[0 ] if a else ' en' )} " ,
149+ ttl = 60 ,
150+ stale_ttl = 30 ,
151+ )
152+ def load_all (lang : str = " en" ) -> dict :
153+ print (f " Loading i18n for { lang} " )
154+ return {" hello" : f " Hello in { lang} " }
155+
156+ load_all() # key -> "i18n:all:en"
157+ load_all(" en" ) # key -> "i18n:all:en"
158+ load_all(lang = " en" ) # key -> "i18n:all:en"
159+ # Body runs once, subsequent calls are cached
160+ ```
161+
162+ ---
163+
105164### TTLCache.cached(key, ttl, cache=None)
106165Simple time-based cache with configurable TTL.
107166
167+ ** Signature:**
168+ ``` python
169+ TTLCache.cached(
170+ key: str | Callable[... , str ],
171+ ttl: int ,
172+ cache: CacheStorage | None = None ,
173+ ) -> Callable
174+ ```
175+
108176** Parameters:**
109- - ` key ` (str | callable): Cache key or function. String ` "user:{}" ` formats with first arg
177+ - ` key ` (str | callable): Cache key template or generator function
110178- ` ttl ` (int): Time-to-live in seconds
111179- ` cache ` (CacheStorage): Optional custom backend (defaults to InMemCache)
112180
113- ** Example:**
181+ ** Examples:**
182+
183+ Positional key:
114184``` python
115- @TTLCache.cached (" user:{} " , ttl = 300 )
116- def get_user (user_id ):
185+ @TTLCache.cached (" user:{} , " , ttl = 300 )
186+ def get_user (user_id : int ):
117187 return db.fetch(user_id)
118188
119- # Custom backend
120- @TTLCache.cached (" data:{} " , ttl = 60 , cache = redis_cache)
121- def get_data (data_id ):
122- return api.fetch(data_id)
189+ get_user(42 ) # key -> "user:42"
190+ ```
191+
192+ Named key:
193+ ``` python
194+ @TTLCache.cached (" user:{user_id} " , ttl = 300 )
195+ def get_user (* , user_id : int ):
196+ return db.fetch(user_id)
197+
198+ get_user(user_id = 42 ) # key -> "user:42"
199+ ```
200+
201+ Custom key function:
202+ ``` python
203+ @TTLCache.cached (key = lambda * a , ** k : f " user: { k.get(' user_id' , a[0 ])} " , ttl = 300 )
204+ def get_user (user_id : int = 0 ):
205+ return db.fetch(user_id)
123206```
124207
125208---
126209
127210### SWRCache.cached(key, ttl, stale_ttl=0, cache=None, enable_lock=True)
128211Serve stale data instantly while refreshing in background.
129212
213+ ** Signature:**
214+ ``` python
215+ SWRCache.cached(
216+ key: str | Callable[... , str ],
217+ ttl: int ,
218+ stale_ttl: int = 0 ,
219+ cache: CacheStorage | None = None ,
220+ enable_lock: bool = True ,
221+ ) -> Callable
222+ ```
223+
130224** Parameters:**
131- - ` key ` (str | callable): Cache key (same format as TTLCache)
225+ - ` key ` (str | callable): Cache key (same patterns as TTLCache)
132226- ` ttl ` (int): Fresh data TTL in seconds
133227- ` stale_ttl ` (int): Grace period to serve stale data while refreshing
134228- ` cache ` (CacheStorage): Optional custom backend
135229- ` enable_lock ` (bool): Prevent thundering herd (default: True)
136230
137- ** Example:**
231+ ** Examples:**
232+
233+ Basic SWR with positional key:
138234``` python
139235@SWRCache.cached (" product:{} " , ttl = 60 , stale_ttl = 30 )
140- def get_product (product_id ):
236+ def get_product (product_id : int ):
141237 return api.fetch_product(product_id)
142238
143- # Returns immediately with fresh or stale data
144- # Never blocks on refresh
239+ get_product(1 ) # key -> "product:1"
240+ ```
241+
242+ Named key with kwargs:
243+ ``` python
244+ @SWRCache.cached (" i18n:{lang} " , ttl = 60 , stale_ttl = 30 )
245+ def load_i18n (* , lang : str = " en" ) -> dict :
246+ return {" hello" : f " Hello in { lang} " }
247+
248+ load_i18n(lang = " en" ) # key -> "i18n:en"
249+ ```
250+
251+ Default arg + key lambda (robust):
252+ ``` python
253+ @SWRCache.cached (
254+ key = lambda * a , ** k : f " i18n:all: { k.get(' lang' , a[0 ] if a else ' en' )} " ,
255+ ttl = 60 ,
256+ stale_ttl = 30 ,
257+ )
258+ def load_all (lang : str = " en" ) -> dict :
259+ return {" hello" : f " Hello in { lang} " }
145260```
146261
147262---
148263
149- ### BGCache.register_loader(cache_key , interval_seconds, ttl_seconds =None, run_immediately=True, on_error=None, cache=None)
264+ ### BGCache.register_loader(key , interval_seconds, ttl =None, run_immediately=True, on_error=None, cache=None)
150265Pre-load expensive data with periodic refresh.
151266
267+ ** Signature:**
268+ ``` python
269+ BGCache.register_loader(
270+ key: str ,
271+ interval_seconds: int ,
272+ ttl: int | None = None ,
273+ run_immediately: bool = True ,
274+ on_error: Callable[[Exception ], None ] | None = None ,
275+ cache: CacheStorage | None = None ,
276+ ) -> Callable
277+ ```
278+
152279** Parameters:**
153- - ` cache_key ` (str): Unique cache key (no formatting)
280+ - ` key ` (str): Unique cache key (no formatting, fixed string )
154281- ` interval_seconds ` (int): Refresh interval in seconds
155- - ` ttl_seconds ` (int): Cache TTL (defaults to interval_seconds × 2 )
156- - ` run_immediately ` (bool): Load on registration (default: True)
157- - ` on_error ` (callable): Error handler function
282+ - ` ttl ` (int | None ): Cache TTL (defaults to 2 × interval_seconds when None )
283+ - ` run_immediately ` (bool): Load once at registration (default: True)
284+ - ` on_error ` (callable): Error handler function ` (Exception) -> None `
158285- ` cache ` (CacheStorage): Optional custom backend
159286
160- ** Example:**
287+ ** Examples:**
288+
289+ Sync loader:
161290``` python
162- @BGCache.register_loader (" inventory" , interval_seconds = 300 )
163- def load_inventory ():
164- return warehouse_api.get_items()
291+ from advanced_caching import BGCache
165292
166- # Async support
167- @BGCache.register_loader (" products" , interval_seconds = 300 )
168- async def load_products ():
293+ @BGCache.register_loader (key = " inventory" , interval_seconds = 300 , ttl = 900 )
294+ def load_inventory () -> list[dict ]:
295+ return warehouse_api.get_all_items()
296+
297+ # Later
298+ items = load_inventory() # instant access to cached data
299+ ```
300+
301+ Async loader:
302+ ``` python
303+ @BGCache.register_loader (key = " products" , interval_seconds = 300 , ttl = 900 )
304+ async def load_products () -> list[dict ]:
169305 return await api.fetch_products()
170306
171- # With error handling
172- @BGCache.register_loader (" config" , interval_seconds = 60 , on_error = logger.error)
173- def load_config ():
174- return settings.fetch()
307+ products = await load_products() # returns cached list
308+ ```
175309
176- # Shutdown scheduler when done
177- BGCache.shutdown()
310+ With error handling:
311+ ``` python
312+ errors: list[Exception ] = []
313+
314+ def on_error (exc : Exception ) -> None :
315+ errors.append(exc)
316+
317+ @BGCache.register_loader (
318+ key = " unstable" ,
319+ interval_seconds = 60 ,
320+ run_immediately = True ,
321+ on_error = on_error,
322+ )
323+ def maybe_fails () -> dict :
324+ raise RuntimeError (" boom" )
325+
326+ # errors list will contain the exception from background job
327+ ```
328+
329+ Shutdown scheduler when done:
330+ ``` python
331+ BGCache.shutdown(wait = True )
178332```
179333
180334---
@@ -456,4 +610,3 @@ MIT License – See [LICENSE](LICENSE) for details.
456610
457611---
458612
459-
0 commit comments