Kekik 1.7.1__py3-none-any.whl → 1.7.3__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.
Potentially problematic release.
This version of Kekik might be problematic. Click here for more details.
- Kekik/cache.py +251 -97
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/METADATA +1 -1
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/RECORD +7 -7
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/LICENSE +0 -0
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/WHEEL +0 -0
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/entry_points.txt +0 -0
- {Kekik-1.7.1.dist-info → Kekik-1.7.3.dist-info}/top_level.txt +0 -0
Kekik/cache.py
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
3
|
from .cli import konsol
|
|
4
4
|
from functools import wraps
|
|
5
|
-
from time import time, sleep
|
|
6
5
|
from hashlib import md5
|
|
7
6
|
from urllib.parse import urlencode
|
|
8
|
-
import
|
|
7
|
+
import time
|
|
8
|
+
import threading
|
|
9
|
+
import asyncio
|
|
10
|
+
import pickle
|
|
11
|
+
|
|
12
|
+
# Redis client (Redis yoksa fallback yapılacak)
|
|
13
|
+
import redis.asyncio as redisAsync
|
|
14
|
+
import redis as redis
|
|
9
15
|
|
|
10
16
|
# -----------------------------------------------------
|
|
11
|
-
# Yardımcı Fonksiyonlar
|
|
17
|
+
# Sabitler ve Yardımcı Fonksiyonlar
|
|
12
18
|
# -----------------------------------------------------
|
|
19
|
+
UNLIMITED = None
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
REDIS_HOST = "127.0.0.1"
|
|
22
|
+
REDIS_PORT = 6379
|
|
23
|
+
REDIS_DB = 0
|
|
24
|
+
REDIS_PASS = None
|
|
15
25
|
|
|
16
26
|
def normalize_for_key(value):
|
|
17
27
|
"""
|
|
18
28
|
Cache key oluşturma amacıyla verilen değeri normalize eder.
|
|
19
|
-
- Basit tipler (int, float, str, bool, None)
|
|
29
|
+
- Basit tipler (int, float, str, bool, None) doğrudan kullanılır.
|
|
20
30
|
- dict: Anahtarları sıralı olarak normalize eder.
|
|
21
31
|
- list/tuple: Elemanları normalize eder.
|
|
22
32
|
- Diğer: Sadece sınıf ismi kullanılır.
|
|
@@ -36,7 +46,7 @@ def normalize_for_key(value):
|
|
|
36
46
|
def simple_cache_key(func, args, kwargs) -> str:
|
|
37
47
|
"""
|
|
38
48
|
Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
|
|
39
|
-
Oluşturulan stringin sonuna MD5 hash
|
|
49
|
+
Oluşturulan stringin sonuna MD5 hash eklenebilir.
|
|
40
50
|
"""
|
|
41
51
|
base_key = f"{func.__module__}.{func.__qualname__}"
|
|
42
52
|
|
|
@@ -60,7 +70,7 @@ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
|
|
|
60
70
|
if not is_fastapi:
|
|
61
71
|
return simple_cache_key(func, args, kwargs)
|
|
62
72
|
|
|
63
|
-
# FastAPI: request ilk
|
|
73
|
+
# FastAPI: request ilk argümandan ya da kwargs'dan alınır.
|
|
64
74
|
request = args[0] if args else kwargs.get("request")
|
|
65
75
|
|
|
66
76
|
if request.method == "GET":
|
|
@@ -73,56 +83,52 @@ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
|
|
|
73
83
|
form_data = await request.form()
|
|
74
84
|
veri = dict(form_data.items())
|
|
75
85
|
|
|
86
|
+
# Eğer "kurek" gibi özel parametreler varsa temizleyebilirsiniz:
|
|
87
|
+
veri.pop("kurek", None)
|
|
88
|
+
|
|
76
89
|
args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
|
|
77
90
|
return f"{request.url.path}?{veri}"
|
|
78
91
|
|
|
79
|
-
|
|
80
92
|
# -----------------------------------------------------
|
|
81
|
-
#
|
|
93
|
+
# Senkron Cache (RAM) Sınıfı
|
|
82
94
|
# -----------------------------------------------------
|
|
83
95
|
|
|
84
|
-
class
|
|
85
|
-
def __init__(self, ttl=UNLIMITED):
|
|
86
|
-
self._ttl = ttl
|
|
87
|
-
self._data = {}
|
|
88
|
-
self._times = {}
|
|
89
|
-
|
|
90
|
-
def _is_expired(self, key):
|
|
91
|
-
"""Belirtilen key'in süresi dolmuşsa True döner."""
|
|
92
|
-
if self._ttl is UNLIMITED:
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
timestamp = self._times.get(key)
|
|
96
|
-
|
|
97
|
-
return timestamp is not None and (time() - timestamp > self._ttl)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class SyncCache(Cache):
|
|
96
|
+
class SyncCache:
|
|
101
97
|
"""
|
|
102
|
-
|
|
103
|
-
TTL
|
|
104
|
-
Bu versiyonda, otomatik temizleme işlevselliği bir thread ile sağlanır.
|
|
98
|
+
Senkron fonksiyonlar için basit in-memory cache.
|
|
99
|
+
TTL süresi dolan veriler periyodik olarak arka plan thread’iyle temizlenir.
|
|
105
100
|
"""
|
|
106
101
|
def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
|
|
107
|
-
|
|
102
|
+
self._ttl = ttl
|
|
103
|
+
self._data = {}
|
|
104
|
+
self._times = {}
|
|
108
105
|
|
|
109
106
|
# TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
|
|
110
107
|
self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
|
|
111
|
-
self._lock = threading.RLock()
|
|
112
108
|
|
|
113
109
|
# Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
|
|
110
|
+
self._lock = threading.RLock()
|
|
114
111
|
self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
|
|
115
112
|
self._cleanup_thread.start()
|
|
116
113
|
|
|
117
114
|
def _auto_cleanup(self):
|
|
118
115
|
"""Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
|
|
119
116
|
while True:
|
|
120
|
-
sleep(self._cleanup_interval)
|
|
117
|
+
time.sleep(self._cleanup_interval)
|
|
121
118
|
with self._lock:
|
|
122
119
|
keys = list(self._data.keys())
|
|
123
120
|
for key in keys:
|
|
124
121
|
self.remove_if_expired(key)
|
|
125
122
|
|
|
123
|
+
def _is_expired(self, key):
|
|
124
|
+
"""Belirtilen key'in süresi dolmuşsa True döner."""
|
|
125
|
+
if self._ttl is UNLIMITED:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
timestamp = self._times.get(key)
|
|
129
|
+
|
|
130
|
+
return timestamp is not None and (time.time() - timestamp > self._ttl)
|
|
131
|
+
|
|
126
132
|
def remove_if_expired(self, key):
|
|
127
133
|
"""
|
|
128
134
|
Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
|
|
@@ -144,26 +150,104 @@ class SyncCache(Cache):
|
|
|
144
150
|
def __setitem__(self, key, value):
|
|
145
151
|
with self._lock:
|
|
146
152
|
self._data[key] = value
|
|
147
|
-
# konsol.log(f"[green][+] {key}")
|
|
148
153
|
if self._ttl is not UNLIMITED:
|
|
149
|
-
self._times[key] = time()
|
|
154
|
+
self._times[key] = time.time()
|
|
155
|
+
|
|
156
|
+
class HybridSyncCache:
|
|
157
|
+
"""
|
|
158
|
+
Senkron işlemler için, öncelikle Redis cache kullanılmaya çalışılır.
|
|
159
|
+
Redis'ten veri alınamazsa ya da hata oluşursa, SyncCache (in-memory) fallback uygulanır.
|
|
160
|
+
"""
|
|
161
|
+
def __init__(self, ttl=None):
|
|
162
|
+
self._ttl = ttl
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
self.redis = redis.Redis(
|
|
166
|
+
host = REDIS_HOST,
|
|
167
|
+
port = REDIS_PORT,
|
|
168
|
+
db = REDIS_DB,
|
|
169
|
+
password = REDIS_PASS,
|
|
170
|
+
decode_responses = False
|
|
171
|
+
)
|
|
172
|
+
self.redis.ping()
|
|
173
|
+
except Exception:
|
|
174
|
+
self.redis = None
|
|
175
|
+
|
|
176
|
+
self.memory = SyncCache(ttl)
|
|
177
|
+
|
|
178
|
+
def get(self, key):
|
|
179
|
+
# Önce Redis ile deniyoruz:
|
|
180
|
+
if self.redis:
|
|
181
|
+
try:
|
|
182
|
+
data = self.redis.get(key)
|
|
183
|
+
except Exception:
|
|
184
|
+
data = None
|
|
185
|
+
if data is not None:
|
|
186
|
+
try:
|
|
187
|
+
result = pickle.loads(data)
|
|
188
|
+
# konsol.log(f"[yellow][~] {key}")
|
|
189
|
+
return result
|
|
190
|
+
except Exception:
|
|
191
|
+
# Deserialize hatası durumunda fallback'e geç
|
|
192
|
+
pass
|
|
150
193
|
|
|
194
|
+
# Redis'te veri yoksa, yerel cache'ten alıyoruz.
|
|
195
|
+
try:
|
|
196
|
+
return self.memory[key]
|
|
197
|
+
except KeyError:
|
|
198
|
+
raise KeyError(key)
|
|
199
|
+
|
|
200
|
+
def set(self, key, value):
|
|
201
|
+
try:
|
|
202
|
+
ser = pickle.dumps(value)
|
|
203
|
+
except Exception:
|
|
204
|
+
# Serialization hatası durumunda yerel cache'e yazalım.
|
|
205
|
+
self.memory[key] = value
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if self.redis:
|
|
209
|
+
try:
|
|
210
|
+
if self._ttl is not None:
|
|
211
|
+
self.redis.set(key, ser, ex=self._ttl)
|
|
212
|
+
else:
|
|
213
|
+
self.redis.set(key, ser)
|
|
214
|
+
return
|
|
215
|
+
except Exception:
|
|
216
|
+
# Redis'e yazılamazsa yerel cache'e geçelim.
|
|
217
|
+
self.memory[key] = value
|
|
218
|
+
return
|
|
219
|
+
else:
|
|
220
|
+
# Redis kullanılmıyorsa direkt yerel cache'e yaz.
|
|
221
|
+
self.memory[key] = value
|
|
222
|
+
|
|
223
|
+
# HybridSyncCache'in subscriptable olması için:
|
|
224
|
+
def __getitem__(self, key):
|
|
225
|
+
return self.get(key)
|
|
226
|
+
|
|
227
|
+
def __setitem__(self, key, value):
|
|
228
|
+
self.set(key, value)
|
|
229
|
+
|
|
230
|
+
# -----------------------------------------------------
|
|
231
|
+
# Asenkron Cache (In-Memory) ve Redis Hybrid Cache
|
|
232
|
+
# -----------------------------------------------------
|
|
151
233
|
|
|
152
|
-
class AsyncCache
|
|
234
|
+
class AsyncCache:
|
|
153
235
|
"""
|
|
154
|
-
|
|
155
|
-
Aynı key için gelen eşzamanlı çağrılar, futures kullanılarak tek sonuç üzerinden paylaşılır.
|
|
156
|
-
Ek olarak, belirli aralıklarla cache’i kontrol edip, süresi dolmuş verileri temizleyen otomatik temizleme görevi çalışır.
|
|
236
|
+
Temel in-memory asenkron cache.
|
|
157
237
|
"""
|
|
158
238
|
def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
|
|
159
239
|
"""
|
|
160
240
|
:param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
|
|
161
241
|
:param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
|
|
162
242
|
"""
|
|
163
|
-
|
|
243
|
+
self._ttl = ttl
|
|
244
|
+
self._data = {}
|
|
245
|
+
self._times = {}
|
|
164
246
|
self.futures = {}
|
|
165
247
|
|
|
248
|
+
# TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
|
|
166
249
|
self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
|
|
250
|
+
|
|
167
251
|
# Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
|
|
168
252
|
try:
|
|
169
253
|
self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
|
|
@@ -174,9 +258,7 @@ class AsyncCache(Cache):
|
|
|
174
258
|
"""Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
|
|
175
259
|
while True:
|
|
176
260
|
await asyncio.sleep(self._cleanup_interval)
|
|
177
|
-
|
|
178
|
-
keys = list(self._data.keys())
|
|
179
|
-
for key in keys:
|
|
261
|
+
for key in list(self._data.keys()):
|
|
180
262
|
self.remove_if_expired(key)
|
|
181
263
|
|
|
182
264
|
def ensure_cleanup_task(self):
|
|
@@ -187,59 +269,139 @@ class AsyncCache(Cache):
|
|
|
187
269
|
except RuntimeError:
|
|
188
270
|
pass # Yine loop yoksa yapacak bir şey yok
|
|
189
271
|
|
|
272
|
+
def remove_if_expired(self, key):
|
|
273
|
+
"""
|
|
274
|
+
Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
|
|
275
|
+
"""
|
|
276
|
+
if self._ttl is not UNLIMITED:
|
|
277
|
+
t = self._times.get(key)
|
|
278
|
+
if t is not None and (time.time() - t > self._ttl):
|
|
279
|
+
# konsol.log(f"[red][-] {key}")
|
|
280
|
+
self._data.pop(key, None)
|
|
281
|
+
self._times.pop(key, None)
|
|
282
|
+
self.futures.pop(key, None)
|
|
283
|
+
|
|
190
284
|
async def get(self, key):
|
|
191
285
|
"""
|
|
192
286
|
Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
|
|
193
287
|
Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
|
|
194
288
|
"""
|
|
289
|
+
# Cleanup task'in aktif olduğundan emin olun.
|
|
195
290
|
self.ensure_cleanup_task()
|
|
291
|
+
# Eğer key'in süresi dolmuşsa, kaldırın.
|
|
196
292
|
self.remove_if_expired(key)
|
|
197
293
|
|
|
198
294
|
try:
|
|
199
|
-
|
|
295
|
+
# Cache içerisinde key varsa direkt değeri döndür.
|
|
296
|
+
value = self._data[key]
|
|
200
297
|
# konsol.log(f"[yellow][~] {key}")
|
|
201
|
-
return
|
|
202
|
-
except KeyError
|
|
298
|
+
return value
|
|
299
|
+
except KeyError:
|
|
300
|
+
# Eğer key cache'de yoksa, aynı key ile başlatılmış future varsa onu bekle.
|
|
203
301
|
future = self.futures.get(key)
|
|
204
302
|
if future:
|
|
205
303
|
await future
|
|
206
|
-
|
|
304
|
+
# Future tamamlandığında sonucu döndür.
|
|
305
|
+
value = future.result()
|
|
207
306
|
# konsol.log(f"[yellow][?] {key}")
|
|
208
|
-
return
|
|
307
|
+
return value
|
|
308
|
+
# Eğer future da yoksa, key bulunamadığına dair hata fırlat.
|
|
309
|
+
raise KeyError(key)
|
|
209
310
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"""
|
|
214
|
-
Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
|
|
215
|
-
"""
|
|
216
|
-
if self._ttl is not UNLIMITED and self._is_expired(key):
|
|
217
|
-
# konsol.log(f"[red][-] {key}")
|
|
218
|
-
self._data.pop(key, None)
|
|
219
|
-
self._times.pop(key, None)
|
|
220
|
-
self.futures.pop(key, None)
|
|
221
|
-
|
|
222
|
-
def __setitem__(self, key, value):
|
|
311
|
+
async def set(self, key, value):
|
|
312
|
+
"""Belirtilen key için cache'e değer ekler."""
|
|
313
|
+
self.ensure_cleanup_task()
|
|
223
314
|
self._data[key] = value
|
|
224
315
|
if self._ttl is not UNLIMITED:
|
|
225
|
-
self._times[key] = time()
|
|
316
|
+
self._times[key] = time.time()
|
|
226
317
|
|
|
318
|
+
class HybridAsyncCache:
|
|
319
|
+
"""
|
|
320
|
+
Öncelikle Redis cache kullanılmaya çalışılır.
|
|
321
|
+
Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCache’e geçilir.
|
|
322
|
+
"""
|
|
323
|
+
def __init__(self, ttl=UNLIMITED):
|
|
324
|
+
self._ttl = ttl
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
self.redis = redisAsync.Redis(
|
|
328
|
+
host = REDIS_HOST,
|
|
329
|
+
port = REDIS_PORT,
|
|
330
|
+
db = REDIS_DB,
|
|
331
|
+
password = REDIS_PASS,
|
|
332
|
+
decode_responses = False
|
|
333
|
+
)
|
|
334
|
+
except Exception:
|
|
335
|
+
self.redis = None
|
|
336
|
+
|
|
337
|
+
self.memory = AsyncCache(ttl)
|
|
338
|
+
self.futures = {}
|
|
339
|
+
|
|
340
|
+
async def get(self, key):
|
|
341
|
+
# Eşzamanlı istek yönetimi: aynı key için bir future varsa, direkt bekleyip sonucu döndür.
|
|
342
|
+
if key in self.futures:
|
|
343
|
+
# konsol.log(f"[yellow][?] {key}")
|
|
344
|
+
return await self.futures[key]
|
|
345
|
+
|
|
346
|
+
# İlk önce Redis ile deneyelim
|
|
347
|
+
if self.redis:
|
|
348
|
+
try:
|
|
349
|
+
data = await self.redis.get(key)
|
|
350
|
+
except Exception:
|
|
351
|
+
return await self.memory.get(key)
|
|
352
|
+
if data is not None:
|
|
353
|
+
try:
|
|
354
|
+
result = pickle.loads(data)
|
|
355
|
+
# konsol.log(f"[yellow][~] {key}")
|
|
356
|
+
return result
|
|
357
|
+
except Exception:
|
|
358
|
+
# Deserialize hatası durumunda in-memory cache'ten dene
|
|
359
|
+
return await self.memory.get(key)
|
|
360
|
+
else:
|
|
361
|
+
# Redis'te veri yoksa, in-memory cache'e bak
|
|
362
|
+
return await self.memory.get(key)
|
|
363
|
+
else:
|
|
364
|
+
# Redis kullanılmıyorsa doğrudan in-memory cache'e bak
|
|
365
|
+
return await self.memory.get(key)
|
|
366
|
+
|
|
367
|
+
async def set(self, key, value):
|
|
368
|
+
# Önce veriyi pickle etmeyi deniyoruz.
|
|
369
|
+
try:
|
|
370
|
+
ser = pickle.dumps(value)
|
|
371
|
+
except Exception:
|
|
372
|
+
# Serialization hatası durumunda sadece in-memory cache'e yaz
|
|
373
|
+
await self.memory.set(key, value)
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
if self.redis:
|
|
377
|
+
try:
|
|
378
|
+
if self._ttl is not UNLIMITED:
|
|
379
|
+
await self.redis.set(key, ser, ex=self._ttl)
|
|
380
|
+
else:
|
|
381
|
+
await self.redis.set(key, ser)
|
|
382
|
+
return
|
|
383
|
+
except Exception:
|
|
384
|
+
# Redis yazma hatası durumunda in-memory fallback
|
|
385
|
+
await self.memory.set(key, value)
|
|
386
|
+
return
|
|
387
|
+
else:
|
|
388
|
+
# Redis yoksa in-memory cache'e yaz
|
|
389
|
+
await self.memory.set(key, value)
|
|
227
390
|
|
|
228
391
|
# -----------------------------------------------------
|
|
229
|
-
#
|
|
392
|
+
# Cache'e Hesaplanmış Sonucu Yazma Yardımcı Fonksiyonları
|
|
230
393
|
# -----------------------------------------------------
|
|
231
394
|
|
|
232
395
|
def _sync_maybe_cache(func, key, result, unless):
|
|
233
|
-
"""Senkron
|
|
396
|
+
"""Senkron fonksiyon sonucu için cache kaydı oluşturur."""
|
|
234
397
|
if unless is None or not unless(result):
|
|
235
398
|
func.__cache[key] = result
|
|
236
399
|
# konsol.log(f"[green][+] {key}")
|
|
237
400
|
|
|
238
401
|
async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
239
402
|
"""
|
|
240
|
-
Asenkron fonksiyon sonucunu hesaplar ve cache'e
|
|
241
|
-
Aynı key için
|
|
242
|
-
Sonuç, unless(result) True değilse cache'e eklenir.
|
|
403
|
+
Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
|
|
404
|
+
Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
|
|
243
405
|
"""
|
|
244
406
|
# __cache'den cache nesnesini alıyoruz.
|
|
245
407
|
cache = func.__cache
|
|
@@ -256,12 +418,12 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
|
256
418
|
# Asenkron fonksiyonu çalıştır ve sonucu elde et.
|
|
257
419
|
result = await func(*args, **kwargs)
|
|
258
420
|
future.set_result(result)
|
|
259
|
-
|
|
421
|
+
|
|
260
422
|
# unless koşuluna göre cache'e ekleme yap.
|
|
261
423
|
if unless is None or not unless(result):
|
|
262
|
-
cache
|
|
424
|
+
await cache.set(key, result)
|
|
263
425
|
# konsol.log(f"[green][+] {key}")
|
|
264
|
-
|
|
426
|
+
|
|
265
427
|
return result
|
|
266
428
|
except Exception as exc:
|
|
267
429
|
future.cancel()
|
|
@@ -270,44 +432,35 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
|
|
|
270
432
|
# İşlem tamamlandığında future'ı temizle.
|
|
271
433
|
cache.futures.pop(key, None)
|
|
272
434
|
|
|
273
|
-
|
|
274
435
|
# -----------------------------------------------------
|
|
275
|
-
#
|
|
436
|
+
# kekik_cache Dekoratörü (Senkrondan Asenkrona)
|
|
276
437
|
# -----------------------------------------------------
|
|
277
438
|
|
|
278
|
-
def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
|
|
439
|
+
def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
|
|
279
440
|
"""
|
|
280
|
-
Bir
|
|
281
|
-
|
|
282
|
-
Args:
|
|
283
|
-
ttl (int, optional): Cache’in geçerlilik süresi (saniye).
|
|
284
|
-
Eğer `UNLIMITED` ise süresizdir. Varsayılan olarak `UNLIMITED`'dir.
|
|
285
|
-
unless (callable, optional): Fonksiyonun sonucunu argüman olarak alan bir callable.
|
|
286
|
-
Eğer `True` dönerse, sonuç cache'e alınmaz. Varsayılan olarak `None`'dır.
|
|
287
|
-
is_fastapi (bool, optional): Eğer `True` ise, cache key'i oluştururken FastAPI request nesnesine özel şekilde davranır.
|
|
288
|
-
Varsayılan olarak `False`'tır.
|
|
289
|
-
|
|
290
|
-
Notlar:
|
|
291
|
-
-------
|
|
292
|
-
Normalde yalnızca Redis cache kullanılmak istenir.
|
|
293
|
-
Ancak Redis'e ulaşılamayan durumlarda RAM fallback kullanılır.
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
Örn.:
|
|
441
|
+
Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
|
|
298
442
|
|
|
299
|
-
|
|
443
|
+
Parametreler:
|
|
444
|
+
- ttl: Cache'in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
|
|
445
|
+
- unless: Sonuç alınmadan önce çağrılan, True dönerse cache'e alınmaz.
|
|
446
|
+
- is_fastapi: True ise, FastAPI Request nesnesine göre key oluşturur.
|
|
447
|
+
- use_redis: Asenkron fonksiyonlarda Redis kullanımı (Hybrid cache) için True verilebilir.
|
|
448
|
+
|
|
449
|
+
Örnek Kullanım:
|
|
450
|
+
|
|
451
|
+
@kekik_cache(ttl=15, unless=lambda sonuc: sonuc is None)
|
|
300
452
|
async def bakalim(param):
|
|
301
|
-
# Burada cache işlemi yapılır.
|
|
302
453
|
return param
|
|
303
454
|
"""
|
|
304
|
-
# Parametresiz
|
|
455
|
+
# Parametresiz kullanım durumunda
|
|
305
456
|
if callable(ttl):
|
|
306
|
-
return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
|
|
457
|
+
return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
|
|
307
458
|
|
|
308
459
|
def decorator(func):
|
|
309
460
|
if asyncio.iscoroutinefunction(func):
|
|
310
|
-
|
|
461
|
+
# Asenkron fonksiyonlar için cache türünü seçelim:
|
|
462
|
+
|
|
463
|
+
func.__cache = HybridAsyncCache(ttl) if use_redis else AsyncCache(ttl)
|
|
311
464
|
|
|
312
465
|
@wraps(func)
|
|
313
466
|
async def async_wrapper(*args, **kwargs):
|
|
@@ -320,7 +473,8 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
|
|
|
320
473
|
|
|
321
474
|
return async_wrapper
|
|
322
475
|
else:
|
|
323
|
-
|
|
476
|
+
# Senkron fonksiyonlar için
|
|
477
|
+
func.__cache = HybridSyncCache(ttl) if use_redis else SyncCache(ttl)
|
|
324
478
|
|
|
325
479
|
@wraps(func)
|
|
326
480
|
def sync_wrapper(*args, **kwargs):
|
|
@@ -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=YJ8hjZY37skYnU-yfPMbLPsEmsokWMzhJk99qkvmYsA,17434
|
|
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.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
38
|
+
Kekik-1.7.3.dist-info/METADATA,sha256=cSf0jClm2kfxcF2XJFOxvm_Qk6_nrcgGcqXuIOotRK8,43959
|
|
39
|
+
Kekik-1.7.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
40
|
+
Kekik-1.7.3.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
|
|
41
|
+
Kekik-1.7.3.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
|
|
42
|
+
Kekik-1.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|