Kekik 1.7.0__tar.gz → 1.7.2__tar.gz

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.

Files changed (49) hide show
  1. kekik-1.7.2/Kekik/cache.py +410 -0
  2. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/PKG-INFO +1 -1
  3. {kekik-1.7.0 → kekik-1.7.2}/PKG-INFO +1 -1
  4. {kekik-1.7.0 → kekik-1.7.2}/setup.py +1 -1
  5. kekik-1.7.0/Kekik/cache.py +0 -241
  6. {kekik-1.7.0 → kekik-1.7.2}/Kekik/BIST.py +0 -0
  7. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Domain2IP.py +0 -0
  8. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Nesne.py +0 -0
  9. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/AESManager.py +0 -0
  10. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/CryptoJS.py +0 -0
  11. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/HexCodec.py +0 -0
  12. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/NaysHash.py +0 -0
  13. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/Packer.py +0 -0
  14. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/StringCodec.py +0 -0
  15. {kekik-1.7.0 → kekik-1.7.2}/Kekik/Sifreleme/__init__.py +0 -0
  16. {kekik-1.7.0 → kekik-1.7.2}/Kekik/__init__.py +0 -0
  17. {kekik-1.7.0 → kekik-1.7.2}/Kekik/cli.py +0 -0
  18. {kekik-1.7.0 → kekik-1.7.2}/Kekik/csv2dict.py +0 -0
  19. {kekik-1.7.0 → kekik-1.7.2}/Kekik/dict2csv.py +0 -0
  20. {kekik-1.7.0 → kekik-1.7.2}/Kekik/dict2json.py +0 -0
  21. {kekik-1.7.0 → kekik-1.7.2}/Kekik/dosya2set.py +0 -0
  22. {kekik-1.7.0 → kekik-1.7.2}/Kekik/dosya_indir.py +0 -0
  23. {kekik-1.7.0 → kekik-1.7.2}/Kekik/hwid_kontrol.py +0 -0
  24. {kekik-1.7.0 → kekik-1.7.2}/Kekik/kisi_ver/__init__.py +0 -0
  25. {kekik-1.7.0 → kekik-1.7.2}/Kekik/kisi_ver/biyografiler.py +0 -0
  26. {kekik-1.7.0 → kekik-1.7.2}/Kekik/kisi_ver/isimler.py +0 -0
  27. {kekik-1.7.0 → kekik-1.7.2}/Kekik/kisi_ver/soyisimler.py +0 -0
  28. {kekik-1.7.0 → kekik-1.7.2}/Kekik/link_islemleri.py +0 -0
  29. {kekik-1.7.0 → kekik-1.7.2}/Kekik/list2html.py +0 -0
  30. {kekik-1.7.0 → kekik-1.7.2}/Kekik/liste_fetis.py +0 -0
  31. {kekik-1.7.0 → kekik-1.7.2}/Kekik/mail_gonder.py +0 -0
  32. {kekik-1.7.0 → kekik-1.7.2}/Kekik/okunabilir_byte.py +0 -0
  33. {kekik-1.7.0 → kekik-1.7.2}/Kekik/proxy_ver.py +0 -0
  34. {kekik-1.7.0 → kekik-1.7.2}/Kekik/qr_ver.py +0 -0
  35. {kekik-1.7.0 → kekik-1.7.2}/Kekik/ses_fetis.py +0 -0
  36. {kekik-1.7.0 → kekik-1.7.2}/Kekik/slugify.py +0 -0
  37. {kekik-1.7.0 → kekik-1.7.2}/Kekik/terminal_baslik.py +0 -0
  38. {kekik-1.7.0 → kekik-1.7.2}/Kekik/txt_fetis.py +0 -0
  39. {kekik-1.7.0 → kekik-1.7.2}/Kekik/unicode_tr.py +0 -0
  40. {kekik-1.7.0 → kekik-1.7.2}/Kekik/zaman_donustur.py +0 -0
  41. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/SOURCES.txt +0 -0
  42. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/dependency_links.txt +0 -0
  43. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/entry_points.txt +0 -0
  44. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/requires.txt +0 -0
  45. {kekik-1.7.0 → kekik-1.7.2}/Kekik.egg-info/top_level.txt +0 -0
  46. {kekik-1.7.0 → kekik-1.7.2}/LICENSE +0 -0
  47. {kekik-1.7.0 → kekik-1.7.2}/MANIFEST.in +0 -0
  48. {kekik-1.7.0 → kekik-1.7.2}/README.md +0 -0
  49. {kekik-1.7.0 → kekik-1.7.2}/setup.cfg +0 -0
