Kekik 1.7.0__py3-none-any.whl → 1.7.2__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/cache.py CHANGED
@@ -1,65 +1,173 @@
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
8
11
 
12
+ # Redis için asenkron client (Redis yoksa fallback yapılacak)
13
+ import redis.asyncio as redis
14
+
15
+ # -----------------------------------------------------
16
+ # Sabitler ve Yardımcı Fonksiyonlar
17
+ # -----------------------------------------------------
9
18
  UNLIMITED = None
10
19
 
11
- class Cache:
20
+ def normalize_for_key(value):
12
21
  """
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).
22
+ Cache key oluşturma amacıyla verilen değeri normalize eder.
23
+ - Basit tipler (int, float, str, bool, None) doğrudan kullanılır.
24
+ - dict: Anahtarları sıralı olarak normalize eder.
25
+ - list/tuple: Elemanları normalize eder.
26
+ - Diğer: Sadece sınıf ismi kullanılır.
15
27
  """
16
- def __init__(self, ttl=UNLIMITED):
17
- self._data = {}
28
+ if isinstance(value, (int, float, str, bool, type(None))):
29
+ return value
30
+
31
+ elif isinstance(value, dict):
32
+ return {k: normalize_for_key(value[k]) for k in sorted(value)}
33
+
34
+ elif isinstance(value, (list, tuple)):
35
+ return [normalize_for_key(item) for item in value]
36
+
37
+ else:
38
+ return value.__class__.__name__
39
+
40
+ def simple_cache_key(func, args, kwargs) -> str:
41
+ """
42
+ Fonksiyonun tam adı ve parametrelerini kullanarak bir cache key oluşturur.
43
+ Oluşturulan stringin sonuna MD5 hash eklenebilir.
44
+ """
45
+ base_key = f"{func.__module__}.{func.__qualname__}"
46
+
47
+ if args:
48
+ norm_args = [normalize_for_key(arg) for arg in args]
49
+ base_key += f"|{norm_args}"
50
+
51
+ if kwargs:
52
+ norm_kwargs = {k: normalize_for_key(v) for k, v in kwargs.items()}
53
+ base_key += f"|{str(sorted(norm_kwargs.items()))}"
54
+
55
+ hashed = md5(base_key.encode('utf-8')).hexdigest()
56
+ return f"{base_key}" # |{hashed}
57
+
58
+ async def make_cache_key(func, args, kwargs, is_fastapi=False) -> str:
59
+ """
60
+ Cache key'ini oluşturur.
61
+ - is_fastapi=False ise simple_cache_key() kullanılır.
62
+ - True ise FastAPI Request nesnesine göre özel key oluşturulur.
63
+ """
64
+ if not is_fastapi:
65
+ return simple_cache_key(func, args, kwargs)
66
+
67
+ # FastAPI: request ilk argümandan ya da kwargs'dan alınır.
68
+ request = args[0] if args else kwargs.get("request")
69
+
70
+ if request.method == "GET":
71
+ # Eğer query_params boşsa {} olarak ayarla
72
+ veri = dict(request.query_params) if request.query_params else {}
73
+ else:
74
+ try:
75
+ veri = await request.json()
76
+ except Exception:
77
+ form_data = await request.form()
78
+ veri = dict(form_data.items())
79
+
80
+ # Eğer "kurek" gibi özel parametreler varsa temizleyebilirsiniz:
81
+ veri.pop("kurek", None)
82
+
83
+ args_hash = md5(urlencode(veri).encode()).hexdigest() if veri else ""
84
+ return f"{request.url.path}?{veri}"
85
+
86
+ # -----------------------------------------------------
87
+ # Senkron Cache (RAM) Sınıfı
88
+ # -----------------------------------------------------
89
+
90
+ class SyncCache:
91
+ """
92
+ Senkron fonksiyonlar için basit in-memory cache.
93
+ TTL süresi dolan veriler periyodik olarak arka plan thread’iyle temizlenir.
94
+ """
95
+ def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
18
96
  self._ttl = ttl
97
+ self._data = {}
19
98
  self._times = {}
20
99
 
100
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
101
+ self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
102
+
103
+ # Arka planda çalışan ve periyodik olarak expired entry'leri temizleyen thread başlatılıyor.
104
+ self._lock = threading.RLock()
105
+ self._cleanup_thread = threading.Thread(target=self._auto_cleanup, daemon=True)
106
+ self._cleanup_thread.start()
107
+
108
+ def _auto_cleanup(self):
109
+ """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
110
+ while True:
111
+ time.sleep(self._cleanup_interval)
112
+ with self._lock:
113
+ keys = list(self._data.keys())
114
+ for key in keys:
115
+ self.remove_if_expired(key)
116
+
21
117
  def _is_expired(self, key):
22
- """Belirtilen key'in süresi dolduysa True döner."""
118
+ """Belirtilen key'in süresi dolmuşsa True döner."""
23
119
  if self._ttl is UNLIMITED:
24
120
  return False
25
121
 
26
122
  timestamp = self._times.get(key)
27
123
 
28
- return timestamp is not None and (time() - timestamp > self._ttl)
124
+ return timestamp is not None and (time.time() - timestamp > self._ttl)
29
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
+ if self._ttl is not UNLIMITED:
148
+ self._times[key] = time.time()
46
149
 
150
+ # -----------------------------------------------------
151
+ # Asenkron Cache (In-Memory) ve Redis Hybrid Cache
152
+ # -----------------------------------------------------
47
153
 
48
- class AsyncCache(Cache):
154
+ class AsyncCache:
49
155
  """
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.
156
+ Temel in-memory asenkron cache.
53
157
  """
54
158
  def __init__(self, ttl=UNLIMITED, cleanup_interval=60 * 60):
55
159
  """
56
160
  :param ttl: Her entry için geçerli süre (saniye). Örneğin 3600 saniye 1 saattir.
57
161
  :param cleanup_interval: Otomatik temizleme görevinin kaç saniyede bir çalışacağını belirler.
58
162
  """
59
- super().__init__(ttl)
163
+ self._ttl = ttl
164
+ self._data = {}
165
+ self._times = {}
60
166
  self.futures = {}
61
167
 
168
+ # TTL sınırsız değilse, cleanup_interval ile ttl'den büyük olanı kullanıyoruz.
62
169
  self._cleanup_interval = max(ttl, cleanup_interval) if ttl is not UNLIMITED else cleanup_interval
170
+
63
171
  # Aktif bir event loop varsa otomatik temizlik görevini başlatıyoruz.
64
172
  try:
65
173
  self._cleanup_task = asyncio.get_running_loop().create_task(self._auto_cleanup())
@@ -70,9 +178,7 @@ class AsyncCache(Cache):
70
178
  """Belirlenen aralıklarla cache içerisindeki süresi dolmuş entry'leri temizler."""
71
179
  while True:
72
180
  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:
181
+ for key in list(self._data.keys()):
76
182
  self.remove_if_expired(key)
77
183
 
78
184
  def ensure_cleanup_task(self):
@@ -83,45 +189,137 @@ class AsyncCache(Cache):
83
189
  except RuntimeError:
84
190
  pass # Yine loop yoksa yapacak bir şey yok
85
191
 
192
+ def remove_if_expired(self, key):
193
+ """
194
+ Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
195
+ """
196
+ if self._ttl is not UNLIMITED:
197
+ t = self._times.get(key)
198
+ if t is not None and (time.time() - t > self._ttl):
199
+ # konsol.log(f"[red][-] {key}")
200
+ self._data.pop(key, None)
201
+ self._times.pop(key, None)
202
+ self.futures.pop(key, None)
203
+
86
204
  async def get(self, key):
87
205
  """
88
206
  Belirtilen key için cache'de saklanan değeri asenkron olarak döndürür.
89
207
  Eğer key bulunamazsa, ilgili future üzerinden beklemeyi sağlar.
90
208
  """
209
+ # Cleanup task'in aktif olduğundan emin olun.
91
210
  self.ensure_cleanup_task()
211
+ # Eğer key'in süresi dolmuşsa, kaldırın.
92
212
  self.remove_if_expired(key)
93
213
 
94
214
  try:
95
- return self._data[key]
96
- except KeyError as e:
215
+ # Cache içerisinde key varsa direkt değeri döndür.
216
+ value = self._data[key]
217
+ # konsol.log(f"[yellow][~] {key}")
218
+ return value
219
+ except KeyError:
220
+ # Eğer key cache'de yoksa, aynı key ile başlatılmış future varsa onu bekle.
97
221
  future = self.futures.get(key)
98
222
  if future:
99
223
  await future
100
- return future.result()
224
+ # Future tamamlandığında sonucu döndür.
225
+ value = future.result()
226
+ # konsol.log(f"[yellow][?] {key}")
227
+ return value
228
+ # Eğer future da yoksa, key bulunamadığına dair hata fırlat.
229
+ raise KeyError(key)
230
+
231
+ async def set(self, key, value):
232
+ """Belirtilen key için cache'e değer ekler."""
233
+ self.ensure_cleanup_task()
234
+ self._data[key] = value
235
+ if self._ttl is not UNLIMITED:
236
+ self._times[key] = time.time()
101
237
 
