Kekik 1.7.2__py3-none-any.whl → 1.7.9__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/Sifreleme/Packer.py CHANGED
@@ -4,9 +4,49 @@ import re
4
4
 
5
5
  class Packer:
6
6
  """
7
- P.A.C.K.E.R. sıkıştırma ve çözme işlemleri için bir sınıf.
7
+ P.A.C.K.E.R. sıkıştırma ve çözme işlemleri için kapsamlı bir sınıf.
8
8
  ! » https://github.com/beautifier/js-beautify/blob/main/python/jsbeautifier/unpackers/packer.py
9
9
  """
10
+
11
+ # Regex kalıpları - daha gevşek, farklı varyasyonları yakalayabilecek şekilde
12
+ PACKED_PATTERN = re.compile(
13
+ r"\}\s*\(\s*['\"](.*?)['\"],\s*(\d+),\s*(\d+),\s*['\"](.+?)['\"]\.split\(['\"]\\?\|['\"]\)",
14
+ re.IGNORECASE | re.MULTILINE | re.DOTALL
15
+ )
16
+
17
+ # Alternatif regex pattern, farklı formatlarda paketlenmiş kodu yakalamak için
18
+ ALTERNATIVE_PATTERNS = [
19
+ # Standart pattern
20
+ re.compile(
21
+ r"\}\('(.*)',\s*(\d+),\s*(\d+),\s*'(.*?)'\.split\('\|'\)",
22
+ re.IGNORECASE | re.MULTILINE | re.DOTALL
23
+ ),
24
+ # Daha gevşek pattern
25
+ re.compile(
26
+ r"\}\s*\(\s*['\"](.*?)['\"],\s*(\d+),\s*(\d+),\s*['\"](.+?)['\"]\.split\(['\"]\\?\|['\"]\)",
27
+ re.IGNORECASE | re.MULTILINE | re.DOTALL
28
+ ),
29
+ # Eval formatı
30
+ re.compile(
31
+ r"eval\(function\(p,a,c,k,e,(?:r|d|)\)\{.*?return p\}(.*?\.split\('\|'\))",
32
+ re.IGNORECASE | re.MULTILINE | re.DOTALL
33
+ )
34
+ ]
35
+
36
+ # Kelime değiştirme deseni
37
+ REPLACE_PATTERN = re.compile(
38
+ r"\b\w+\b",
39
+ re.IGNORECASE | re.MULTILINE
40
+ )
41
+
42
+ # Alfabeler
43
+ ALPHABET = {
44
+ 52: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP",
45
+ 54: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR",
46
+ 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
47
+ 95: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
48
+ }
49
+
10
50
  @staticmethod
11
51
  def clean_escape_sequences(source: str) -> str:
12
52
  """Kaçış dizilerini temizler."""
@@ -18,45 +58,142 @@ class Packer:
18
58
  @staticmethod
19
59
  def extract_arguments(source: str) -> tuple[str, list[str], int, int]:
20
60
  """P.A.C.K.E.R. formatındaki kaynak koddan argümanları çıkarır."""
21
- match = re.search(r"}\('(.*)',(\d+),(\d+),'(.*)'\.split\('\|'\)", source, re.DOTALL)
61
+ # Önce standart pattern ile dene
62
+ match = Packer.PACKED_PATTERN.search(source)
63
+
64
+ # Eğer bulunamazsa, alternatif pattern'leri dene
65
+ if not match:
66
+ for pattern in Packer.ALTERNATIVE_PATTERNS:
67
+ match = pattern.search(source)
68
+ if match:
69
+ break
22
70
 
23
71
  if not match:
24
- raise ValueError("Invalid P.A.C.K.E.R. source format.")
72
+ # Son çare: daha serbest bir string arama
73
+ if "'.split('|')" in source or '".split("|")' in source:
74
+ # Manuel olarak parçalama işlemi yap
75
+ try:
76
+ # Basit bir yaklaşım, çoğu vakada çalışır
77
+ parts = re.findall(r"\((['\"](.*?)['\"],\s*(\d+),\s*(\d+),\s*['\"](.*?)['\"]\.split", source)
78
+ if parts:
79
+ payload, radix, count, symtab = parts[0][1:]
80
+ return payload, symtab.split("|"), int(radix), int(count)
81
+ except Exception:
82
+ pass
83
+
84
+ raise ValueError("Invalid P.A.C.K.E.R. source format. Pattern not found.")
25
85
 
26
- payload, radix, count, symtab = match.groups()
86
+ # Eval formatını işle
87
+ if len(match.groups()) == 1:
88
+ # Eval formatı yakalandı, içeriği çıkar
89
+ eval_content = match.group(1)
90
+ if inner_match := re.search(r"\('(.*)',(\d+),(\d+),'(.*)'\)", eval_content):
91
+ payload, radix, count, symtab = inner_match.groups()
92
+ else:
93
+ raise ValueError("Cannot extract arguments from eval pattern")
94
+ else:
95
+ # Standart format yakalandı
96
+ payload, radix, count, symtab = match.groups()
27
97
 
28
98
  return payload, symtab.split("|"), int(radix), int(count)
29
99
 
30
100
  @staticmethod
31
- def convert_base(s: str, base: int) -> int:
32
- """Bir sayıyı belirli bir tabandan ondalık tabana çevirir."""
33
- alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
101
+ def unbase(value: str, base: int) -> int:
102
+ """
103
+ Verilen değeri belirtilen tabandan ondalık sayıya dönüştürür.
104
+ Geniş taban desteği (2-95 arası) sağlar.
105
+ """
106
+ # Standart Python taban dönüşümü (2-36 arası)
107
+ if 2 <= base <= 36:
108
+ try:
109
+ return int(value, base)
110
+ except ValueError:
111
+ return 0
34
112
 
35
- return sum(alphabet.index(char) * (base**idx) for idx, char in enumerate(reversed(s)))
113
+ # Geniş taban desteği (37-95 arası)
114
+ if base > 95:
115
+ raise ValueError(f"Desteklenmeyen taban: {base}")
36
116
 
117
+ # Uygun alfabeyi seç
118
+ if base > 62:
119
+ selector = 95
120
+ elif base > 54:
121
+ selector = 62
122
+ elif base > 52:
123
+ selector = 54
124
+ else:
125
+ selector = 52
126
+
127
+ # Alfabeden karakter-indeks sözlüğü oluştur
128
+ char_dict = {char: idx for idx, char in enumerate(Packer.ALPHABET[selector])}
129
+
130
+ # Değeri dönüştür
131
+ result = 0
132
+ for index, char in enumerate(reversed(value)):
133
+ digit = char_dict.get(char, 0)
134
+ result += digit * (base ** index)
135
+
136
+ return result
137
+
37
138
  @staticmethod
38
139
  def lookup_symbol(match: re.Match, symtab: list[str], radix: int) -> str:
39
140
  """Sembolleri arar ve yerine koyar."""
40
- word = match[0]
141
+ word = match[0]
142
+
143
+ try:
144
+ index = Packer.unbase(word, radix)
145
+ if 0 <= index < len(symtab):
146
+ replacement = symtab[index]
147
+ return replacement or word
148
+ except (ValueError, IndexError):
149
+ pass
41
150
 
42
- return symtab[Packer.convert_base(word, radix)] or word
151
+ return word
43
152
 
44
153
  @staticmethod
45
154
  def unpack(source: str) -> str:
46
- """P.A.C.K.E.R. formatındaki sıkıştırılmış bir kaynağı çözer."""
155
+ """
156
+ P.A.C.K.E.R. formatındaki sıkıştırılmış bir JavaScript kodunu çözer.
157
+ Birden fazla format ve varyasyonu destekler.
158
+ """
159
+ # Kaçış dizilerini temizle
47
160
  source = Packer.clean_escape_sequences(source)
48
161
 
49
- payload, symtab, radix, count = Packer.extract_arguments(source)
162
+ # Argümanları çıkar
163
+ try:
164
+ payload, symtab, radix, count = Packer.extract_arguments(source)
50
165
 
51
- if count != len(symtab):
52
- raise ValueError("Malformed P.A.C.K.E.R. symtab.")
166
+ # Sembol tablosunun doğruluğunu kontrol et (ancak sıkı değil)
167
+ if len(symtab) != count:
168
+ print(f"Uyarı: Sembol tablosu sayısı ({len(symtab)}) ile belirtilen sayı ({count}) eşleşmiyor, ancak devam ediliyor.")
53
169
 
54
- return re.sub(r"\b\w+\b", lambda match: Packer.lookup_symbol(match, symtab, radix), payload)
170
+ # Kelimeleri değiştir ve sonucu döndür
171
+ return Packer.REPLACE_PATTERN.sub(
172
+ lambda match: Packer.lookup_symbol(match, symtab, radix),
173
+ payload
174
+ )
175
+ except Exception as e:
176
+ # Detaylı hata mesajı
177
+ raise ValueError(f"Unpacking failed: {str(e)}\nSource preview: {source[:100]}...")
55
178
 
56
179
  @staticmethod
57
- def pack(source: str, radix: int = 62) -> str:
58
- """Bir metni P.A.C.K.E.R. formatında sıkıştırır."""
59
- # Bu işlev, simgeleri ve sıkıştırılmış metni yeniden oluşturmak için bir yol sağlar.
60
- # Ancak bu, belirli bir algoritma veya sıkıştırma tekniğine bağlıdır.
61
- # Gerçekleştirilmesi zor olabilir çünkü P.A.C.K.E.R.'ın spesifik sıkıştırma mantığını takip etmek gerekir.
62
- raise NotImplementedError("Packing function is not implemented.")
180
+ def detect_packed(source: str) -> bool:
181
+ """Verilen kodun P.A.C.K.E.R. formatında sıkıştırılmış olup olmadığını kontrol eder."""
182
+ # Standart pattern'i kontrol et
183
+ if Packer.PACKED_PATTERN.search(source):
184
+ return True
185
+
186
+ # Alternatif pattern'leri kontrol et
187
+ for pattern in Packer.ALTERNATIVE_PATTERNS:
188
+ if pattern.search(source):
189
+ return True
190
+
191
+ # Yaygın belirteçleri kontrol et
192
+ indicators = [
193
+ ".split('|')",
194
+ '.split("|")',
195
+ "function(p,a,c,k,e,",
196
+ "function(p, a, c, k, e, "
197
+ ]
198
+
199
+ return any(indicator in source for indicator in indicators)
Kekik/cache.py CHANGED
@@ -9,13 +9,23 @@ import threading
9
9
  import asyncio
10
10
  import pickle
11
11
 
12
- # Redis için asenkron client (Redis yoksa fallback yapılacak)
13
- import redis.asyncio as redis
12
+ # Redis client (Redis yoksa fallback yapılacak)
13
+ import redis.asyncio as redisAsync
14
+ import redis as redis
14
15
 
15
16
  # -----------------------------------------------------
16
17
  # Sabitler ve Yardımcı Fonksiyonlar
17
18
  # -----------------------------------------------------
18
- UNLIMITED = None
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
+ # FastAPI için cache'ten hariç tutulacak parametreler ve HTTP status kodları
27
+ CACHE_IGNORE_PARAMS = {"kurek", "debug", "_", "t", "timestamp"}
28
+ CACHE_IGNORE_STATUS_CODES = {400, 401, 403, 404, 500, 501, 502, 503}
19
29
 
20
30
  def normalize_for_key(value):
21
31
  """
@@ -43,6 +53,7 @@ def simple_cache_key(func, args, kwargs) -> str:
43
53
  Oluşturulan stringin sonuna MD5 hash eklenebilir.
44
54
  """
45
55
  base_key = f"{func.__module__}.{func.__qualname__}"
56
+ base_key = "|".join(base_key.split("."))
46
57
 
47
58
  if args:
48
59
  norm_args = [normalize_for_key(arg) for arg in args]
@@ -55,11 +66,12 @@ def simple_cache_key(func, args, kwargs) -> str:
55
66
  hashed = md5(base_key.encode('utf-8')).hexdigest()
56
67
  return f"{base_key}" # |{hashed}
57
68
 
58
- async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
69
+ async def make_cache_key(func, args, kwargs, is_fastapi=False, include_auth=False) -> str:
59
70
  """
60
71
  Cache key'ini oluşturur.
61
72
  - is_fastapi=False ise simple_cache_key() kullanılır.
62
73
  - True ise FastAPI Request nesnesine göre özel key oluşturulur.
74
+ - include_auth=True ise authorization header'ı key'e dahil edilir.
63
75
  """
64
76
  if not is_fastapi:
65
77
  return simple_cache_key(func, args, kwargs)
@@ -67,21 +79,40 @@ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
67
79
  # FastAPI: request ilk argümandan ya da kwargs'dan alınır.
68
80
  request = args[0] if args else kwargs.get("request")
69
81
 
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
+ # Request bulunamamışsa fallback
83
+ if request is None or not hasattr(request, 'method'):
84
+ return simple_cache_key(func, args, kwargs)
82
85
 
83
- args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
84
- return f"{request.url.path}?{veri}"
86
+ try:
87
+ if request.method == "GET":
88
+ # Eğer query_params boşsa {} olarak ayarla
89
+ veri = dict(request.query_params) if request.query_params else {}
90
+ else:
91
+ try:
92
+ content_type = request.headers.get("content-type", "")
93
+ if "application/json" in content_type:
94
+ veri = await request.json()
95
+ else:
96
+ form_data = await request.form()
97
+ veri = dict(form_data.items())
98
+ except Exception:
99
+ veri = {}
100
+
101
+ # Sistem parametrelerini temizle
102
+ for param in CACHE_IGNORE_PARAMS:
103
+ veri.pop(param, None)
104
+
105
+ # Authorization header'ı dahil et (user-specific cache için)
106
+ if include_auth and "authorization" in request.headers:
107
+ auth_hash = md5(request.headers["authorization"].encode()).hexdigest()
108
+ veri[f"_auth_hash"] = auth_hash
109
+
110
+ args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
111
+ return f"{request.url.path}|{veri}" if veri else f"{request.url.path}"
112
+ except Exception as e:
113
+ # Herhangi bir hata durumunda fallback
114
+ konsol.log(f"[yellow]FastAPI cache key oluşturma hatası: {e}, basit key kullanılıyor")
115
+ return simple_cache_key(func, args, kwargs)
85
116
 