@@ -0,0 +1,410 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from .cli import konsol
4
+ from functools import wraps
5
+ from hashlib import md5
6
+ from urllib.parse import urlencode
7
+ import time
8
+ import threading
9
+ import asyncio
10
+ import pickle
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
+ # -----------------------------------------------------
18
+ UNLIMITED = None
19
+
20
+ def normalize_for_key(value):
21
+ """
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.
27
+ """
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):
96
+ self._ttl = ttl
97
+ self._data = {}
98
+ self._times = {}
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
+
117
+ def _is_expired(self, key):
118
+ """Belirtilen key'in süresi dolmuşsa True döner."""
119
+ if self._ttl is UNLIMITED:
120
+ return False
121
+
122
+ timestamp = self._times.get(key)
123
+
124
+ return timestamp is not None and (time.time() - timestamp > self._ttl)
125
+
126
+ def remove_if_expired(self, key):
127
+ """
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.
130
+ """
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}")
136
+
137
+ def __getitem__(self, key):
138
+ with self._lock:
139
+ self.remove_if_expired(key)
140
+ veri = self._data[key]
141
+ # konsol.log(f"[yellow][~] {key}")
142
+ return veri
143
+
144
+ def __setitem__(self, key, value):
145
+ with self._lock:
146
+ self._data[key] = value
147
+ if self._ttl is not UNLIMITED:
148
+ self._times[key] = time.time()
149
+
150
+ # -----------------------------------------------------
151
+ # Asenkron Cache (In-Memory) ve Redis Hybrid Cache
152
+ # -----------------------------------------------------
153
+
154
+ class AsyncCache:
155
+ """
156
+ Temel in-memory asenkron cache.
157
+ """
158
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
159
+ """
160
+ :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
161
+ :param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
162
+ """
163
+ self._ttl = ttl
164
+ self._data = {}
165
+ self._times = {}
166
+ self.futures = {}
167
+
168
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
169
+ self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
170
+
171
+ # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
172
+ try:
173
+ self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
174
+ except RuntimeError:
175
+ self._cleanup_task = None
176
+
177
+ async def _auto_cleanup(self):
178
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
179
+ while True:
180
+ await asyncio.sleep(self._cleanup_interval)
181
+ for key in list(self._data.keys()):
182
+ self.remove_if_expired(key)
183
+
184
+ def ensure_cleanup_task(self):
185
+ """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
186
+ if self._cleanup_task is None:
187
+ try:
188
+ self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
189
+ except RuntimeError:
190
+ pass # Yine loop yoksa yapacak bir şey yok
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
+
204
+ async def get(self, key):
205
+ """
206
+ Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
207
+ Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
208
+ """
209
+ # Cleanup task'in aktif olduğundan emin olun.
210
+ self.ensure_cleanup_task()
211
+ # Eğer key'in süresi dolmuşsa, kaldırın.
212
+ self.remove_if_expired(key)
213
+
214
+ try:
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.
221
+ future = self.futures.get(key)
222
+ if future:
223
+ await future
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()
237
+
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
245
+
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]
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
+ # -----------------------------------------------------
312
+
313
+ def _sync_maybe_cache(func, key, result, unless):
314
+ """Senkron fonksiyon sonucu için cache kaydı oluşturur."""
315
+ if unless is None or not unless(result):
316
+ func.__cache[key] = result
317
+ # konsol.log(f"[green][+] {key}")
318
+
319
+ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
320
+ """
321
+ Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
322
+ Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
323
+ """
324
+ # __cache'den cache nesnesini alıyoruz.
325
+ cache = func.__cache
326
+
327
+ # Aynı key için aktif bir future varsa, onun sonucunu döndür.
328
+ if key in cache.futures:
329
+ return await cache.futures[key]
330
+
331
+ # Yeni future oluşturuluyor ve cache.futures'e ekleniyor.
332
+ future = asyncio.Future()
333
+ cache.futures[key] = future
334
+
335
+ try:
336
+ # Asenkron fonksiyonu çalıştır ve sonucu elde et.
337
+ result = await func(*args, **kwargs)
338
+ future.set_result(result)
339
+
340
+ # unless koşuluna göre cache'e ekleme yap.
341
+ if unless is None or not unless(result):
342
+ await cache.set(key, result)
343
+ # konsol.log(f"[green][+] {key}")
344
+
345
+ return result
346
+ except Exception as exc:
347
+ future.cancel()
348
+ raise exc
349
+ finally:
350
+ # İşlem tamamlandığında future'ı temizle.
351
+ cache.futures.pop(key, None)
352
+
353
+ # -----------------------------------------------------
354
+ # kekik_cache Dekoratörü (Senkrondan Asenkrona)
355
+ # -----------------------------------------------------
356
+
357
+ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
358
+ """
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.
366
+
367
+ Örnek Kullanım:
368
+
369
+ @kekik_cache(ttl=15, unless=lambda sonuc: sonuc is None)
370
+ async def bakalim(param):
371
+ return param
372
+ """
373
+ # Parametresiz kullanım durumunda
374
+ if callable(ttl):
375
+ return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
376
+
377
+ def decorator(func):
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
+
383
+ @wraps(func)
384
+ async def async_wrapper(*args, **kwargs):
385
+ key = await make_cache_key(func, args, kwargs, is_fastapi)
386
+
387
+ try:
388
+ return await func.__cache.get(key)
389
+ except KeyError:
390
+ return await _async_compute_and_cache(func, key, unless, *args, **kwargs)
391
+
392
+ return async_wrapper
393
+ else:
394
+ # Senkron fonksiyonlar için
395
+ func.__cache = SyncCache(ttl)
396
+
397
+ @wraps(func)
398
+ def sync_wrapper(*args, **kwargs):
399
+ key = simple_cache_key(func, args, kwargs)
400
+
401
+ try:
402
+ return func.__cache[key]
403
+ except KeyError:
404
+ result = func(*args, **kwargs)
405
+ _sync_maybe_cache(func, key, result, unless)
406
+ return result
407
+
408
+ return sync_wrapper
409
+
410
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Kekik
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: İşlerimizi kolaylaştıracak fonksiyonların el altında durduğu kütüphane..
5
5
  Home-page: https://github.com/keyiflerolsun/Kekik
6
6
  Author: keyiflerolsun
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Kekik
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: İşlerimizi kolaylaştıracak fonksiyonların el altında durduğu kütüphane..
5
5
  Home-page: https://github.com/keyiflerolsun/Kekik
6
6
  Author: keyiflerolsun
@@ -6,7 +6,7 @@ from io import open
6
6
  setup(
7
7
  # ? Genel Bilgiler
8
8
  name = "Kekik",
9
- version = "1.7.0",
9
+ version = "1.7.2",
10
10
  url = "https://github.com/keyiflerolsun/Kekik",
11
11
  description = "İşlerimizi kolaylaştıracak fonksiyonların el altında durduğu kütüphane..",
12
12
  keywords = ["Kekik", "KekikAkademi", "keyiflerolsun"],
@@ -1,241 +0,0 @@
1
- # ! https://github.com/Fatal1ty/aiocached
2
-
3
- import asyncio
4
- from functools import wraps
5
- from time import time
6
- from hashlib import md5
7
- from urllib.parse import urlencode
8
-
9
- UNLIMITED = None
10
-
11
- class Cache:
12
- """
13
- Basit in-memory cache yapısı.
14
- TTL (time-to-live) süresi dolan veriler otomatik olarak temizlenir (get/erişim anında kontrol edilir).
15
- """
16
- def __init__(self, ttl=UNLIMITED):
17
- self._data = {}
18
- self._ttl = ttl
19
- self._times = {}
20
-
21
- def _is_expired(self, key):
22
- """Belirtilen key'in süresi dolduysa True döner."""
23
- if self._ttl is UNLIMITED:
24
- return False
25
-
26
- timestamp = self._times.get(key)
27
-
28
- return timestamp is not None and (time() - timestamp > self._ttl)
29
-
30
- def remove_if_expired(self, key):
31
- """
32
- Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
33
- """
34
- if self._is_expired(key):
35
- self._data.pop(key, None)
36
- self._times.pop(key, None)
37
-
38
- def __getitem__(self, key):
39
- self.remove_if_expired(key)
40
- return self._data[key]
41
-
42
- def __setitem__(self, key, value):
43
- self._data[key] = value
44
- if self._ttl is not UNLIMITED:
45
- self._times[key] = time()
46
-
47
-
48
- class AsyncCache(Cache):
49
- """
50
- Asenkron işlemleri destekleyen cache yapısı.
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.
53
- """
54
- def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
55
- """
56
- :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
57
- :param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
58
- """
59
- super().__init__(ttl)
60
- self.futures = {}
61
-
62
- self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
63
- # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
64
- try:
65
- self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
66
- except RuntimeError:
67
- self._cleanup_task = None
68
-
69
- async def _auto_cleanup(self):
70
- """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
71
- while True:
72
- await asyncio.sleep(self._cleanup_interval)
73
- # _data kopyasını almak, üzerinde dönüp silme yaparken hata almamak için.
74
- keys = list(self._data.keys())
75
- for key in keys:
76
- self.remove_if_expired(key)
77
-
78
- def ensure_cleanup_task(self):
79
- """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
80
- if self._cleanup_task is None:
81
- try:
82
- self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
83
- except RuntimeError:
84
- pass # Yine loop yoksa yapacak bir şey yok
85
-
86
- async def get(self, key):
87
- """
88
- Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
89
- Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
90
- """
91
- self.ensure_cleanup_task()
92
- self.remove_if_expired(key)
93
-
94
- try:
95
- return self._data[key]
96
- except KeyError as e:
97
- future = self.futures.get(key)
98
- if future:
99
- await future
100
- return future.result()
101
-
102
- raise e
103
-
104
- def remove_if_expired(self, key):
105
- """
106
- Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
107
- """
108
- if self._ttl is not UNLIMITED and self._is_expired(key):
109
- self._data.pop(key, None)
110
- self._times.pop(key, None)
111
- self.futures.pop(key, None)
112
-
113
-
114
- def _sync_maybe_cache(func, key, result, unless):
115
- """Senkron sonuç için cache kaydını oluşturur (unless koşuluna bakarak)."""
116
- if unless is None or not unless(result):
117
- func.__cache[key] = result
118
-
119
-
120
- async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
121
- """
122
- Asenkron fonksiyon sonucunu hesaplar ve cache'e ekler.
123
- Aynı key için işlem devam ediyorsa, mevcut sonucu bekler.
124
- Sonuç, unless(result) True değilse cache'e eklenir.
125
- """
126
- # __cache'den cache nesnesini alıyoruz.
127
- cache = func.__cache
128
-
129
- # Aynı key için aktif bir future varsa, onun sonucunu döndür.
130
- if key in cache.futures:
131
- return await cache.futures[key]
132
-
133
- # Yeni future oluşturuluyor ve cache.futures'e ekleniyor.
134
- future = asyncio.Future()
135
- cache.futures[key] = future
136
-
137
- try:
138
- # Asenkron fonksiyonu çalıştır ve sonucu elde et.
139
- result = await func(*args, **kwargs)
140
- future.set_result(result)
141
-
142
- # unless koşuluna göre cache'e ekleme yap.
143
- if unless is None or not unless(result):
144
- cache[key] = result
145
-
146
- return result
147
- except Exception as exc:
148
- future.cancel()
149
- raise exc
150
- finally:
151
- # İşlem tamamlandığında future'ı temizle.
152
- cache.futures.pop(key, None)
153
-
154
- async def make_cache_key(args, kwargs, is_fastapi=False):
155
- """
156
- Cache key'ini oluşturur.
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
-
182
-
183
- def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
184
- """
185
- Bir fonksiyon veya coroutine'in sonucunu cache'ler.
186
-
187
- Args:
188
- ttl (int, optional): Cache’in geçerlilik süresi (saniye).
189
- Eğer `UNLIMITED` ise süresizdir. Varsayılan olarak `UNLIMITED`'dir.
190
- unless (callable, optional): Fonksiyonun sonucunu argüman olarak alan bir callable.
191
- Eğer `True` dönerse, sonuç cache'e alınmaz. Varsayılan olarak `None`'dır.
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.:
203
-
204
- @kekik_cache(ttl=15, unless=lambda sonuc: bool(sonuc is None))
205
- async def bakalim(param):
206
- # Burada cache işlemi yapılır.
207
- return param
208
- """
209
- # Parametresiz kullanıldığında
210
- if callable(ttl):
211
- return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
212
-
213
- def decorator(func):
214
- func.__cache = AsyncCache(ttl)
215
-
216
- if asyncio.iscoroutinefunction(func):
217
- @wraps(func)
218
- async def async_wrapper(*args, **kwargs):
219
- key = await make_cache_key(args, kwargs, is_fastapi)
220
-
221
- try:
222
- return await func.__cache.get(key)
223
- except KeyError:
224
- return await _async_compute_and_cache(func, key, unless, *args, **kwargs)
225
-
226
- return async_wrapper
227
- else:
228
- @wraps(func)
229
- def sync_wrapper(*args, **kwargs):
230
- key = (args, tuple(sorted(kwargs.items())))
231
-
232
- try:
233
- return func.__cache[key]
234
- except KeyError:
235
- result = func(*args, **kwargs)
236
- _sync_maybe_cache(func, key, result, unless)
237
- return result
238
-
239
- return sync_wrapper
240
-
241
- return decorator
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes