Kekik 1.7.1__tar.gz → 1.7.3__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.3/Kekik/cache.py +492 -0
  2. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/PKG-INFO +1 -1
  3. {kekik-1.7.1 → kekik-1.7.3}/PKG-INFO +1 -1
  4. {kekik-1.7.1 → kekik-1.7.3}/setup.py +1 -1
  5. kekik-1.7.1/Kekik/cache.py +0 -338
  6. {kekik-1.7.1 → kekik-1.7.3}/Kekik/BIST.py +0 -0
  7. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Domain2IP.py +0 -0
  8. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Nesne.py +0 -0
  9. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/AESManager.py +0 -0
  10. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/CryptoJS.py +0 -0
  11. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/HexCodec.py +0 -0
  12. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/NaysHash.py +0 -0
  13. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/Packer.py +0 -0
  14. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/StringCodec.py +0 -0
  15. {kekik-1.7.1 → kekik-1.7.3}/Kekik/Sifreleme/__init__.py +0 -0
  16. {kekik-1.7.1 → kekik-1.7.3}/Kekik/__init__.py +0 -0
  17. {kekik-1.7.1 → kekik-1.7.3}/Kekik/cli.py +0 -0
  18. {kekik-1.7.1 → kekik-1.7.3}/Kekik/csv2dict.py +0 -0
  19. {kekik-1.7.1 → kekik-1.7.3}/Kekik/dict2csv.py +0 -0
  20. {kekik-1.7.1 → kekik-1.7.3}/Kekik/dict2json.py +0 -0
  21. {kekik-1.7.1 → kekik-1.7.3}/Kekik/dosya2set.py +0 -0
  22. {kekik-1.7.1 → kekik-1.7.3}/Kekik/dosya_indir.py +0 -0
  23. {kekik-1.7.1 → kekik-1.7.3}/Kekik/hwid_kontrol.py +0 -0
  24. {kekik-1.7.1 → kekik-1.7.3}/Kekik/kisi_ver/__init__.py +0 -0
  25. {kekik-1.7.1 → kekik-1.7.3}/Kekik/kisi_ver/biyografiler.py +0 -0
  26. {kekik-1.7.1 → kekik-1.7.3}/Kekik/kisi_ver/isimler.py +0 -0
  27. {kekik-1.7.1 → kekik-1.7.3}/Kekik/kisi_ver/soyisimler.py +0 -0
  28. {kekik-1.7.1 → kekik-1.7.3}/Kekik/link_islemleri.py +0 -0
  29. {kekik-1.7.1 → kekik-1.7.3}/Kekik/list2html.py +0 -0
  30. {kekik-1.7.1 → kekik-1.7.3}/Kekik/liste_fetis.py +0 -0
  31. {kekik-1.7.1 → kekik-1.7.3}/Kekik/mail_gonder.py +0 -0
  32. {kekik-1.7.1 → kekik-1.7.3}/Kekik/okunabilir_byte.py +0 -0
  33. {kekik-1.7.1 → kekik-1.7.3}/Kekik/proxy_ver.py +0 -0
  34. {kekik-1.7.1 → kekik-1.7.3}/Kekik/qr_ver.py +0 -0
  35. {kekik-1.7.1 → kekik-1.7.3}/Kekik/ses_fetis.py +0 -0
  36. {kekik-1.7.1 → kekik-1.7.3}/Kekik/slugify.py +0 -0
  37. {kekik-1.7.1 → kekik-1.7.3}/Kekik/terminal_baslik.py +0 -0
  38. {kekik-1.7.1 → kekik-1.7.3}/Kekik/txt_fetis.py +0 -0
  39. {kekik-1.7.1 → kekik-1.7.3}/Kekik/unicode_tr.py +0 -0
  40. {kekik-1.7.1 → kekik-1.7.3}/Kekik/zaman_donustur.py +0 -0
  41. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/SOURCES.txt +0 -0
  42. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/dependency_links.txt +0 -0
  43. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/entry_points.txt +0 -0
  44. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/requires.txt +0 -0
  45. {kekik-1.7.1 → kekik-1.7.3}/Kekik.egg-info/top_level.txt +0 -0
  46. {kekik-1.7.1 → kekik-1.7.3}/LICENSE +0 -0
  47. {kekik-1.7.1 → kekik-1.7.3}/MANIFEST.in +0 -0
  48. {kekik-1.7.1 → kekik-1.7.3}/README.md +0 -0
  49. {kekik-1.7.1 → kekik-1.7.3}/setup.cfg +0 -0
