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 +158 -21
- Kekik/cache.py +473 -99
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info}/METADATA +29 -2
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info}/RECORD +8 -8
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info}/WHEEL +1 -1
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info}/entry_points.txt +0 -0
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info/licenses}/LICENSE +0 -0
- {Kekik-1.6.7.dist-info → kekik-1.7.9.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
"""
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
151
|
+
return word
|
|
43
152
|
|
|
44
153
|
@staticmethod
|
|
45
154
|
def unpack(source: str) -> str:
|
|
46
|
-
"""
|
|
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
|
-
|
|
162
|
+
# Argümanları çıkar
|
|
163
|
+
try:
|
|
164
|
+
payload, symtab, radix, count = Packer.extract_arguments(source)
|
|
50
165
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
"""
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
#
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
-
|
|
21
|
+
REDIS_HOST = "127.0.0.1"
|
|
22
|
+
REDIS_PORT = 6379
|
|
23
|
+
REDIS_DB = 0
|
|
24
|
+
REDIS_PASS = None
|
|
10
25
|
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
35
|
-
self.
|
|
36
|
-
|
|
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.
|
|
40
|
-
|
|
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.
|
|
44
|
-
|
|
45
|
-
self.
|
|
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
|
-
|
|
280
|
+
# -----------------------------------------------------
|
|
281
|
+
# Asenkron Cache (In-Memory) ve Redis Hybrid Cache
|
|
282
|
+
# -----------------------------------------------------
|
|
283
|
+
|
|
284
|
+
class AsyncCache:
|
|
49
285
|
"""
|
|
50
|
-
|
|
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
|
-
|
|
60
|
-
self.
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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
|
|
114
|
-
Aynı key için
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: Kekik
|
|
3
|
-
Version: 1.
|
|
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=
|
|
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=
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|