86
117
  # -----------------------------------------------------
87
118
  # Senkron Cache (RAM) Sınıfı
@@ -90,29 +121,35 @@ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
90
121
  class SyncCache:
91
122
  """
92
123
  Senkron fonksiyonlar için basit in-memory cache.
93
- TTL süresi dolan veriler periyodik olarak arka plan threadiyle temizlenir.
124
+ TTL süresi dolan veriler periyodik olarak arka plan thread'iyle temizlenir.
94
125
  """
95
- def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
96
- self._ttl = ttl
97
- self._data = {}
98
- self._times = {}
126
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60, max_size=10000):
127
+ self._ttl = ttl
128
+ self._data = {}
129
+ self._times = {}
130
+ self._access_counts = {} # LRU tracker
131
+ self._max_size = max_size
99
132
 
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
133
+ # TTL sınırsız değilse, cleanup_interval kullanıyoruz.
134
+ self._cleanup_interval = cleanup_interval if ttl is UNLIMITED else min(ttl, cleanup_interval)
102
135
 
103
136
  # 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)
137
+ self._lock = threading.RLock()
138
+ self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
139
+ self._cleanup_thread.daemon = True
106
140
  self._cleanup_thread.start()
107
141
 
108
142
  def _auto_cleanup(self):
109
143
  """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
110
144
  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)
145
+ try:
146
+ time.sleep(self._cleanup_interval)
147
+ with self._lock:
148
+ keys = list(self._data.keys())
149
+ for key in keys:
150
+ self.remove_if_expired(key)
151
+ except Exception as e:
152
+ konsol.log(f"[red]Cache cleanup hatası: {e}")
116
153
 
117
154
  def _is_expired(self, key):
118
155
  """Belirtilen key'in süresi dolmuşsa True döner."""
@@ -138,15 +175,108 @@ class SyncCache:
138
175
  with self._lock:
139
176
  self.remove_if_expired(key)
140
177
  veri = self._data[key]
178
+ # LRU tracker'ı güncelle
179
+ self._access_counts[key] = self._access_counts.get(key, 0) + 1
141
180
  # konsol.log(f"[yellow][~] {key}")
142
181
  return veri
143
182
 
144
183
  def __setitem__(self, key, value):
145
184
  with self._lock:
146
- self._data[key] = value
185
+ # Kapasite kontrolü - LRU temizlemesi
186
+ if len(self._data) >= self._max_size and key not in self._data:
187
+ # En az kullanılan key'i bul ve sil
188
+ lru_key = min(self._access_counts, key=self._access_counts.get)
189
+ self._data.pop(lru_key, None)
190
+ self._times.pop(lru_key, None)
191
+ self._access_counts.pop(lru_key, None)
192
+ # konsol.log(f"[red][-] LRU eviction: {lru_key}")
193
+
194
+ self._data[key] = value
195
+ self._access_counts[key] = 0
147
196
  if self._ttl is not UNLIMITED:
148
197
  self._times[key] = time.time()
149
198
 
