Kekik 1.7.0__py3-none-any.whl → 1.7.1__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.

Potentially problematic release.


This version of Kekik might be problematic. Click here for more details.

Kekik/cache.py CHANGED
@@ -1,25 +1,94 @@
1
1
  # ! https://github.com/Fatal1ty/aiocached
2
2
 
3
- import asyncio
3
+ from .cli import konsol
4
4
  from functools import wraps
5
- from time import time
5
+ from time import time, sleep
6
6
  from hashlib import md5
7
7
  from urllib.parse import urlencode
8
+ import asyncio, threading
9
+
10
+ # -----------------------------------------------------
11
+ # Yardımcı Fonksiyonlar
12
+ # -----------------------------------------------------
8
13
 
9
14
  UNLIMITED = None
10
15
 
11
- class Cache:
16
+ def normalize_for_key(value):
12
17
  """
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).
18
+ Cache key oluşturma amacıyla verilen değeri normalize eder.
19
+ - Basit tipler (int, float, str, bool, None) direk kullanılır.
20
+ - dict: Anahtarları sıralı olarak normalize eder.
21
+ - list/tuple: Elemanları normalize eder.
22
+ - Diğer: Sadece sınıf ismi kullanılır.
15
23
  """
24
+ if isinstance(value, (int, float, str, bool, type(None))):
25
+ return value
26
+
27
+ elif isinstance(value, dict):
28
+ return {k: normalize_for_key(value[k]) for k in sorted(value)}
29
+
30
+ elif isinstance(value, (list, tuple)):
31
+ return [normalize_for_key(item) for item in value]
32
+
33
+ else:
34
+ return value.__class__.__name__
35
+
36
+ def simple_cache_key(func, args, kwargs) -> str:
37
+ """
38
+ Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
39
+ Oluşturulan stringin sonuna MD5 hash eklenir.
40
+ """
41
+ base_key = f"{func.__module__}.{func.__qualname__}"
42
+
43
+ if args:
44
+ norm_args = [normalize_for_key(arg) for arg in args]
45
+ base_key += f"|{norm_args}"
46
+
47
+ if kwargs:
48
+ norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
49
+ base_key += f"|{str(sorted(norm_kwargs.items()))}"
50
+
51
+ hashed = md5(base_key.encode('utf-8')).hexdigest()
52
+ return f"{base_key}" # |{hashed}
53
+
54
+ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
55
+ """
56
+ Cache key'ini oluşturur.
57
+ - is_fastapi=False ise simple_cache_key() kullanılır.
58
+ - True ise FastAPI Request nesnesine göre özel key oluşturulur.
59
+ """
60
+ if not is_fastapi:
61
+ return simple_cache_key(func, args, kwargs)
62
+
63
+ # FastAPI: request ilk argüman ya da kwargs'dan alınır
64
+ request = args[0] if args else kwargs.get("request")
65
+
66
+ if request.method == "GET":
67
+ # Eğer query_params boşsa {} olarak ayarla
68
+ veri = dict(request.query_params) if request.query_params else {}
69
+ else:
70
+ try:
71
+ veri = await request.json()
72
+ except Exception:
73
+ form_data = await request.form()
74
+ veri = dict(form_data.items())
75
+
76
+ args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
77
+ return f"{request.url.path}?{veri}"
78
+
79
+
80
+ # -----------------------------------------------------
81
+ # In-Memory (RAM) Cache
82
+ # -----------------------------------------------------
83
+
84
+ class Cache:
16
85
  def __init__(self, ttl=UNLIMITED):
17
- self._data = {}
18
86
  self._ttl = ttl
87
+ self._data = {}
19
88
  self._times = {}
20
89
 
21
90
  def _is_expired(self, key):
22
- """Belirtilen key'in süresi dolduysa True döner."""
91
+ """Belirtilen key'in süresi dolmuşsa True döner."""
23
92
  if self._ttl is UNLIMITED:
24
93
  return False
25
94
 
@@ -27,22 +96,57 @@ class Cache:
27
96
 
28
97
  return timestamp is not None and (time() - timestamp > self._ttl)
29
98
 
