Kekik 1.6.7__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
@@ -1,65 +1,306 @@
1
- # ! https://github.com/Fatal1ty/aiocached
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- import asyncio
3
+ from .cli import konsol
4
4
  from functools import wraps
5
- from time import time
6
5
  from hashlib import md5
7
6
  from urllib.parse import urlencode
7
+ import time
8
+ import threading
9
+ import asyncio
10
+ import pickle
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
8
20
 
9
- UNLIMITED = None
21
+ REDIS_HOST = "127.0.0.1"
22
+ REDIS_PORT = 6379
23
+ REDIS_DB = 0
24
+ REDIS_PASS = None
10
25
 
11
- class Cache:
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}
29
+
30
+ def normalize_for_key(value):
12
31
  """
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).
32
+ Cache key oluşturma amacıyla verilen değeri normalize eder.
33
+ - Basit tipler (int, float, str, bool, None) doğrudan kullanılır.
34
+ - dict: Anahtarları sıralı olarak normalize eder.
35
+ - list/tuple: Elemanları normalize eder.
36
+ - Diğer: Sadece sınıf ismi kullanılır.
15
37
  """
16
- def __init__(self, ttl=UNLIMITED):
17
- self._data = {}
18
- self._ttl = ttl
19
- self._times = {}
38
+ if isinstance(value, (int, float, str, bool, type(None))):
39
+ return value
40
+
41
+ elif isinstance(value, dict):
42
+ return {k: normalize_for_key(value[k]) for k in sorted(value)}
43
+
44
+ elif isinstance(value, (list, tuple)):
45
+ return [normalize_for_key(item) for item in value]
46
+
47
+ else:
48
+ return value.__class__.__name__
49
+
50
+ def simple_cache_key(func, args, kwargs) -> str:
51
+ """
52
+ Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
53
+ Oluşturulan stringin sonuna MD5 hash eklenebilir.
54
+ """
55
+ base_key = f"{func.__module__}.{func.__qualname__}"
56
+ base_key = "|".join(base_key.split("."))
57
+
58
+ if args:
59
+ norm_args = [normalize_for_key(arg) for arg in args]
60
+ base_key += f"|{norm_args}"
61
+
62
+ if kwargs:
63
+ norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
64
+ base_key += f"|{str(sorted(norm_kwargs.items()))}"
65
+
66
+ hashed = md5(base_key.encode('utf-8')).hexdigest()
67
+ return f"{base_key}" # |{hashed}
68
+
69
+ async def make_cache_key(func, args, kwargs, is_fastapi=False, include_auth=False) -> str:
70
+ """
71
+ Cache key'ini oluşturur.
72
+ - is_fastapi=False ise simple_cache_key() kullanılır.
73
+ - True ise FastAPI Request nesnesine göre özel key oluşturulur.
74
+ - include_auth=True ise authorization header'ı key'e dahil edilir.
75
+ """
76
+ if not is_fastapi:
77
+ return simple_cache_key(func, args, kwargs)
78
+
79
+ # FastAPI: request ilk argümandan ya da kwargs'dan alınır.
80
+ request = args[0] if args else kwargs.get("request")
81
+
82
+ # Request bulunamamışsa fallback
83
+ if request is None or not hasattr(request, 'method'):
84
+ return simple_cache_key(func, args, kwargs)
85
+
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)
116
+
117
+ # -----------------------------------------------------
118
+ # Senkron Cache (RAM) Sınıfı
119
+ # -----------------------------------------------------
120
+
121
+ class SyncCache:
122
+ """
123
+ Senkron fonksiyonlar için basit in-memory cache.
124
+ TTL süresi dolan veriler periyodik olarak arka plan thread'iyle temizlenir.
125
+ """
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
132
+
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)
135
+
136
+ # Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
137
+ self._lock = threading.RLock()
138
+ self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
139
+ self._cleanup_thread.daemon = True
140
+ self._cleanup_thread.start()
141
+
142
+ def _auto_cleanup(self):
143
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
144
+ while True:
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}")
20
153
 
21
154
  def _is_expired(self, key):
22
- """Belirtilen key'in süresi dolduysa True döner."""
155
+ """Belirtilen key'in süresi dolmuşsa True döner."""
23
156
  if self._ttl is UNLIMITED:
24
157
  return False
25
158
 
26
159
  timestamp = self._times.get(key)
27
160
 
28
- return timestamp is not None and (time() - timestamp > self._ttl)
161
+ return timestamp is not None and (time.time() - timestamp > self._ttl)
29
162
 
30
163
  def remove_if_expired(self, key):
31
164
  """
32
165
  Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
166
+ Thread güvenliği sağlamak için lock kullanılır.
33
167
  """
34
- if self._is_expired(key):
35
- self._data.pop(key, None)
36
- self._times.pop(key, None)
168
+ with self._lock:
169
+ if self._is_expired(key):
170
+ self._data.pop(key, None)
171
+ self._times.pop(key, None)
172
+ # konsol.log(f"[red][-] {key}")
37
173
 
38
174
  def __getitem__(self, key):
39
- self.remove_if_expired(key)
40
- return self._data[key]
175
+ with self._lock:
176
+ self.remove_if_expired(key)
177
+ veri = self._data[key]
178
+ # LRU tracker'ı güncelle
179
+ self._access_counts[key] = self._access_counts.get(key, 0) + 1
180
+ # konsol.log(f"[yellow][~] {key}")
181
+ return veri
41
182
 
42
183
  def __setitem__(self, key, value):
43
- self._data[key] = value
44
- if self._ttl is not UNLIMITED:
45
- self._times[key] = time()
184
+ with self._lock:
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
196
+ if self._ttl is not UNLIMITED:
197
+ self._times[key] = time.time()
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
46
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)
47
279
 
48
- class AsyncCache(Cache):
280
+ # -----------------------------------------------------
281
+ # Asenkron Cache (In-Memory) ve Redis Hybrid Cache
282
+ # -----------------------------------------------------
283
+
284
+ class AsyncCache:
49
285
  """
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.
286
+ Temel in-memory asenkron cache.
53
287
  """
54
- def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
288
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60, max_size=10000):
55
289
  """
56
290
  :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
57
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).
58
293
  """
59
- super().__init__(ttl)
60
- 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
300
+
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)
61
303
 
62
- self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
63
304
  # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
64
305
  try:
65
306
  self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
@@ -69,50 +310,175 @@ class AsyncCache(Cache):
69
310
  async def _auto_cleanup(self):
70
311
  """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
71
312
  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)
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}")
319
+
320
+ def ensure_cleanup_task(self):
321
+ """Event loop mevcutsa, cleanup task henüz başlatılmadıysa oluştur."""
322
+ if self._cleanup_task is None:
323
+ try:
324
+ self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
325
+ except RuntimeError:
326
+ pass # Yine loop yoksa yapacak bir şey yok
327
+
328
+ def remove_if_expired(self, key):
329
+ """
330
+ Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
331
+ """
332
+ if self._ttl is not UNLIMITED:
333
+ t = self._times.get(key)
334
+ if t is not None and (time.time() - t > self._ttl):
335
+ # konsol.log(f"[red][-] {key}")
336
+ self._data.pop(key, None)
337
+ self._times.pop(key, None)
338
+ self.futures.pop(key, None)
77
339
 
78
340
  async def get(self, key):
79
341
  """
80
342
  Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
81
343
  Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
82
344
  """
345
+ # Cleanup task'in aktif olduğundan emin olun.
346
+ self.ensure_cleanup_task()
347
+ # Eğer key'in süresi dolmuşsa, kaldırın.
83
348
  self.remove_if_expired(key)
84
349
 
85
350
  try:
86
- return self._data[key]
87
- except KeyError as e:
351
+ # Cache içerisinde key varsa direkt değeri döndür.
352
+ value = self._data[key]
353
+ # LRU tracker'ı güncelle
354
+ self._access_counts[key] = self._access_counts.get(key, 0) + 1
355
+ # konsol.log(f"[yellow][~] {key}")
356
+ return value
357
+ except KeyError:
358
+ # Eğer key cache'de yoksa, aynı key ile başlatılmış future varsa onu bekle.
88
359
  future = self.futures.get(key)
89
360
  if future:
90
361
  await future
91
- return future.result()
362
+ # Future tamamlandığında sonucu döndür.
363
+ value = future.result()
364
+ # konsol.log(f"[yellow][?] {key}")
365
+ return value
366
+ # Eğer future da yoksa, key bulunamadığına dair hata fırlat.
367
+ raise KeyError(key)
368
+
369
+ async def set(self, key, value):
370
+ """Belirtilen key için cache'e değer ekler."""
371
+ self.ensure_cleanup_task()
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
385
+ if self._ttl is not UNLIMITED:
386
+ self._times[key] = time.time()
92
387
 
93
- raise e
388
+ class HybridAsyncCache:
389
+ """
390
+ Öncelikle Redis cache kullanılmaya çalışılır.
391
+ Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCache'e geçilir.
392
+ """
393
+ def __init__(self, ttl=UNLIMITED, max_size=10000):
394
+ self._ttl = ttl
395
+ self._max_size = max_size
94
396
 
95
- def remove_if_expired(self, key):
96
- """
97
- Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
98
- """
99
- if self._ttl is not UNLIMITED and self._is_expired(key):
100
- self._data.pop(key, None)
101
- self._times.pop(key, None)
102
- self.futures.pop(key, None)
397
+ try:
398
+ self.redis = redisAsync.Redis(
399
+ host = REDIS_HOST,
400
+ port = REDIS_PORT,
401
+ db = REDIS_DB,
402
+ password = REDIS_PASS,
403
+ decode_responses = False
404
+ )
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}")
407
+ self.redis = None
408
+
409
+ self.memory = AsyncCache(ttl, max_size=max_size)
410
+ self.futures = {}
411
+
412
+ async def get(self, key):
413
+ # Eşzamanlı istek yönetimi: aynı key için bir future varsa, direkt bekleyip sonucu döndür.
414
+ if key in self.futures:
415
+ # konsol.log(f"[yellow][?] {key}")
416
+ return await self.futures[key]
417
+
418
+ # İlk önce Redis ile deneyelim
419
+ if self.redis:
420
+ try:
421
+ data = await self.redis.get(key)
422
+ except Exception as e:
423
+ konsol.log(f"[yellow]Async Redis get hatası: {e}")
424
+ return await self.memory.get(key)
425
+ if data is not None:
426
+ try:
427
+ result = pickle.loads(data)
428
+ # konsol.log(f"[yellow][~] {key}")
429
+ return result
430
+ except Exception as e:
431
+ # Deserialize hatası durumunda in-memory cache'ten dene
432
+ konsol.log(f"[yellow]Async pickle deserialize hatası: {e}")
433
+ return await self.memory.get(key)
434
+ else:
435
+ # Redis'te veri yoksa, in-memory cache'e bak
436
+ return await self.memory.get(key)
437
+ else:
438
+ # Redis kullanılmıyorsa doğrudan in-memory cache'e bak
439
+ return await self.memory.get(key)
440
+
441
+ async def set(self, key, value):
442
+ # Önce veriyi pickle etmeyi deniyoruz.
443
+ try:
444
+ ser = pickle.dumps(value)
445
+ except Exception as e:
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")
449
+ await self.memory.set(key, value)
450
+ return
451
+
452
+ if self.redis:
453
+ try:
454
+ if self._ttl is not UNLIMITED:
455
+ await self.redis.set(key, ser, ex=self._ttl)
456
+ else:
457
+ await self.redis.set(key, ser)
458
+ return
459
+ except Exception as e:
460
+ # Redis yazma hatası durumunda in-memory fallback
461
+ # konsol.log(f"[yellow]Async Redis set hatası: {e}, in-memory cache kullanılıyor")
462
+ await self.memory.set(key, value)
463
+ return
464
+ else:
465
+ # Redis yoksa in-memory cache'e yaz
466
+ await self.memory.set(key, value)
103
467
 
468
+ # -----------------------------------------------------
469
+ # Cache'e Hesaplanmış Sonucu Yazma Yardımcı Fonksiyonları
470
+ # -----------------------------------------------------
104
471
 
105
472
  def _sync_maybe_cache(func, key, result, unless):
106
- """Senkron sonuç için cache kaydını oluşturur (unless koşuluna bakarak)."""
473
+ """Senkron fonksiyon sonucu için cache kaydı oluşturur."""
107
474
  if unless is None or not unless(result):
108
475
  func.__cache[key] = result
109
-
476
+ # konsol.log(f"[green][+] {key}")
110
477
 
111
478
  async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
112
479
  """
113
- Asenkron fonksiyon sonucunu hesaplar ve cache'e ekler.
114
- Aynı key için işlem devam ediyorsa, mevcut sonucu bekler.
115
- Sonuç, unless(result) True değilse cache'e eklenir.
480
+ Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
481
+ Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
116
482
  """
117
483
  # __cache'den cache nesnesini alıyoruz.
118
484
  cache = func.__cache
@@ -129,11 +495,12 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
129
495
  # Asenkron fonksiyonu çalıştır ve sonucu elde et.
130
496
  result = await func(*args, **kwargs)
131
497
  future.set_result(result)
132
-
498
+
133
499
  # unless koşuluna göre cache'e ekleme yap.
134
500
  if unless is None or not unless(result):
135
- cache[key] = result
136
-
501
+ await cache.set(key, result)
502
+ # konsol.log(f"[green][+] {key}")
503
+
137
504
  return result
138
505
  except Exception as exc:
139
506
  future.cancel()
@@ -142,61 +509,65 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
142
509
  # İşlem tamamlandığında future'ı temizle.
143
510
  cache.futures.pop(key, None)
144
511
 
145
- async def make_cache_key(args, kwargs, is_fastapi=False):
146
- """
147
- Cache key'ini oluşturur.
148
-
149
- :param is_fastapi (bool): Eğer True ise, ilk argümanın bir FastAPI Request nesnesi olduğu varsayılır.
150
- Bu durumda, cache key, request nesnesinin URL yolunu (request.url.path) ve
151
- isteğe ait verilerden (GET istekleri için query parametreleri; diğer istekler için JSON veya form verileri)
152
- elde edilen verinin URL uyumlu halinin md5 hash'inin birleşiminden oluşturulur.
153
- Böylece, aynı URL ve aynı istek verileri için her seferinde aynı cache key üretilecektir.
154
- Eğer False ise, cache key args ve kwargs değerlerinden, sıralı bir tuple olarak oluşturulur.
155
- """
156
- if not is_fastapi:
157
- return (args, tuple(sorted(kwargs.items())))
158
-
159
- request = args[0] if args else kwargs.get("request")
160
-
161
- if request.method == "GET":
162
- veri = dict(request.query_params) if request.query_params else None
163
- else:
164
- try:
165
- veri = await request.json()
166
- except Exception:
167
- form_data = await request.form()
168
- veri = dict(form_data.items())
169
-
170
- args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
171
- return f"{request.url.path}?{args_hash}"
172
-
512
+ # -----------------------------------------------------
513
+ # kekik_cache Dekoratörü (Senkrondan Asenkrona)
514
+ # -----------------------------------------------------
173
515
 
174
- def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
516
+ def kekik_cache(ttl=UNLIMITED, unless=None, use_redis=True, max_size=10000, is_fastapi=None, include_auth=False):
175
517
  """
176
- Bir fonksiyon veya coroutine'in sonucunu cache'ler.
518
+ Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
519
+ FastAPI endpoint'leri otomatik detekt edilir (is_fastapi=None ise) veya manuel olarak belirtilebilir.
177
520
 
178
- :param ttl: Cache’in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
179
- :param unless: Fonksiyonun sonucunu argüman olarak alan bir callable.
180
- Eğer True dönerse, sonuç cache'e alınmaz.
181
- :param is_fastapi: Eğer True ise, cache key'i oluştururken FastAPI request nesnesine özel şekilde davranır.
182
-
183
- Örnek kullanım:
521
+ Parametreler:
522
+ - ttl: Cache'in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
523
+ - unless: Sonuç alınmadan önce çağrılan, True dönerse cache'e alınmaz.
524
+ - is_fastapi: True/False ile açıkça belirt, None ise otomatik detekt (Request nesnesine bakarak).
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).
528
+
529
+ Örnek Kullanım:
184
530
 
185
- @kekik_cache(ttl=15, unless=lambda res: res is None)
186
- async def bakalim(param):
187
- ...
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 {}
188
553
  """
189
- # Parametresiz kullanıldığında
554
+ # Parametresiz kullanım durumunda
190
555
  if callable(ttl):
191
- return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(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)
192
557
 
193
558
  def decorator(func):
194
- func.__cache = AsyncCache(ttl)
195
-
196
559
  if asyncio.iscoroutinefunction(func):
560
+ # Asenkron fonksiyonlar için cache türünü seçelim:
561
+ func.__cache = HybridAsyncCache(ttl, max_size=max_size) if use_redis else AsyncCache(ttl, max_size=max_size)
562
+
197
563
  @wraps(func)
198
564
  async def async_wrapper(*args, **kwargs):
199
- key = await make_cache_key(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)
200
571
 
201
572
  try:
202
573
  return await func.__cache.get(key)
@@ -205,9 +576,12 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
205
576
 
206
577
  return async_wrapper
207
578
  else:
579
+ # Senkron fonksiyonlar için
580
+ func.__cache = HybridSyncCache(ttl, max_size=max_size) if use_redis else SyncCache(ttl, max_size=max_size)
581
+
208
582
  @wraps(func)
209
583
  def sync_wrapper(*args, **kwargs):
210
- key = (args, tuple(sorted(kwargs.items())))
584
+ key = simple_cache_key(func, args, kwargs)
211
585
 
212
586
  try:
213
587
  return func.__cache[key]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: Kekik
3
- Version: 1.6.7
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=YrlSJBIaFeI6syURQhkF4M8tv6zr72zny-lnazA2VoI,7858
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.6.7.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
38
- Kekik-1.6.7.dist-info/METADATA,sha256=JLbEAry2cH-qyFJ6Uc5i2-RPC4HI_aO6CYPivbvvhwM,43959
39
- Kekik-1.6.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
- Kekik-1.6.7.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
41
- Kekik-1.6.7.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
42
- Kekik-1.6.7.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