@@ -0,0 +1,492 @@
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 client (Redis yoksa fallback yapılacak)
13
+ import redis.asyncio as redisAsync
14
+ import redis as redis
15
+
16
+ # -----------------------------------------------------
17
+ # Sabitler ve Yardımcı Fonksiyonlar
18
+ # -----------------------------------------------------
19
+ UNLIMITED = None
20
+
21
+ REDIS_HOST = "127.0.0.1"
22
+ REDIS_PORT = 6379
23
+ REDIS_DB = 0
24
+ REDIS_PASS = None
25
+
26
+ def normalize_for_key(value):
27
+ """
28
+ Cache key oluşturma amacıyla verilen değeri normalize eder.
29
+ - Basit tipler (int, float, str, bool, None) doğrudan kullanılır.
30
+ - dict: Anahtarları sıralı olarak normalize eder.
31
+ - list/tuple: Elemanları normalize eder.
32
+ - Diğer: Sadece sınıf ismi kullanılır.
33
+ """
34
+ if isinstance(value, (int, float, str, bool, type(None))):
35
+ return value
36
+
37
+ elif isinstance(value, dict):
38
+ return {k: normalize_for_key(value[k]) for k in sorted(value)}
39
+
40
+ elif isinstance(value, (list, tuple)):
41
+ return [normalize_for_key(item) for item in value]
42
+
43
+ else:
44
+ return value.__class__.__name__
45
+
46
+ def simple_cache_key(func, args, kwargs) -> str:
47
+ """
48
+ Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
49
+ Oluşturulan stringin sonuna MD5 hash eklenebilir.
50
+ """
51
+ base_key = f"{func.__module__}.{func.__qualname__}"
52
+
53
+ if args:
54
+ norm_args = [normalize_for_key(arg) for arg in args]
55
+ base_key += f"|{norm_args}"
56
+
57
+ if kwargs:
58
+ norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
59
+ base_key += f"|{str(sorted(norm_kwargs.items()))}"
60
+
61
+ hashed = md5(base_key.encode('utf-8')).hexdigest()
62
+ return f"{base_key}" # |{hashed}
63
+
64
+ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
65
+ """
66
+ Cache key'ini oluşturur.
67
+ - is_fastapi=False ise simple_cache_key() kullanılır.
68
+ - True ise FastAPI Request nesnesine göre özel key oluşturulur.
69
+ """
70
+ if not is_fastapi:
71
+ return simple_cache_key(func, args, kwargs)
72
+
73
+ # FastAPI: request ilk argümandan ya da kwargs'dan alınır.
74
+ request = args[0] if args else kwargs.get("request")
75
+
76
+ if request.method == "GET":
77
+ # Eğer query_params boşsa {} olarak ayarla
78
+ veri = dict(request.query_params) if request.query_params else {}
79
+ else:
80
+ try:
81
+ veri = await request.json()
82
+ except Exception:
83
+ form_data = await request.form()
84
+ veri = dict(form_data.items())
85
+
86
+ # Eğer "kurek" gibi özel parametreler varsa temizleyebilirsiniz:
87
+ veri.pop("kurek", None)
88
+
89
+ args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
90
+ return f"{request.url.path}?{veri}"
91
+
92
+ # -----------------------------------------------------
93
+ # Senkron Cache (RAM) Sınıfı
94
+ # -----------------------------------------------------
95
+
96
+ class SyncCache:
97
+ """
98
+ Senkron fonksiyonlar için basit in-memory cache.
99
+ TTL süresi dolan veriler periyodik olarak arka plan thread’iyle temizlenir.
100
+ """
101
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
102
+ self._ttl = ttl
103
+ self._data = {}
104
+ self._times = {}
105
+
106
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
107
+ self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
108
+
109
+ # Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
110
+ self._lock = threading.RLock()
111
+ self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
112
+ self._cleanup_thread.start()
113
+
114
+ def _auto_cleanup(self):
115
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
116
+ while True:
117
+ time.sleep(self._cleanup_interval)
118
+ with self._lock:
119
+ keys = list(self._data.keys())
120
+ for key in keys:
121
+ self.remove_if_expired(key)
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
+
132
+ def remove_if_expired(self, key):
133
+ """
134
+ Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
135
+ Thread güvenliği sağlamak için lock kullanılır.
136
+ """
137
+ with self._lock:
138
+ if self._is_expired(key):
139
+ self._data.pop(key, None)
140
+ self._times.pop(key, None)
141
+ # konsol.log(f"[red][-] {key}")
142
+
143
+ def __getitem__(self, key):
144
+ with self._lock:
145
+ self.remove_if_expired(key)
146
+ veri = self._data[key]
147
+ # konsol.log(f"[yellow][~] {key}")
148
+ return veri
149
+
150
+ def __setitem__(self, key, value):
151
+ with self._lock:
152
+ self._data[key] = value
153
+ if self._ttl is not UNLIMITED:
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
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
+ # -----------------------------------------------------
233
+
234
+ class AsyncCache:
235
+ """
236
+ Temel in-memory asenkron cache.
237
+ """
238
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
239
+ """
240
+ :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
241
+ :param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
242
+ """
243
+ self._ttl = ttl
244
+ self._data = {}
245
+ self._times = {}
246
+ self.futures = {}
247
+
248
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
249
+ self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
250
+
251
+ # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
252
+ try:
253
+ self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
254
+ except RuntimeError:
255
+ self._cleanup_task = None
256
+
257
+ async def _auto_cleanup(self):
258
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
259
+ while True:
260
+ await asyncio.sleep(self._cleanup_interval)
261
+ for key in list(self._data.keys()):
262
+ self.remove_if_expired(key)
263
+
264
+ def ensure_cleanup_task(self):
265
+ """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
266
+ if self._cleanup_task is None:
267
+ try:
268
+ self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
269
+ except RuntimeError:
270
+ pass # Yine loop yoksa yapacak bir şey yok
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
+
284
+ async def get(self, key):
285
+ """
286
+ Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
287
+ Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
288
+ """
289
+ # Cleanup task'in aktif olduğundan emin olun.
290
+ self.ensure_cleanup_task()
291
+ # Eğer key'in süresi dolmuşsa, kaldırın.
292
+ self.remove_if_expired(key)
293
+
294
+ try:
295
+ # Cache içerisinde key varsa direkt değeri döndür.
296
+ value = self._data[key]
297
+ # konsol.log(f"[yellow][~] {key}")
298
+ return value
299
+ except KeyError:
300
+ # Eğer key cache'de yoksa, aynı key ile başlatılmış future varsa onu bekle.
301
+ future = self.futures.get(key)
302
+ if future:
303
+ await future
304
+ # Future tamamlandığında sonucu döndür.
305
+ value = future.result()
306
+ # konsol.log(f"[yellow][?] {key}")
307
+ return value
308
+ # Eğer future da yoksa, key bulunamadığına dair hata fırlat.
309
+ raise KeyError(key)
310
+
311
+ async def set(self, key, value):
312
+ """Belirtilen key için cache'e değer ekler."""
313
+ self.ensure_cleanup_task()
314
+ self._data[key] = value
315
+ if self._ttl is not UNLIMITED:
316
+ self._times[key] = time.time()
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)
390
+
391
+ # -----------------------------------------------------
392
+ # Cache'e Hesaplanmış Sonucu Yazma Yardımcı Fonksiyonları
393
+ # -----------------------------------------------------
394
+
395
+ def _sync_maybe_cache(func, key, result, unless):
396
+ """Senkron fonksiyon sonucu için cache kaydı oluşturur."""
397
+ if unless is None or not unless(result):
398
+ func.__cache[key] = result
399
+ # konsol.log(f"[green][+] {key}")
400
+
401
+ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
402
+ """
403
+ Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
404
+ Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
405
+ """
406
+ # __cache'den cache nesnesini alıyoruz.
407
+ cache = func.__cache
408
+
409
+ # Aynı key için aktif bir future varsa, onun sonucunu döndür.
410
+ if key in cache.futures:
411
+ return await cache.futures[key]
412
+
413
+ # Yeni future oluşturuluyor ve cache.futures'e ekleniyor.
414
+ future = asyncio.Future()
415
+ cache.futures[key] = future
416
+
417
+ try:
418
+ # Asenkron fonksiyonu çalıştır ve sonucu elde et.
419
+ result = await func(*args, **kwargs)
420
+ future.set_result(result)
421
+
422
+ # unless koşuluna göre cache'e ekleme yap.
423
+ if unless is None or not unless(result):
424
+ await cache.set(key, result)
425
+ # konsol.log(f"[green][+] {key}")
426
+
427
+ return result
428
+ except Exception as exc:
429
+ future.cancel()
430
+ raise exc
431
+ finally:
432
+ # İşlem tamamlandığında future'ı temizle.
433
+ cache.futures.pop(key, None)
434
+
435
+ # -----------------------------------------------------
436
+ # kekik_cache Dekoratörü (Senkrondan Asenkrona)
437
+ # -----------------------------------------------------
438
+
439
+ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
440
+ """
441
+ Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
442
+
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)
452
+ async def bakalim(param):
453
+ return param
454
+ """
455
+ # Parametresiz kullanım durumunda
456
+ if callable(ttl):
457
+ return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
458
+
459
+ def decorator(func):
460
+ if asyncio.iscoroutinefunction(func):
461
+ # Asenkron fonksiyonlar için cache türünü seçelim:
462
+
463
+ func.__cache = HybridAsyncCache(ttl) if use_redis else AsyncCache(ttl)
464
+
465
+ @wraps(func)
466
+ async def async_wrapper(*args, **kwargs):
467
+ key = await make_cache_key(func, args, kwargs, is_fastapi)
468
+
469
+ try:
470
+ return await func.__cache.get(key)
471
+ except KeyError:
472
+ return await _async_compute_and_cache(func, key, unless, *args, **kwargs)
473
+
474
+ return async_wrapper
475
+ else:
476
+ # Senkron fonksiyonlar için
477
+ func.__cache = HybridSyncCache(ttl) if use_redis else SyncCache(ttl)
478
+
479
+ @wraps(func)
480
+ def sync_wrapper(*args, **kwargs):
481
+ key = simple_cache_key(func, args, kwargs)
482
+
483
+ try:
484
+ return func.__cache[key]
485
+ except KeyError:
486
+ result = func(*args, **kwargs)
487
+ _sync_maybe_cache(func, key, result, unless)
488
+ return result
489
+
490
+ return sync_wrapper
491
+
492
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Kekik
3
- Version: 1.7.1
3
+ Version: 1.7.3
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.1
3
+ Version: 1.7.3
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.1",
9
+ version = "1.7.3",
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,338 +0,0 @@
1
- # ! https://github.com/Fatal1ty/aiocached
2
-
3
- from .cli import konsol
4
- from functools import wraps
5
- from time import time, sleep
6
- from hashlib import md5
7
- from urllib.parse import urlencode
8
- import asyncio, threading
9
-
10
- # -----------------------------------------------------
11
- # Yardımcı Fonksiyonlar
12
- # -----------------------------------------------------
13
-
14
- UNLIMITED = None
15
-
16
- def normalize_for_key(value):
17
- """
18
- Cache key oluşturma amacıyla verilen değeri normalize eder.
19
- - Basit tipler (int, float, str, bool, None) direk kullanılır.
20
- - dict: Anahtarları sıralı olarak normalize eder.
21
- - list/tuple: Elemanları normalize eder.
22
- - Diğer: Sadece sınıf ismi kullanılır.
23
- """
24
- if isinstance(value, (int, float, str, bool, type(None))):
25
- return value
26
-
27
- elif isinstance(value, dict):
28
- return {k: normalize_for_key(value[k]) for k in sorted(value)}
29
-
30
- elif isinstance(value, (list, tuple)):
31
- return [normalize_for_key(item) for item in value]
32
-
33
- else:
34
- return value.__class__.__name__
35
-
36
- def simple_cache_key(func, args, kwargs) -> str:
37
- """
38
- Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
39
- Oluşturulan stringin sonuna MD5 hash eklenir.
40
- """
41
- base_key = f"{func.__module__}.{func.__qualname__}"
42
-
43
- if args:
44
- norm_args = [normalize_for_key(arg) for arg in args]
45
- base_key += f"|{norm_args}"
46
-
47
- if kwargs:
48
- norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
49
- base_key += f"|{str(sorted(norm_kwargs.items()))}"
50
-
51
- hashed = md5(base_key.encode('utf-8')).hexdigest()
52
- return f"{base_key}" # |{hashed}
53
-
54
- async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
55
- """
56
- Cache key'ini oluşturur.
57
- - is_fastapi=False ise simple_cache_key() kullanılır.
58
- - True ise FastAPI Request nesnesine göre özel key oluşturulur.
59
- """
60
- if not is_fastapi:
61
- return simple_cache_key(func, args, kwargs)
62
-
63
- # FastAPI: request ilk argüman ya da kwargs'dan alınır
64
- request = args[0] if args else kwargs.get("request")
65
-
66
- if request.method == "GET":
67
- # Eğer query_params boşsa {} olarak ayarla
68
- veri = dict(request.query_params) if request.query_params else {}
69
- else:
70
- try:
71
- veri = await request.json()
72
- except Exception:
73
- form_data = await request.form()
74
- veri = dict(form_data.items())
75
-
76
- args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
77
- return f"{request.url.path}?{veri}"
78
-
79
-
80
- # -----------------------------------------------------
81
- # In-Memory (RAM) Cache
82
- # -----------------------------------------------------
83
-
84
- class Cache:
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):
101
- """
102
- Basit in-memory cache yapısı.
103
- TTL (time-to-live) süresi dolan veriler otomatik olarak temizlenir.
104
- Bu versiyonda, otomatik temizleme işlevselliği bir thread ile sağlanır.
105
- """
106
- def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
107
- super().__init__(ttl)
108
-
109
- # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
110
- self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
111
- self._lock = threading.RLock()
112
-
113
- # Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
114
- self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
115
- self._cleanup_thread.start()
116
-
117
- def _auto_cleanup(self):
118
- """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
119
- while True:
120
- sleep(self._cleanup_interval)
121
- with self._lock:
122
- keys = list(self._data.keys())
123
- for key in keys:
124
- self.remove_if_expired(key)
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
- # konsol.log(f"[green][+] {key}")
148
- if self._ttl is not UNLIMITED:
149
- self._times[key] = time()
150
-
151
-
152
- class AsyncCache(Cache):
153
- """
154
- Asenkron işlemleri destekleyen cache yapısı.
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.
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
- super().__init__(ttl)
164
- self.futures = {}
165
-
166
- self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
167
- # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
168
- try:
169
- self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
170
- except RuntimeError:
171
- self._cleanup_task = None
172
-
173
- async def _auto_cleanup(self):
174
- """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
175
- while True:
176
- await asyncio.sleep(self._cleanup_interval)
177
- # _data kopyasını almak, üzerinde dönüp silme yaparken hata almamak için.
178
- keys = list(self._data.keys())
179
- for key in keys:
180
- self.remove_if_expired(key)
181
-
182
- def ensure_cleanup_task(self):
183
- """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
184
- if self._cleanup_task is None:
185
- try:
186
- self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
187
- except RuntimeError:
188
- pass # Yine loop yoksa yapacak bir şey yok
189
-
190
- async def get(self, key):
191
- """
192
- Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
193
- Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
194
- """
195
- self.ensure_cleanup_task()
196
- self.remove_if_expired(key)
197
-
198
- try:
199
- veri = self._data[key]
200
- # konsol.log(f"[yellow][~] {key}")
201
- return veri
202
- except KeyError as e:
203
- future = self.futures.get(key)
204
- if future:
205
- await future
206
- veri = future.result()
207
- # konsol.log(f"[yellow][?] {key}")
208
- return veri
209
-
210
- raise e
211
-
212
- def remove_if_expired(self, key):
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):
223
- self._data[key] = value
224
- if self._ttl is not UNLIMITED:
225
- self._times[key] = time()
226
-
227
-
228
- # -----------------------------------------------------
229
- # Fonksiyonun Sonucunu Hesaplayıp Cache'e Yazma
230
- # -----------------------------------------------------
231
-
232
- def _sync_maybe_cache(func, key, result, unless):
233
- """Senkron sonuç için cache kaydını oluşturur (unless koşuluna bakarak)."""
234
- if unless is None or not unless(result):
235
- func.__cache[key] = result
236
- # konsol.log(f"[green][+] {key}")
237
-
238
- async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
239
- """
240
- Asenkron fonksiyon sonucunu hesaplar ve cache'e ekler.
241
- Aynı key için işlem devam ediyorsa, mevcut sonucu bekler.
242
- Sonuç, unless(result) True değilse cache'e eklenir.
243
- """
244
- # __cache'den cache nesnesini alıyoruz.
245
- cache = func.__cache
246
-
247
- # Aynı key için aktif bir future varsa, onun sonucunu döndür.
248
- if key in cache.futures:
249
- return await cache.futures[key]
250
-
251
- # Yeni future oluşturuluyor ve cache.futures'e ekleniyor.
252
- future = asyncio.Future()
253
- cache.futures[key] = future
254
-
255
- try:
256
- # Asenkron fonksiyonu çalıştır ve sonucu elde et.
257
- result = await func(*args, **kwargs)
258
- future.set_result(result)
259
-
260
- # unless koşuluna göre cache'e ekleme yap.
261
- if unless is None or not unless(result):
262
- cache[key] = result
263
- # konsol.log(f"[green][+] {key}")
264
-
265
- return result
266
- except Exception as exc:
267
- future.cancel()
268
- raise exc
269
- finally:
270
- # İşlem tamamlandığında future'ı temizle.
271
- cache.futures.pop(key, None)
272
-
273
-
274
- # -----------------------------------------------------
275
- # Dekoratör: kekik_cache
276
- # -----------------------------------------------------
277
-
278
- def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
279
- """
280
- Bir fonksiyon veya coroutine'in sonucunu cache'ler.
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.:
298
-
299
- @kekik_cache(ttl=15, unless=lambda sonuc: bool(sonuc is None))
300
- async def bakalim(param):
301
- # Burada cache işlemi yapılır.
302
- return param
303
- """
304
- # Parametresiz kullanıldığında
305
- if callable(ttl):
306
- return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
307
-
308
- def decorator(func):
309
- if asyncio.iscoroutinefunction(func):
310
- func.__cache = AsyncCache(ttl)
311
-
312
- @wraps(func)
313
- async def async_wrapper(*args, **kwargs):
314
- key = await make_cache_key(func, args, kwargs, is_fastapi)
315
-
316
- try:
317
- return await func.__cache.get(key)
318
- except KeyError:
319
- return await _async_compute_and_cache(func, key, unless, *args, **kwargs)
320
-
321
- return async_wrapper
322
- else:
323
- func.__cache = SyncCache(ttl)
324
-
325
- @wraps(func)
326
- def sync_wrapper(*args, **kwargs):
327
- key = simple_cache_key(func, args, kwargs)
328
-
329
- try:
330
- return func.__cache[key]
331
- except KeyError:
332
- result = func(*args, **kwargs)
333
- _sync_maybe_cache(func, key, result, unless)
334
- return result
335
-
336
- return sync_wrapper
337
-
338
- 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