99
+
100
+ class SyncCache(Cache):
101
+ """
102
+ Basit in-memory cache yapısı.
103
+ TTL (time-to-live) süresi dolan veriler otomatik olarak temizlenir.
104
+ Bu versiyonda, otomatik temizleme işlevselliği bir thread ile sağlanır.
105
+ """
106
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
107
+ super().__init__(ttl)
108
+
109
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
110
+ self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
111
+ self._lock = threading.RLock()
112
+
113
+ # Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
114
+ self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
115
+ self._cleanup_thread.start()
116
+
117
+ def _auto_cleanup(self):
118
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
119
+ while True:
120
+ sleep(self._cleanup_interval)
121
+ with self._lock:
122
+ keys = list(self._data.keys())
123
+ for key in keys:
124
+ self.remove_if_expired(key)
125
+
30
126
  def remove_if_expired(self, key):
31
127
  """
32
128
  Eğer key'in cache süresi dolmuşsa, ilgili entry'yi temizler.
129
+ Thread güvenliği sağlamak için lock kullanılır.
33
130
  """
34
- if self._is_expired(key):
35
- self._data.pop(key, None)
36
- self._times.pop(key, None)
131
+ with self._lock:
132
+ if self._is_expired(key):
133
+ self._data.pop(key, None)
134
+ self._times.pop(key, None)
135
+ # konsol.log(f"[red][-] {key}")
37
136
 
38
137
  def __getitem__(self, key):
39
- self.remove_if_expired(key)
40
- return self._data[key]
138
+ with self._lock:
139
+ self.remove_if_expired(key)
140
+ veri = self._data[key]
141
+ # konsol.log(f"[yellow][~] {key}")
142
+ return veri
41
143
 
42
144
  def __setitem__(self, key, value):
43
- self._data[key] = value
44
- if self._ttl is not UNLIMITED:
45
- self._times[key] = time()
145
+ with self._lock:
146
+ self._data[key] = value
147
+ # konsol.log(f"[green][+] {key}")
148
+ if self._ttl is not UNLIMITED:
149
+ self._times[key] = time()
46
150
 
47
151
 
48
152
  class AsyncCache(Cache):
@@ -92,12 +196,16 @@ class AsyncCache(Cache):
92
196
  self.remove_if_expired(key)
93
197
 
94
198
  try:
95
- return self._data[key]
199
+ veri = self._data[key]
200
+ # konsol.log(f"[yellow][~] {key}")
201
+ return veri
96
202
  except KeyError as e:
97
203
  future = self.futures.get(key)
98
204
  if future:
99
205
  await future
100
- return future.result()
206
+ veri = future.result()
207
+ # konsol.log(f"[yellow][?] {key}")
208
+ return veri
101
209
 
102
210
  raise e
103
211
 
@@ -106,16 +214,26 @@ class AsyncCache(Cache):
106
214
  Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
107
215
  """
108
216
  if self._ttl is not UNLIMITED and self._is_expired(key):
217
+ # konsol.log(f"[red][-] {key}")
109
218
  self._data.pop(key, None)
110
219
  self._times.pop(key, None)
111
220
  self.futures.pop(key, None)
112
221
 
222
+ def __setitem__(self, key, value):
223
+ self._data[key] = value
224
+ if self._ttl is not UNLIMITED:
225
+ self._times[key] = time()
226
+
227
+
228
+ # -----------------------------------------------------
229
+ # Fonksiyonun Sonucunu Hesaplayıp Cache'e Yazma
230
+ # -----------------------------------------------------
113
231
 
114
232
  def _sync_maybe_cache(func, key, result, unless):
115
233
  """Senkron sonuç için cache kaydını oluşturur (unless koşuluna bakarak)."""
116
234
  if unless is None or not unless(result):
117
235
  func.__cache[key] = result
118
-
236
+ # konsol.log(f"[green][+] {key}")
119
237
 
120
238
  async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
121
239
  """
@@ -142,6 +260,7 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
142
260
  # unless koşuluna göre cache'e ekleme yap.
143
261
  if unless is None or not unless(result):
144
262
  cache[key] = result
263
+ # konsol.log(f"[green][+] {key}")
145
264
 
146
265
  return result
147
266
  except Exception as exc:
@@ -151,34 +270,10 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
151
270
  # İşlem tamamlandığında future'ı temizle.
152
271
  cache.futures.pop(key, None)
153
272
 
154
- async def make_cache_key(args, kwargs, is_fastapi=False):
155
- """
156
- Cache key'ini oluşturur.
157
-
158
- :param is_fastapi (bool): Eğer True ise, ilk argümanın bir FastAPI Request nesnesi olduğu varsayılır.
159
- Bu durumda, cache key, request nesnesinin URL yolunu (request.url.path) ve
160
- isteğe ait verilerden (GET istekleri için query parametreleri; diğer istekler için JSON veya form verileri)
161
- elde edilen verinin URL uyumlu halinin md5 hash'inin birleşiminden oluşturulur.
162
- Böylece, aynı URL ve aynı istek verileri için her seferinde aynı cache key üretilecektir.
163
- Eğer False ise, cache key args ve kwargs değerlerinden, sıralı bir tuple olarak oluşturulur.
164
- """
165
- if not is_fastapi:
166
- return (args, tuple(sorted(kwargs.items())))
167
-
168
- request = args[0] if args else kwargs.get("request")
169
-
170
- if request.method == "GET":
171
- veri = dict(request.query_params) if request.query_params else None
172
- else:
173
- try:
174
- veri = await request.json()
175
- except Exception:
176
- form_data = await request.form()
177
- veri = dict(form_data.items())
178
-
179
- args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
180
- return f"{request.url.path}?{args_hash}"
181
273
 
274
+ # -----------------------------------------------------
275
+ # Dekoratör: kekik_cache
276
+ # -----------------------------------------------------
182
277
 
183
278
  def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
184
279
  """
@@ -211,12 +306,12 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
211
306
  return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
212
307
 
213
308
  def decorator(func):
214
- func.__cache = AsyncCache(ttl)
215
-
216
309
  if asyncio.iscoroutinefunction(func):
310
+ func.__cache = AsyncCache(ttl)
311
+
217
312
  @wraps(func)
218
313
  async def async_wrapper(*args, **kwargs):
219
- key = await make_cache_key(args, kwargs, is_fastapi)
314
+ key = await make_cache_key(func, args, kwargs, is_fastapi)
220
315
 
221
316
  try:
222
317
  return await func.__cache.get(key)
@@ -225,9 +320,11 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
225
320
 
226
321
  return async_wrapper
227
322
  else:
323
+ func.__cache = SyncCache(ttl)
324
+
228
325
  @wraps(func)
229
326
  def sync_wrapper(*args, **kwargs):
230
- key = (args, tuple(sorted(kwargs.items())))
327
+ key = simple_cache_key(func, args, kwargs)
231
328
 
232
329
  try:
233
330
  return func.__cache[key]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Kekik
3
- Version: 1.7.0
3
+ Version: 1.7.1
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
@@ -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=oLzr1UrzQpeRSjW7MFfJuSRXH3m4aGq98_d_QgoLwfQ,8746
5
+ Kekik/cache.py,sha256=Co0r6Jv8sERFP2ulSb8ccoXyFwqCGYv2QNLyqgOWjFM,12092
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
@@ -34,9 +34,9 @@ Kekik/kisi_ver/__init__.py,sha256=gH613YZC3ziE7f67dViefRSuwDwsHyVvXHpM5CzZ2q4,13
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.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
38
- Kekik-1.7.0.dist-info/METADATA,sha256=XCZ7g4OpdRavkHS3lGruuViDHz3ktxXHWdwzIq2HhqQ,43959
39
- Kekik-1.7.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
- Kekik-1.7.0.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
41
- Kekik-1.7.0.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
42
- Kekik-1.7.0.dist-info/RECORD,,
37
+ Kekik-1.7.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
38
+ Kekik-1.7.1.dist-info/METADATA,sha256=sTy45X5EuJKCfn9R7Yj4lHdGQdOqLponeeVIAWi7pWE,43959
39
+ Kekik-1.7.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
+ Kekik-1.7.1.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
41
+ Kekik-1.7.1.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
42
+ Kekik-1.7.1.dist-info/RECORD,,
File without changes
File without changes