Kekik 1.7.0__py3-none-any.whl → 1.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Kekik/cache.py +270 -101
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/METADATA +1 -1
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/RECORD +7 -7
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/LICENSE +0 -0
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/WHEEL +0 -0
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/entry_points.txt +0 -0
- {Kekik-1.7.0.dist-info → Kekik-1.7.2.dist-info}/top_level.txt +0 -0
Kekik/cache.py
CHANGED
|
@@ -1,65 +1,173 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from .cli import konsol
|
|
4
4
|
from functools import wraps
|
|
5
|
-
from time import time
|
|
6
5
|
from hashlib import md5
|
|
7
6
|
from urllib.parse import urlencode
|
|
7
|
+
import time
|
|
8
|
+
import threading
|
|
9
|
+
import asyncio
|
|
10
|
+
import pickle
|
|
8
11
|
|
|
12
|
+
# Redis için asenkron client (Redis yoksa fallback yapılacak)
|
|
13
|
+
import redis.asyncio as redis
|
|
14
|
+
|
|
15
|
+
# -----------------------------------------------------
|
|
16
|
+
# Sabitler ve Yardımcı Fonksiyonlar
|
|
17
|
+
# -----------------------------------------------------
|
|
9
18
|
UNLIMITED = None
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
def normalize_for_key(value):
|
|
12
21
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
Cache key oluşturma amacıyla verilen değeri normalize eder.
|
|
23
|
+
- Basit tipler (int, float, str, bool, None) doğrudan kullanılır.
|
|
24
|
+
- dict: Anahtarları sıralı olarak normalize eder.
|
|
25
|
+
- list/tuple: Elemanları normalize eder.
|
|
26
|
+
- Diğer: Sadece sınıf ismi kullanılır.
|
|
15
27
|
"""
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
if isinstance(value, (int, float, str, bool, type(None))):
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
elif isinstance(value, dict):
|
|
32
|
+
return {k: normalize_for_key(value[k]) for k in sorted(value)}
|
|
33
|
+
|
|
34
|
+
elif isinstance(value, (list, tuple)):
|
|
35
|
+
return [normalize_for_key(item) for item in value]
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
return value.__class__.__name__
|
|
39
|
+
|
|
40
|
+
def simple_cache_key(func, args, kwargs) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
|
|
43
|
+
Oluşturulan stringin sonuna MD5 hash eklenebilir.
|
|
44
|
+
"""
|
|
45
|
+
base_key = f"{func.__module__}.{func.__qualname__}"
|
|
46
|
+
|
|
47
|
+
if args:
|
|
48
|
+
norm_args = [normalize_for_key(arg) for arg in args]
|
|
49
|
+
base_key += f"|{norm_args}"
|
|
50
|
+
|
|
51
|
+
if kwargs:
|
|
52
|
+
norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
|
|
53
|
+
base_key += f"|{str(sorted(norm_kwargs.items()))}"
|
|
54
|
+
|
|
55
|
+
hashed = md5(base_key.encode('utf-8')).hexdigest()
|
|
56
|
+
return f"{base_key}" # |{hashed}
|
|
57
|
+
|
|
58
|
+
async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Cache key'ini oluşturur.
|
|
61
|
+
- is_fastapi=False ise simple_cache_key() kullanılır.
|
|
62
|
+
- True ise FastAPI Request nesnesine göre özel key oluşturulur.
|
|
63
|
+
"""
|
|
64
|
+
if not is_fastapi:
|
|
65
|
+
return simple_cache_key(func, args, kwargs)
|
|
66
|
+
|
|
67
|
+
# FastAPI: request ilk argümandan ya da kwargs'dan alınır.
|
|
68
|
+
request = args[0] if args else kwargs.get("request")
|
|
69
|
+
|
|
70
|
+
if request.method == "GET":
|
|
71
|
+
# Eğer query_params boşsa {} olarak ayarla
|
|
72
|
+
veri = dict(request.query_params) if request.query_params else {}
|
|
73
|
+
else:
|
|
74
|
+
try:
|
|
75
|
+
veri = await request.json()
|
|
76
|
+
except Exception:
|
|
77
|
+
form_data = await request.form()
|
|
78
|
+
veri = dict(form_data.items())
|
|
79
|
+
|
|
80
|
+
# Eğer "kurek" gibi özel parametreler varsa temizleyebilirsiniz:
|
|
81
|
+
veri.pop("kurek", None)
|
|
82
|
+
|
|
83
|
+
args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
|
|
84
|
+
return f"{request.url.path}?{veri}"
|
|
85
|
+
|
|
86
|
+
# -----------------------------------------------------
|
|
87
|
+
# Senkron Cache (RAM) Sınıfı
|
|
88
|
+
# -----------------------------------------------------
|
|
89
|
+
|
|
90
|
+
class SyncCache:
|
|
91
|
+
"""
|
|
92
|
+
Senkron fonksiyonlar için basit in-memory cache.
|
|
93
|
+
TTL süresi dolan veriler periyodik olarak arka plan thread’iyle temizlenir.
|
|
94
|
+
"""
|
|
95
|
+
def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
|
|
18
96
|
self._ttl = ttl
|
|
97
|
+
self._data = {}
|
|
19
98
|
self._times = {}
|
|
20
99
|
|
|
100
|
+
# TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
|
|
101
|
+
self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
|
|
102
|
+
|
|
103
|
+
# Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
|
|
104
|
+
self._lock = threading.RLock()
|
|
105
|
+
self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
|
|
106
|
+
self._cleanup_thread.start()
|
|
107
|
+
|
|
108
|
+
def _auto_cleanup(self):
|
|
109
|
+
"""Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
|
|
110
|
+
while True:
|
|
111
|
+
time.sleep(self._cleanup_interval)
|
|
112
|
+
with self._lock:
|
|
113
|
+
keys = list(self._data.keys())
|
|
114
|
+
for key in keys:
|
|
115
|
+
self.remove_if_expired(key)
|
|
116
|
+
|
|
21
117
|
def _is_expired(self, key):
|
|
22
|
-
"""Belirtilen key'in süresi
|
|
118
|
+
"""Belirtilen key'in süresi dolmuşsa True döner."""
|
|
23
119
|
if self._ttl is UNLIMITED:
|
|
24
120
|
return False
|
|
25
121
|
|
|
26
122
|
timestamp = self._times.get(key)
|
|
27
123
|
|
|
28
|
-
return timestamp is not None and (time() - timestamp > self._ttl)
|
|
124
|
+
return timestamp is not None and (time.time() - timestamp > self._ttl)
|
|
29
125
|
|
|
30
126
|
def remove_if_expired(self, key):
|
|
31
127
|
"""
|
|
32
128
|
Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
|
|
129
|
+
Thread güvenliği sağlamak için lock kullanılır.
|
|
33
130
|
"""
|
|
34
|
-
|
|
35
|
-
self.
|
|
36
|
-
|
|
131
|
+
with self._lock:
|
|
132
|
+
if self._is_expired(key):
|
|
133
|
+
self._data.pop(key, None)
|
|
134
|
+
self._times.pop(key, None)
|
|
135
|
+
# konsol.log(f"[red][-] {key}")
|
|
37
136
|
|
|
38
137
|
def __getitem__(self, key):
|
|
39
|
-
self.
|
|
40
|
-
|
|
138
|
+
with self._lock:
|
|
139
|
+
self.remove_if_expired(key)
|
|
140
|
+
veri = self._data[key]
|
|
141
|
+
# konsol.log(f"[yellow][~] {key}")
|
|
142
|
+
return veri
|
|
41
143
|
|
|
42
144
|
def __setitem__(self, key, value):
|
|
43
|
-
self.
|
|
44
|
-
|
|
45
|
-
self.
|
|
145
|
+
with self._lock:
|
|
146
|
+
self._data[key] = value
|
|
147
|
+
if self._ttl is not UNLIMITED:
|
|
148
|
+
self._times[key] = time.time()
|
|
46
149
|
|
|
150
|
+
# -----------------------------------------------------
|
|
151
|
+
# Asenkron Cache (In-Memory) ve Redis Hybrid Cache
|
|
152
|
+
# -----------------------------------------------------
|
|
47
153
|
|
|
48
|
-
class AsyncCache
|
|
154
|
+
class AsyncCache:
|
|
49
155
|
"""
|
|
50
|
-
|
|
51
|
-
Aynı key için gelen eşzamanlı çağrılar, futures kullanılarak tek sonuç üzerinden paylaşılır.
|
|
52
|
-
Ek olarak, belirli aralıklarla cache’i kontrol edip, süresi dolmuş verileri temizleyen otomatik temizleme görevi çalışır.
|
|
156
|
+
Temel in-memory asenkron cache.
|
|
53
157
|
"""
|
|
54
158
|
def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
|
|
55
159
|
"""
|
|
56
160
|
:param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
|
|
57
161
|
:param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
|
|
58
162
|
"""
|
|
59
|
-
|
|
163
|
+
self._ttl = ttl
|
|
164
|
+
self._data = {}
|
|
165
|
+
self._times = {}
|
|
60
166
|
self.futures = {}
|
|
61
167
|
|
|
168
|
+
# TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
|
|
62
169
|
self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
|
|
170
|
+
|
|
63
171
|
# Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
|
|
64
172
|
try:
|
|
65
173
|
self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
|
|
@@ -70,9 +178,7 @@ class AsyncCache(Cache):
|
|
|
70
178
|
"""Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
|
|
71
179
|
while True:
|
|
72
180
|
await asyncio.sleep(self._cleanup_interval)
|
|
73
|
-
|
|
74
|
-
keys = list(self._data.keys())
|
|
75
|
-
for key in keys:
|
|
181
|
+
for key in list(self._data.keys()):
|
|
76
182
|
self.remove_if_expired(key)
|
|
77
183
|
|
|
78
184
|
def ensure_cleanup_task(self):
|
|
@@ -83,45 +189,137 @@ class AsyncCache(Cache):
|
|
|
83
189
|
except RuntimeError:
|
|
84
190
|
pass # Yine loop yoksa yapacak bir şey yok
|
|
85
191
|
|
|
192
|
+
def remove_if_expired(self, key):
|
|
193
|
+
"""
|
|
194
|
+
Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
|
|
195
|
+
"""
|
|
196
|
+
if self._ttl is not UNLIMITED:
|
|
197
|
+
t = self._times.get(key)
|
|
198
|
+
if t is not None and (time.time() - t > self._ttl):
|
|
199
|
+
# konsol.log(f"[red][-] {key}")
|
|
200
|
+
self._data.pop(key, None)
|
|
201
|
+
self._times.pop(key, None)
|
|
202
|
+
self.futures.pop(key, None)
|
|
203
|
+
|
|
86
204
|
async def get(self, key):
|
|
87
205
|
"""
|
|
88
206
|
Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
|
|
89
207
|
Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
|
|
90
208
|
"""
|
|
209
|
+
# Cleanup task'in aktif olduğundan emin olun.
|
|
91
210
|
self.ensure_cleanup_task()
|
|
211
|
+
# Eğer key'in süresi dolmuşsa, kaldırın.
|
|
92
212
|
self.remove_if_expired(key)
|
|
93
213
|
|
|
94
214
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
215
|
+
# Cache içerisinde key varsa direkt değeri döndür.
|
|
216
|
+
value = self._data[key]
|
|
217
|
+
# konsol.log(f"[yellow][~] {key}")
|
|
218
|
+
return value
|
|
219
|
+
except KeyError:
|
|
220
|
+
# Eğer key cache'de yoksa, aynı key ile başlatılmış future varsa onu bekle.
|
|
97
221
|
future = self.futures.get(key)
|
|
98
222
|
if future:
|
|
99
223
|
await future
|
|
100
|
-
|
|
224
|
+
# Future tamamlandığında sonucu döndür.
|
|
225
|
+
value = future.result()
|
|
226
|
+
# konsol.log(f"[yellow][?] {key}")
|
|
227
|
+
return value
|
|
228
|
+
# Eğer future da yoksa, key bulunamadığına dair hata fırlat.
|
|
229
|
+
raise KeyError(key)
|
|
230
|
+
|
|
231
|
+
async def set(self, key, value):
|
|
232
|
+
"""Belirtilen key için cache'e değer ekler."""
|
|
233
|
+
self.ensure_cleanup_task()
|
|
234
|
+
self._data[key] = value
|
|
235
|
+
if self._ttl is not UNLIMITED:
|
|
236
|
+
self._times[key] = time.time()
|
|
101
237
|
|
|
102
|
-
|
|
238
|
+
class HybridAsyncCache:
|
|
239
|
+
"""
|
|
240
|
+
Öncelikle Redis cache kullanılmaya çalışılır.
|
|
241
|
+
Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCache’e geçilir.
|
|
242
|
+
"""
|
|
243
|
+
def __init__(self, ttl=UNLIMITED):
|
|
244
|
+
self._ttl = ttl
|
|
103
245
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.
|
|
246
|
+
try:
|
|
247
|
+
self.redis = redis.Redis(
|
|
248
|
+
host = "127.0.0.1",
|
|
249
|
+
port = 6379,
|
|
250
|
+
decode_responses = False
|
|
251
|
+
)
|
|
252
|
+
except Exception:
|
|
253
|
+
self.redis = None
|
|
254
|
+
|
|
255
|
+
self.memory = AsyncCache(ttl)
|
|
256
|
+
self.futures = {}
|
|
257
|
+
|
|
258
|
+
async def get(self, key):
|
|
259
|
+
# Eşzamanlı istek yönetimi: aynı key için bir future varsa, direkt bekleyip sonucu döndür.
|
|
260
|
+
if key in self.futures:
|
|
261
|
+
# konsol.log(f"[yellow][?] {key}")
|
|
262
|
+
return await self.futures[key]
|
|
112
263
|
|
|
264
|
+
# İlk önce Redis ile deneyelim
|
|
265
|
+
if self.redis:
|
|
266
|
+
try:
|
|
267
|
+
data = await self.redis.get(key)
|
|
268
|
+
except Exception:
|
|
269
|
+
return await self.memory.get(key)
|
|
270
|
+
if data is not None:
|
|
271
|
+
try:
|
|
272
|
+
result = pickle.loads(data)
|
|
273
|
+
# konsol.log(f"[yellow][~] {key}")
|
|
274
|
+
return result
|
|
275
|
+
except Exception:
|
|
276
|
+
# Deserialize hatası durumunda in-memory cache'ten dene
|
|
277
|
+
return await self.memory.get(key)
|
|
278
|
+
else:
|
|
279
|
+
# Redis'te veri yoksa, in-memory cache'e bak
|
|
280
|
+
return await self.memory.get(key)
|
|
281
|
+
else:
|
|
282
|
+
# Redis kullanılmıyorsa doğrudan in-memory cache'e bak
|
|
283
|
+
return await self.memory.get(key)
|
|
284
|
+
|
|
285
|
+
async def set(self, key, value):
|
|
286
|
+
# Önce veriyi pickle etmeyi deniyoruz.
|
|
287
|
+
try:
|
|
288
|
+
ser = pickle.dumps(value)
|
|
289
|
+
except Exception:
|
|
290
|
+
# Serialization hatası durumunda sadece in-memory cache'e yaz
|
|
291
|
+
await self.memory.set(key, value)
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
if self.redis:
|
|
295
|
+
try:
|
|
296
|
+
if self._ttl is not UNLIMITED:
|
|
297
|
+
await self.redis.set(key, ser, ex=self._ttl)
|
|
298
|
+
else:
|
|
299
|
+
await self.redis.set(key, ser)
|
|
300
|
+
return
|
|
301
|
+
except Exception:
|
|
302
|
+
# Redis yazma hatası durumunda in-memory fallback
|
|
303
|
+
await self.memory.set(key, value)
|
|
304
|
+
return
|
|
305
|
+
else:
|
|
306
|
+
# Redis yoksa in-memory cache'e yaz
|
|
307
|
+
await self.memory.set(key, value)
|
|
308
|
+
|
|
309
|
+
# -----------------------------------------------------
|
|
310
|
+
# Cache'e Hesaplanmış Sonucu Yazma Yardımcı Fonksiyonları
|
|
311
|
+
# -----------------------------------------------------
|
|
113
312
|
|
|
114
313
|
def _sync_maybe_cache(func, key, result, unless):
|
|
115
|
-
"""Senkron
|
|
314
|
+
"""Senkron fonksiyon sonucu için cache kaydı oluşturur."""
|
|
116
315
|
if unless is None or not unless(result):
|
|
117
316
|
func.__cache[key] = result
|
|
118
|
-
|
|
317
|
+
# konsol.log(f"[green][+] {key}")
|
|
119
318
|
|
|
120
319
|
async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
121
320
|
"""
|
|
122
|
-
Asenkron fonksiyon sonucunu hesaplar ve cache'e
|
|
123
|
-
Aynı key için
|
|
124
|
-
Sonuç, unless(result) True değilse cache'e eklenir.
|
|
321
|
+
Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
|
|
322
|
+
Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
|
|
125
323
|
"""
|
|
126
324
|
# __cache'den cache nesnesini alıyoruz.
|
|
127
325
|
cache = func.__cache
|
|
@@ -138,11 +336,12 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
|
138
336
|
# Asenkron fonksiyonu çalıştır ve sonucu elde et.
|
|
139
337
|
result = await func(*args, **kwargs)
|
|
140
338
|
future.set_result(result)
|
|
141
|
-
|
|
339
|
+
|
|
142
340
|
# unless koşuluna göre cache'e ekleme yap.
|
|
143
341
|
if unless is None or not unless(result):
|
|
144
|
-
cache
|
|
145
|
-
|
|
342
|
+
await cache.set(key, result)
|
|
343
|
+
# konsol.log(f"[green][+] {key}")
|
|
344
|
+
|
|
146
345
|
return result
|
|
147
346
|
except Exception as exc:
|
|
148
347
|
future.cancel()
|
|
@@ -151,72 +350,39 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
|
151
350
|
# İşlem tamamlandığında future'ı temizle.
|
|
152
351
|
cache.futures.pop(key, None)
|
|
153
352
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
:param is_fastapi (bool): Eğer True ise, ilk argümanın bir FastAPI Request nesnesi olduğu varsayılır.
|
|
159
|
-
Bu durumda, cache key, request nesnesinin URL yolunu (request.url.path) ve
|
|
160
|
-
isteğe ait verilerden (GET istekleri için query parametreleri; diğer istekler için JSON veya form verileri)
|
|
161
|
-
elde edilen verinin URL uyumlu halinin md5 hash'inin birleşiminden oluşturulur.
|
|
162
|
-
Böylece, aynı URL ve aynı istek verileri için her seferinde aynı cache key üretilecektir.
|
|
163
|
-
Eğer False ise, cache key args ve kwargs değerlerinden, sıralı bir tuple olarak oluşturulur.
|
|
164
|
-
"""
|
|
165
|
-
if not is_fastapi:
|
|
166
|
-
return (args, tuple(sorted(kwargs.items())))
|
|
167
|
-
|
|
168
|
-
request = args[0] if args else kwargs.get("request")
|
|
169
|
-
|
|
170
|
-
if request.method == "GET":
|
|
171
|
-
veri = dict(request.query_params) if request.query_params else None
|
|
172
|
-
else:
|
|
173
|
-
try:
|
|
174
|
-
veri = await request.json()
|
|
175
|
-
except Exception:
|
|
176
|
-
form_data = await request.form()
|
|
177
|
-
veri = dict(form_data.items())
|
|
178
|
-
|
|
179
|
-
args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
|
|
180
|
-
return f"{request.url.path}?{args_hash}"
|
|
181
|
-
|
|
353
|
+
# -----------------------------------------------------
|
|
354
|
+
# kekik_cache Dekoratörü (Senkrondan Asenkrona)
|
|
355
|
+
# -----------------------------------------------------
|
|
182
356
|
|
|
183
|
-
def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
|
|
357
|
+
def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
|
|
184
358
|
"""
|
|
185
|
-
Bir
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
is_fastapi (bool, optional): Eğer `True` ise, cache key'i oluştururken FastAPI request nesnesine özel şekilde davranır.
|
|
193
|
-
Varsayılan olarak `False`'tır.
|
|
194
|
-
|
|
195
|
-
Notlar:
|
|
196
|
-
-------
|
|
197
|
-
Normalde yalnızca Redis cache kullanılmak istenir.
|
|
198
|
-
Ancak Redis'e ulaşılamayan durumlarda RAM fallback kullanılır.
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
Örn.:
|
|
359
|
+
Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
|
|
360
|
+
|
|
361
|
+
Parametreler:
|
|
362
|
+
- ttl: Cache'in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
|
|
363
|
+
- unless: Sonuç alınmadan önce çağrılan, True dönerse cache'e alınmaz.
|
|
364
|
+
- is_fastapi: True ise, FastAPI Request nesnesine göre key oluşturur.
|
|
365
|
+
- use_redis: Asenkron fonksiyonlarda Redis kullanımı (Hybrid cache) için True verilebilir.
|
|
203
366
|
|
|
204
|
-
|
|
367
|
+
Örnek Kullanım:
|
|
368
|
+
|
|
369
|
+
@kekik_cache(ttl=15, unless=lambda sonuc: sonuc is None)
|
|
205
370
|
async def bakalim(param):
|
|
206
|
-
# Burada cache işlemi yapılır.
|
|
207
371
|
return param
|
|
208
372
|
"""
|
|
209
|
-
# Parametresiz
|
|
373
|
+
# Parametresiz kullanım durumunda
|
|
210
374
|
if callable(ttl):
|
|
211
|
-
return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
|
|
375
|
+
return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
|
|
212
376
|
|
|
213
377
|
def decorator(func):
|
|
214
|
-
func.__cache = AsyncCache(ttl)
|
|
215
|
-
|
|
216
378
|
if asyncio.iscoroutinefunction(func):
|
|
379
|
+
# Asenkron fonksiyonlar için cache türünü seçelim:
|
|
380
|
+
|
|
381
|
+
func.__cache = HybridAsyncCache(ttl) if use_redis else AsyncCache(ttl)
|
|
382
|
+
|
|
217
383
|
@wraps(func)
|
|
218
384
|
async def async_wrapper(*args, **kwargs):
|
|
219
|
-
key = await make_cache_key(args, kwargs, is_fastapi)
|
|
385
|
+
key = await make_cache_key(func, args, kwargs, is_fastapi)
|
|
220
386
|
|
|
221
387
|
try:
|
|
222
388
|
return await func.__cache.get(key)
|
|
@@ -225,9 +391,12 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
|
|
|
225
391
|
|
|
226
392
|
return async_wrapper
|
|
227
393
|
else:
|
|
394
|
+
# Senkron fonksiyonlar için
|
|
395
|
+
func.__cache = SyncCache(ttl)
|
|
396
|
+
|
|
228
397
|
@wraps(func)
|
|
229
398
|
def sync_wrapper(*args, **kwargs):
|
|
230
|
-
key = (args,
|
|
399
|
+
key = simple_cache_key(func, args, kwargs)
|
|
231
400
|
|
|
232
401
|
try:
|
|
233
402
|
return func.__cache[key]
|
|
@@ -2,7 +2,7 @@ Kekik/BIST.py,sha256=gFvEFdLGgsxUq33AkqOr5pVGsSm9jR0v6DVwoSzhX7w,1375
|
|
|
2
2
|
Kekik/Domain2IP.py,sha256=dMDatPElTMfLORzC_QN9znex8Se1eid3EPsxAe5S1aQ,4851
|
|
3
3
|
Kekik/Nesne.py,sha256=33pp49cZkRCoa0aUFfzVMpx5FSJchSdiKhm97Nkol1Q,7020
|
|
4
4
|
Kekik/__init__.py,sha256=Iy-O4c_DuY2ttQJv6j7xoVk3IS9EcC0Aqx3vhFvCgd8,1057
|
|
5
|
-
Kekik/cache.py,sha256=
|
|
5
|
+
Kekik/cache.py,sha256=vJ9b7eAyKH0Z2JCW5JIDsyLkNwXvWkS3vnJpv_mj0Us,14892
|
|
6
6
|
Kekik/cli.py,sha256=I--MV8-okBSUYAaV4fRsMeB2n57OJITrgbEnK1OdTPY,3648
|
|
7
7
|
Kekik/csv2dict.py,sha256=HO0AgREXE0yM7cL4OwqpkCGm2pz31RTb0mhnTg6gdwo,423
|
|
8
8
|
Kekik/dict2csv.py,sha256=AcGvEg9i5MXtwMwg7WiDxOj8h9LldNjjcRiWFwA9XJU,560
|
|
@@ -34,9 +34,9 @@ Kekik/kisi_ver/__init__.py,sha256=gH613YZC3ziE7f67dViefRSuwDwsHyVvXHpM5CzZ2q4,13
|
|
|
34
34
|
Kekik/kisi_ver/biyografiler.py,sha256=5Xv1ixaDGHGtl5Nf92jo9dPgF3jDXOGEPmWgoEsn9j8,189486
|
|
35
35
|
Kekik/kisi_ver/isimler.py,sha256=zHVimWL_4TvoLE3qzWQslDBc8-IJZSB02s0vRwsVM1g,88066
|
|
36
36
|
Kekik/kisi_ver/soyisimler.py,sha256=YQJYp2SjENgwOaCa9mmShxPYeeUll2cq8Vox-d8_kB8,78485
|
|
37
|
-
Kekik-1.7.
|
|
38
|
-
Kekik-1.7.
|
|
39
|
-
Kekik-1.7.
|
|
40
|
-
Kekik-1.7.
|
|
41
|
-
Kekik-1.7.
|
|
42
|
-
Kekik-1.7.
|
|
37
|
+
Kekik-1.7.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
38
|
+
Kekik-1.7.2.dist-info/METADATA,sha256=7RQJjD9uh3TDqIPkeRQ8bszs0gVcZJlFf8BMaP31r2U,43959
|
|
39
|
+
Kekik-1.7.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
40
|
+
Kekik-1.7.2.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
|
|
41
|
+
Kekik-1.7.2.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
|
|
42
|
+
Kekik-1.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|