102
- raise e
238
+ class HybridAsyncCache:
239
+ """
240
+ Öncelikle Redis cache kullanılmaya çalışılır.
241
+ Hata durumunda veya Redis erişilemiyorsa in-memory AsyncCache’e geçilir.
242
+ """
243
+ def __init__(self, ttl=UNLIMITED):
244
+ self._ttl = ttl
103
245
 
104
- def remove_if_expired(self, key):
105
- """
106
- Belirtilen key'in süresi dolduysa, cache ve futures içerisinden temizler.
107
- """
108
- if self._ttl is not UNLIMITED and self._is_expired(key):
109
- self._data.pop(key, None)
110
- self._times.pop(key, None)
111
- self.futures.pop(key, None)
246
+ try:
247
+ self.redis = redis.Redis(
248
+ host = "127.0.0.1",
249
+ port = 6379,
250
+ decode_responses = False
251
+ )
252
+ except Exception:
253
+ self.redis = None
254
+
255
+ self.memory = AsyncCache(ttl)
256
+ self.futures = {}
257
+
258
+ async def get(self, key):
259
+ # Eşzamanlı istek yönetimi: aynı key için bir future varsa, direkt bekleyip sonucu döndür.
260
+ if key in self.futures:
261
+ # konsol.log(f"[yellow][?] {key}")
262
+ return await self.futures[key]
112
263
 
264
+ # İlk önce Redis ile deneyelim
265
+ if self.redis:
266
+ try:
267
+ data = await self.redis.get(key)
268
+ except Exception:
269
+ return await self.memory.get(key)
270
+ if data is not None:
271
+ try:
272
+ result = pickle.loads(data)
273
+ # konsol.log(f"[yellow][~] {key}")
274
+ return result
275
+ except Exception:
276
+ # Deserialize hatası durumunda in-memory cache'ten dene
277
+ return await self.memory.get(key)
278
+ else:
279
+ # Redis'te veri yoksa, in-memory cache'e bak
280
+ return await self.memory.get(key)
281
+ else:
282
+ # Redis kullanılmıyorsa doğrudan in-memory cache'e bak
283
+ return await self.memory.get(key)
284
+
285
+ async def set(self, key, value):
286
+ # Önce veriyi pickle etmeyi deniyoruz.
287
+ try:
288
+ ser = pickle.dumps(value)
289
+ except Exception:
290
+ # Serialization hatası durumunda sadece in-memory cache'e yaz
291
+ await self.memory.set(key, value)
292
+ return
293
+
294
+ if self.redis:
295
+ try:
296
+ if self._ttl is not UNLIMITED:
297
+ await self.redis.set(key, ser, ex=self._ttl)
298
+ else:
299
+ await self.redis.set(key, ser)
300
+ return
301
+ except Exception:
302
+ # Redis yazma hatası durumunda in-memory fallback
303
+ await self.memory.set(key, value)
304
+ return
305
+ else:
306
+ # Redis yoksa in-memory cache'e yaz
307
+ await self.memory.set(key, value)
308
+
309
+ # -----------------------------------------------------
310
+ # Cache'e Hesaplanmış Sonucu Yazma Yardımcı Fonksiyonları
311
+ # -----------------------------------------------------
113
312
 
114
313
  def _sync_maybe_cache(func, key, result, unless):
115
- """Senkron sonuç için cache kaydını oluşturur (unless koşuluna bakarak)."""
314
+ """Senkron fonksiyon sonucu için cache kaydı oluşturur."""
116
315
  if unless is None or not unless(result):
117
316
  func.__cache[key] = result
118
-
317
+ # konsol.log(f"[green][+] {key}")
119
318
 
120
319
  async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
121
320
  """
122
- Asenkron fonksiyon sonucunu hesaplar ve cache'e ekler.
123
- Aynı key için işlem devam ediyorsa, mevcut sonucu bekler.
124
- Sonuç, unless(result) True değilse cache'e eklenir.
321
+ Asenkron fonksiyon sonucunu hesaplar ve cache'e yazar.
322
+ Aynı key için gelen eşzamanlı çağrılar future üzerinden bekletilir.
125
323
  """
126
324
  # __cache'den cache nesnesini alıyoruz.
127
325
  cache = func.__cache
@@ -138,11 +336,12 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
138
336
  # Asenkron fonksiyonu çalıştır ve sonucu elde et.
139
337
  result = await func(*args, **kwargs)
140
338
  future.set_result(result)
141
-
339
+
142
340
  # unless koşuluna göre cache'e ekleme yap.
143
341
  if unless is None or not unless(result):
144
- cache[key] = result
145
-
342
+ await cache.set(key, result)
343
+ # konsol.log(f"[green][+] {key}")
344
+
146
345
  return result
147
346
  except Exception as exc:
148
347
  future.cancel()
@@ -151,72 +350,39 @@ async def _async_compute_and_cache(func, key, unless, *args, **kwargs):
151
350
  # İşlem tamamlandığında future'ı temizle.