199
+ class HybridSyncCache:
200
+ """
201
+ Senkron işlemler için, öncelikle Redis cache kullanılmaya çalışılır.
202
+ Redis'ten veri alınamazsa ya da hata oluşursa, SyncCache (in-memory) fallback uygulanır.
203
+ """
204
+ def __init__(self, ttl=None, max_size=10000):
205
+ self._ttl = ttl
206
+ self._max_size = max_size
207
+
208
+ try:
209
+ self.redis = redis.Redis(
210
+ host = REDIS_HOST,
211
+ port = REDIS_PORT,
212
+ db = REDIS_DB,
213
+ password = REDIS_PASS,
214
+ decode_responses = False
215
+ )
216
+ self.redis.ping()
217
+ except Exception as e:
218
+ konsol.log(f"[yellow]Redis bağlantısı başarısız, in-memory cache kullanılıyor: {e}")
219
+ self.redis = None
220
+
221
+ self.memory = SyncCache(ttl, max_size=max_size)
222
+
223
+ def get(self, key):
224
+ # Önce Redis ile deniyoruz:
225
+ if self.redis:
226
+ try:
227
+ data = self.redis.get(key)
228
+ except Exception as e:
229
+ # konsol.log(f"[yellow]Redis get hatası: {e}")
230
+ data = None
231
+ if data is not None:
232
+ try:
233
+ result = pickle.loads(data)
234
+ # konsol.log(f"[yellow][~] {key}")
235
+ return result
236
+ except Exception as e:
237
+ # Deserialize hatası durumunda fallback'e geç
238
+ # konsol.log(f"[yellow]Pickle deserialize hatası: {e}")
239
+ pass
240
+
241
+ # Redis'te veri yoksa, yerel cache'ten alıyoruz.
242
+ try:
243
+ return self.memory[key]
244
+ except KeyError:
245
+ raise KeyError(key)
246
+
247
+ def set(self, key, value):
248
+ try:
249
+ ser = pickle.dumps(value)
250
+ except Exception as e:
251
+ # Serialization hatası durumunda yerel cache'e yazalım.
252
+ # (TemplateResponse, FileResponse gibi pickle'lanamayan objeler için)
253
+ # konsol.log(f"[yellow]Pickle serialize hatası: {e}, in-memory cache kullanılıyor")
254
+ self.memory[key] = value
255
+ return
256
+
257
+ if self.redis:
258
+ try:
259
+ if self._ttl is not None:
260
+ self.redis.set(key, ser, ex=self._ttl)
261
+ else:
262
+ self.redis.set(key, ser)
263
+ return
264
+ except Exception as e:
265
+ # Redis'e yazılamazsa yerel cache'e geçelim.
266
+ # konsol.log(f"[yellow]Redis set hatası: {e}, in-memory cache kullanılıyor")
267
+ self.memory[key] = value
268
+ return
269
+ else:
270
+ # Redis kullanılmıyorsa direkt yerel cache'e yaz.
271
+ self.memory[key] = value
272
+
273
+ # HybridSyncCache'in subscriptable olması için:
274
+ def __getitem__(self, key):
275
+ return self.get(key)
276
+
277
+ def __setitem__(self, key, value):
278
+ self.set(key, value)
279
+
150
280
  # -----------------------------------------------------
151
281
  # Asenkron Cache (In-Memory) ve Redis Hybrid Cache
152
282
  # -----------------------------------------------------
@@ -155,18 +285,21 @@ class AsyncCache:
155
285
  """
156
286
  Temel in-memory asenkron cache.
157
287
  """
158
- def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
288
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60, max_size=10000):
159
289
  """
160
290
  :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
161
291
  :param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
292
+ :param max_size: Maksimum cache boyutu (en eski entry'ler silinir).
162
293
  """
163
- self._ttl = ttl
164
- self._data = {}
165
- self._times = {}
166
- self.futures = {}
294
+ self._ttl = ttl
295
+ self._data = {}
296
+ self._times = {}
297
+ self._access_counts = {} # LRU tracker
298
+ self.futures = {}
299
+ self._max_size = max_size
167
300
 
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
301
+ # TTL sınırsız değilse, cleanup_interval kullanıyoruz.
302
+ self._cleanup_interval = cleanup_interval if ttl is UNLIMITED else min(ttl, cleanup_interval)
170
303
 
171
304
  # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
172
305
  try:
@@ -177,9 +310,12 @@ class AsyncCache:
177
310
  async def _auto_cleanup(self):
178
311
  """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
179
312
  while True:
180
- await asyncio.sleep(self._cleanup_interval)
181
- for key in list(self._data.keys()):
182
- self.remove_if_expired(key)
313
+ try:
314
+ await asyncio.sleep(self._cleanup_interval)
315
+ for key in list(self._data.keys()):
316
+ self.remove_if_expired(key)
317
+ except Exception as e:
318
+ konsol.log(f"[red]Async cache cleanup hatası: {e}")
183
319
 
184
320
  def ensure_cleanup_task(self):
185
321
  """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
@@ -214,6 +350,8 @@ class AsyncCache:
214
350
  try:
215
351
  # Cache içerisinde key varsa direkt değeri döndür.
216
352
  value = self._data[key]
353
+ # LRU tracker'ı güncelle
354
+ self._access_counts[key] = self._access_counts.get(key, 0) + 1
217
355
  # konsol.log(f"[yellow][~] {key}")
218
356
  return value
219
357
  except KeyError:
@@ -231,28 +369,44 @@ class AsyncCache:
231
369
  async def set(self, key, value):
232
370
  """Belirtilen key için cache'e değer ekler."""
233
371
  self.ensure_cleanup_task()
234
- self._data[key] = value
372
+
373
+ # Kapasite kontrolü - LRU temizlemesi
374
+ if len(self._data) >= self._max_size and key not in self._data:
375
+ # En az kullanılan key'i bul ve sil
376
+ lru_key = min(self._access_counts, key=self._access_counts.get)
377
+ self._data.pop(lru_key, None)
378
+ self._times.pop(lru_key, None)
379
+ self._access_counts.pop(lru_key, None)
380
+ self.futures.pop(lru_key, None)
381
+ # konsol.log(f"[red][-] Async LRU eviction: {lru_key}")
382
+
383
+ self._data[key] = value
384
+ self._access_counts[key] = 0
235
385
  if self._ttl is not UNLIMITED:
236
386
  self._times[key] = time.time()
237
387
 
238
388
  class HybridAsyncCache:
239
389
  """
240
390
  Öncelikle Redis cache kullanılmaya çalışılır.
241
- Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCachee geçilir.
391
+ Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCache'e geçilir.
242
392
  """
243
- def __init__(self, ttl=UNLIMITED):
393
+ def __init__(self, ttl=UNLIMITED, max_size=10000):
244
394
  self._ttl = ttl
395
+ self._max_size = max_size
245
396
 
246
397
  try:
247
- self.redis = redis.Redis(
248
- host = "127.0.0.1",
249
- port = 6379,
398
+ self.redis = redisAsync.Redis(
399
+ host = REDIS_HOST,
400
+ port = REDIS_PORT,
401
+ db = REDIS_DB,
402
+ password = REDIS_PASS,
250
403
  decode_responses = False
251
404
  )
252
- except Exception:
405
+ except Exception as e:
406
+ konsol.log(f"[yellow]Async Redis bağlantısı başarısız, in-memory cache kullanılıyor: {e}")
253
407
  self.redis = None
254
408
 
255
- self.memory = AsyncCache(ttl)
409
+ self.memory = AsyncCache(ttl, max_size=max_size)
256
410
  self.futures = {}
257
411
 
258
412
  async def get(self, key):
@@ -265,15 +419,17 @@ class HybridAsyncCache:
265
419
  if self.redis:
266
420
  try:
267
421
  data = await self.redis.get(key)
268
- except Exception:
422
+ except Exception as e:
423
+ konsol.log(f"[yellow]Async Redis get hatası: {e}")
269
424
  return await self.memory.get(key)
270
425
  if data is not None:
271
426
  try:
272
427
  result = pickle.loads(data)
273
428
  # konsol.log(f"[yellow][~] {key}")
274
429
  return result
275
- except Exception:
430
+ except Exception as e:
276
431
  # Deserialize hatası durumunda in-memory cache'ten dene
432
+ konsol.log(f"[yellow]Async pickle deserialize hatası: {e}")
277
433
  return await self.memory.get(key)
278
434
  else:
279
435
  # Redis'te veri yoksa, in-memory cache'e bak
@@ -286,8 +442,10 @@ class HybridAsyncCache:
286
442
  # Önce veriyi pickle etmeyi deniyoruz.
287
443
  try:
288
444
  ser = pickle.dumps(value)
289
- except Exception:
445
+ except Exception as e:
290
446
  # Serialization hatası durumunda sadece in-memory cache'e yaz
447
+ # (TemplateResponse, FileResponse gibi pickle'lanamayan objeler için)
448
+ # konsol.log(f"[yellow]Async pickle serialize hatası: {e}, in-memory cache kullanılıyor")
291
449
  await self.memory.set(key, value)
292
450
  return
293
451
 
@@ -298,8 +456,9 @@ class HybridAsyncCache:
298
456
  else:
299
457
  await self.redis.set(key, ser)
300
458
  return
301
- except Exception:
459
+ except Exception as e:
302
460
  # Redis yazma hatası durumunda in-memory fallback
461
+ # konsol.log(f"[yellow]Async Redis set hatası: {e}, in-memory cache kullanılıyor")
303
462
  await self.memory.set(key, value)
304
463
  return
305
464
  else:
@@ -354,35 +513,61 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
354
513
  # kekik_cache Dekoratörü (Senkrondan Asenkrona)
355
514
  # -----------------------------------------------------
356
515
 
357
- def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
516
+ def kekik_cache(ttl=UNLIMITED, unless=None, use_redis=True, max_size=10000, is_fastapi=None, include_auth=False):
358
517
  """
359
518
  Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
519
+ FastAPI endpoint'leri otomatik detekt edilir (is_fastapi=None ise) veya manuel olarak belirtilebilir.
360
520
 
361
521
  Parametreler:
362
522
  - ttl: Cache'in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
363
523
  - 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.
524
+ - is_fastapi: True/False ile açıkça belirt, None ise otomatik detekt (Request nesnesine bakarak).
365
525
  - use_redis: Asenkron fonksiyonlarda Redis kullanımı (Hybrid cache) için True verilebilir.
526
+ - max_size: Cache'in maksimum boyutu. Kapasiteyi aşarsa LRU temizlemesi yapılır.
527
+ - include_auth: Authorization header'ını key'e dahil et (user-specific cache).
366
528
 
367
529
  Örnek Kullanım:
368
530
 
369
- @kekik_cache(ttl=15, unless=lambda sonuc: sonuc is None)
370
- async def bakalim(param):
371
- return param
531
+ # Basit fonksiyon
532
+ @kekik_cache(ttl=300)
533
+ async def hesapla(param):
534
+ return param * 2
535
+
536
+ # FastAPI endpoint (otomatik detekt)
537
+ @app.get("/users/{user_id}")
538
+ @kekik_cache(ttl=300, include_auth=True)
539
+ async def get_user(user_id: int, request: Request):
540
+ return {"id": user_id, "name": "..."}
541
+
542
+ # FastAPI endpoint (manuel belirtim)
543
+ @app.get("/data")
544
+ @kekik_cache(ttl=600, is_fastapi=True)
545
+ async def get_data(request: Request):
546
+ return {"data": "..."}
547
+
548
+ # Hata response'larını cache'leme
549
+ @app.get("/data")
550
+ @kekik_cache(ttl=300, unless=lambda r: r.status_code >= 400)
551
+ async def get_data(request: Request):
552
+ return {}
372
553
  """
373
554
  # Parametresiz kullanım durumunda
374
555
  if callable(ttl):
375
- return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
556
+ return kekik_cache(UNLIMITED, unless=unless, use_redis=use_redis, max_size=max_size, is_fastapi=is_fastapi, include_auth=include_auth)(ttl)
376
557
 
377
558
  def decorator(func):
378
559
  if asyncio.iscoroutinefunction(func):
379
560
  # Asenkron fonksiyonlar için cache türünü seçelim:
380
-
381
- func.__cache = HybridAsyncCache(ttl) if use_redis else AsyncCache(ttl)
561
+ func.__cache = HybridAsyncCache(ttl, max_size=max_size) if use_redis else AsyncCache(ttl, max_size=max_size)
382
562
 
383
563
  @wraps(func)
384
564
  async def async_wrapper(*args, **kwargs):
385
- key = await make_cache_key(func, args, kwargs, is_fastapi)
565
+ # is_fastapi otomatik deteksiyon (None ise) ya da manuel belirtim
566
+ detect_fastapi = is_fastapi
567
+ if detect_fastapi is None:
568
+ detect_fastapi = args and hasattr(args[0], "url") and hasattr(args[0], "method")
569
+
570
+ key = await make_cache_key(func, args, kwargs, detect_fastapi, include_auth=include_auth)
386
571
 
387
572
  try:
388
573
  return await func.__cache.get(key)
@@ -392,7 +577,7 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
392
577
  return async_wrapper
393
578
  else:
394
579
  # Senkron fonksiyonlar için
395
- func.__cache = SyncCache(ttl)
580
+ func.__cache = HybridSyncCache(ttl, max_size=max_size) if use_redis else SyncCache(ttl, max_size=max_size)
396
581
 
397
582
  @wraps(func)
398
583
  def sync_wrapper(*args, **kwargs):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: Kekik
3
- Version: 1.7.2
3
+ Version: 1.7.9
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
@@ -26,6 +26,7 @@ Requires-Dist: simplejson
26
26
  Requires-Dist: rich
27
27
  Requires-Dist: tabulate
28
28
  Requires-Dist: pycryptodome
29
+ Requires-Dist: redis
29
30
  Dynamic: author
30
31
  Dynamic: author-email
31
32
  Dynamic: classifier
@@ -34,6 +35,7 @@ Dynamic: description-content-type
34
35
  Dynamic: home-page
35
36
  Dynamic: keywords
36
37
  Dynamic: license
38
+ Dynamic: license-file
37
39
  Dynamic: requires-dist
38
40
  Dynamic: requires-python
39
41
  Dynamic: summary
@@ -315,6 +317,11 @@ var played = 0;
315
317
  eval_jwSetup = re.compile(r'\};\s*(eval\(function[\s\S]*?)var played = \d+;').findall(veri)[0]
316
318
  print(Packer.unpack(Packer.unpack(eval_jwSetup)))
317
319
  # jwSetup.sources=[{"default":true,"file":"\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x32\x2e\x69\x6d\x61\x67\x65\x73\x70\x6f\x74\x2e\x62\x75\x7a\x7a\x2f\x66\x32\x2f\x4e\x74\x4f\x31\x4e\x51\x5a\x6a\x44\x51\x41\x6b\x78\x6c\x58\x45\x47\x33\x6c\x62\x66\x62\x30\x31\x79\x74\x70\x57\x66\x4e\x30\x66\x62\x66\x50\x58\x5a\x55\x31\x6a\x50\x77\x5a\x6d\x48\x71\x58\x41\x37\x6c\x6d\x6d\x4b\x67\x47\x59\x31\x66\x47\x42\x6d\x6c\x38\x68\x32\x7a\x33\x4f\x5a\x69\x4f\x63\x4c\x6b\x51\x70\x7a\x57\x78\x4b\x45\x4c\x57\x42\x63\x79\x4d\x74\x75\x55\x44\x57\x46\x4e\x6c\x69\x64\x70\x46\x46\x65\x6e\x65\x64\x66\x48\x30\x69\x74\x66\x59\x67\x38\x52\x47\x41\x6b\x38\x6c\x76\x72\x31","label":"0","type":"hls","preload":"none"}];var mu=getLocation(jwSetup.sources[0].file);
320
+
321
+ # ! Veya
322
+
323
+ while Packer.detect_packed(veri):
324
+ veri = Packer.unpack(veri)
318
325
  ```
319
326
 
320
327
  ### **[CryptoJS](https://github.com/keyiflerolsun/Kekik/blob/main/Kekik/Sifreleme/CryptoJS.py)**
@@ -367,6 +374,26 @@ print(NaysHash().generate_xtoken(
367
374
  # EygcmEIe3aU0TWIubaQTuBwbrqpY7HFcNDajlSKCT5c=
368
375
  ```
369
376
 
377
+ ### **[kekik_cache](https://github.com/keyiflerolsun/Kekik/blob/main/Kekik/cache.py)**
378
+ ```python
379
+ import Kekik.cache as cache
380
+ cache.REDIS_HOST = "127.0.0.1"
381
+ cache.REDIS_PORT = 6379
382
+ cache.REDIS_DB = 0
383
+ cache.REDIS_PASS = None
384
+ kekik_cache = cache.kekik_cache
385
+
386
+ @kekik_cache(ttl=5)
387
+ def sync_func(x, y):
388
+ time.sleep(1)
389
+ return x + y
390
+
391
+ @kekik_cache(ttl=5)
392
+ async def async_func(x, y):
393
+ await asyncio.sleep(1)
394
+ return x * y
395
+ ```
396
+
370
397
  ### **[dict2json](https://github.com/keyiflerolsun/Kekik/blob/main/Kekik/dict2json.py)**
371
398
  ### **[dosya_indir](https://github.com/keyiflerolsun/Kekik/blob/main/Kekik/dosya_indir.py)**
372
399
  ### **[benim_hwid](https://github.com/keyiflerolsun/Kekik/blob/main/Kekik/hwid_kontrol.py)**
@@ -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=vJ9b7eAyKH0Z2JCW5JIDsyLkNwXvWkS3vnJpv_mj0Us,14892
5
+ Kekik/cache.py,sha256=to3ihJyxPasQDr5QO1hJPRVDbidBSILK2kGKUfPZ2wE,23308
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
@@ -27,16 +27,16 @@ Kekik/Sifreleme/AESManager.py,sha256=eYgbHANtYrWFJgLeUuj63tlL0Yn8gyPCJYbXfWlpNbo
27
27
  Kekik/Sifreleme/CryptoJS.py,sha256=qDlgTaSXcs5jF4DNmjuwK5CL3VL1P7xyJzDTof1--As,3126
28
28
  Kekik/Sifreleme/HexCodec.py,sha256=fB1ZGBYCQLUUiZNXqn0sxYhEMhxPoyC1BPYkRJ5G7hY,900
29
29
  Kekik/Sifreleme/NaysHash.py,sha256=CJVlyHCXSv8JN8fitF2LQHLcd-opY6zergcgmX3pj_Y,2234
30
- Kekik/Sifreleme/Packer.py,sha256=4DWFMyhqsZvVKIUjZNruR8RwHSjuPKJi8WDCUTBGWCQ,2600
30
+ Kekik/Sifreleme/Packer.py,sha256=o4jkoKdI0UOg9jqGsCg2H82vLbfsuYQnWFYS1DzRWrU,7290
31
31
  Kekik/Sifreleme/StringCodec.py,sha256=5kmFLN7g2c_ENxD491lAlH0AGtW7tIy5h5KPHEuTzyM,1426
32
32
  Kekik/Sifreleme/__init__.py,sha256=IDSVFP4xshnpPLyCX2ndY_N9cYIzikqu5LFi_E-3-5Q,284
33
33
  Kekik/kisi_ver/__init__.py,sha256=gH613YZC3ziE7f67dViefRSuwDwsHyVvXHpM5CzZ2q4,1386
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.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,,
37
+ kekik-1.7.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
38
+ kekik-1.7.9.dist-info/METADATA,sha256=z_24aSaYTNhm4UWDjgfeC5dk4EiHs7SRm_Uys-6Nn54,44515
39
+ kekik-1.7.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ kekik-1.7.9.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
41
+ kekik-1.7.9.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
42
+ kekik-1.7.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5