152
351
  cache.futures.pop(key, None)
153
352
 
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
-
353
+ # -----------------------------------------------------
354
+ # kekik_cache Dekoratörü (Senkrondan Asenkrona)
355
+ # -----------------------------------------------------
182
356
 
183
- def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
357
+ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False, use_redis=True):
184
358
  """
185
- Bir fonksiyon veya coroutine'in sonucunu cache'ler.
186
-
187
- Args:
188
- ttl (int, optional): Cachein geçerlilik süresi (saniye).
189
- Eğer `UNLIMITED` ise süresizdir. Varsayılan olarak `UNLIMITED`'dir.
190
- unless (callable, optional): Fonksiyonun sonucunu argüman olarak alan bir callable.
191
- Eğer `True` dönerse, sonuç cache'e alınmaz. Varsayılan olarak `None`'dır.
192
- is_fastapi (bool, optional): Eğer `True` ise, cache key'i oluştururken FastAPI request nesnesine özel şekilde davranır.
193
- Varsayılan olarak `False`'tır.
194
-
195
- Notlar:
196
- -------
197
- Normalde yalnızca Redis cache kullanılmak istenir.
198
- Ancak Redis'e ulaşılamayan durumlarda RAM fallback kullanılır.
199
-
200
- ---
201
-
202
- Örn.:
359
+ Bir fonksiyonun (senkron/asenkron) sonucunu cache'ler.
360
+
361
+ Parametreler:
362
+ - ttl: Cache'in geçerlilik süresi (saniye). UNLIMITED ise süresizdir.
363
+ - unless: Sonuç alınmadan önce çağrılan, True dönerse cache'e alınmaz.
364
+ - is_fastapi: True ise, FastAPI Request nesnesine göre key oluşturur.
365
+ - use_redis: Asenkron fonksiyonlarda Redis kullanımı (Hybrid cache) için True verilebilir.
203
366
 
204
- @kekik_cache(ttl=15, unless=lambda sonuc: bool(sonuc is None))
367
+ Örnek Kullanım:
368
+
369
+ @kekik_cache(ttl=15, unless=lambda sonuc: sonuc is None)
205
370
  async def bakalim(param):
206
- # Burada cache işlemi yapılır.
207
371
  return param
208
372
  """
209
- # Parametresiz kullanıldığında
373
+ # Parametresiz kullanım durumunda
210
374
  if callable(ttl):
211
- return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi)(ttl)
375
+ return kekik_cache(UNLIMITED, unless=unless, is_fastapi=is_fastapi, use_redis=use_redis)(ttl)
212
376
 
213
377
  def decorator(func):
214
- func.__cache = AsyncCache(ttl)
215
-
216
378
  if asyncio.iscoroutinefunction(func):
379
+ # Asenkron fonksiyonlar için cache türünü seçelim:
380
+
381
+ func.__cache = HybridAsyncCache(ttl) if use_redis else AsyncCache(ttl)
382
+
217
383
  @wraps(func)
218
384
  async def async_wrapper(*args, **kwargs):
219
- key = await make_cache_key(args, kwargs, is_fastapi)
385
+ key = await make_cache_key(func, args, kwargs, is_fastapi)
220
386
 
221
387
  try:
222
388
  return await func.__cache.get(key)
@@ -225,9 +391,12 @@ def kekik_cache(ttl=UNLIMITED, unless=None, is_fastapi=False):
225
391
 
226
392
  return async_wrapper
227
393
  else:
394
+ # Senkron fonksiyonlar için
395
+ func.__cache = SyncCache(ttl)
396
+
228
397
  @wraps(func)
229
398
  def sync_wrapper(*args, **kwargs):
230
- key = (args, tuple(sorted(kwargs.items())))
399
+ key = simple_cache_key(func, args, kwargs)
231
400
 
232
401
  try:
233
402
  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.2
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=vJ9b7eAyKH0Z2JCW5JIDsyLkNwXvWkS3vnJpv_mj0Us,14892
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.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
38
+ Kekik-1.7.2.dist-info/METADATA,sha256=7RQJjD9uh3TDqIPkeRQ8bszs0gVcZJlFf8BMaP31r2U,43959
39
+ Kekik-1.7.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
+ Kekik-1.7.2.dist-info/entry_points.txt,sha256=yjBifxtRlqfg8lPkH4Bu-urSa5ecptCHsuth-DcyWcg,59
41
+ Kekik-1.7.2.dist-info/top_level.txt,sha256=NotddscfgxawvuRyAa7xkgnMhyteFDcBxb5aU5GY3BM,6
42
+ Kekik-1.7.2.dist-info/RECORD,,
File without changes
File without changes