mftool 3.4__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.
mftool/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2026 Sujit Nayakwadi
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+ __VERSION__='3.4'
25
+ from .mftool import Mftool
26
+
mftool/cache.py ADDED
@@ -0,0 +1,233 @@
1
+ """
2
+ Cache utility for mftool
3
+ Implements TTL-based caching to reduce redundant API calls
4
+ """
5
+ import time
6
+ import json
7
+ import os
8
+ from functools import wraps
9
+ from threading import Lock
10
+
11
+
12
+ class CacheManager:
13
+ """
14
+ Thread-safe in-memory cache with TTL support
15
+ """
16
+ def __init__(self, default_ttl=86400): # 24 hours default
17
+ self._cache = {}
18
+ self._lock = Lock()
19
+ self.default_ttl = default_ttl
20
+ self.enabled = True
21
+
22
+ def get(self, key):
23
+ """Get value from cache if not expired"""
24
+ if not self.enabled:
25
+ return None
26
+
27
+ with self._lock:
28
+ if key in self._cache:
29
+ value, expiry = self._cache[key]
30
+ if time.time() < expiry:
31
+ return value
32
+ else:
33
+ # Remove expired entry
34
+ del self._cache[key]
35
+ return None
36
+
37
+ def set(self, key, value, ttl=None):
38
+ """Set value in cache with TTL"""
39
+ if not self.enabled:
40
+ return
41
+
42
+ if ttl is None:
43
+ ttl = self.default_ttl
44
+
45
+ expiry = time.time() + ttl
46
+ with self._lock:
47
+ self._cache[key] = (value, expiry)
48
+
49
+ def clear(self):
50
+ """Clear all cache entries"""
51
+ with self._lock:
52
+ self._cache.clear()
53
+
54
+ def clear_expired(self):
55
+ """Remove all expired entries"""
56
+ current_time = time.time()
57
+ with self._lock:
58
+ expired_keys = [k for k, (_, expiry) in self._cache.items() if current_time >= expiry]
59
+ for key in expired_keys:
60
+ del self._cache[key]
61
+
62
+ def disable(self):
63
+ """Disable caching"""
64
+ self.enabled = False
65
+
66
+ def enable(self):
67
+ """Enable caching"""
68
+ self.enabled = True
69
+
70
+ def get_stats(self):
71
+ """Get cache statistics"""
72
+ with self._lock:
73
+ total_entries = len(self._cache)
74
+ current_time = time.time()
75
+ valid_entries = sum(1 for _, expiry in self._cache.values() if current_time < expiry)
76
+ expired_entries = total_entries - valid_entries
77
+
78
+ return {
79
+ 'total_entries': total_entries,
80
+ 'valid_entries': valid_entries,
81
+ 'expired_entries': expired_entries,
82
+ 'cache_enabled': self.enabled
83
+ }
84
+
85
+
86
+ class DiskCache:
87
+ """
88
+ Persistent disk-based cache with TTL support
89
+ """
90
+ def __init__(self, cache_dir=None, default_ttl=86400):
91
+ if cache_dir is None:
92
+ cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.cache')
93
+
94
+ self.cache_dir = cache_dir
95
+ self.default_ttl = default_ttl
96
+ self.enabled = True
97
+ self._lock = Lock()
98
+
99
+ # Create cache directory if it doesn't exist
100
+ if not os.path.exists(self.cache_dir):
101
+ os.makedirs(self.cache_dir)
102
+
103
+ def _get_cache_path(self, key):
104
+ """Generate cache file path for key"""
105
+ # Use hash to create safe filename
106
+ safe_key = str(abs(hash(key)))
107
+ return os.path.join(self.cache_dir, f"{safe_key}.json")
108
+
109
+ def get(self, key):
110
+ """Get value from disk cache if not expired"""
111
+ if not self.enabled:
112
+ return None
113
+
114
+ cache_path = self._get_cache_path(key)
115
+
116
+ with self._lock:
117
+ if os.path.exists(cache_path):
118
+ try:
119
+ with open(cache_path, 'r') as f:
120
+ cache_data = json.load(f)
121
+
122
+ if time.time() < cache_data['expiry']:
123
+ return cache_data['value']
124
+ else:
125
+ # Remove expired file
126
+ os.remove(cache_path)
127
+ except (json.JSONDecodeError, KeyError, IOError):
128
+ # Invalid cache file, remove it
129
+ if os.path.exists(cache_path):
130
+ os.remove(cache_path)
131
+
132
+ return None
133
+
134
+ def set(self, key, value, ttl=None):
135
+ """Set value in disk cache with TTL"""
136
+ if not self.enabled:
137
+ return
138
+
139
+ if ttl is None:
140
+ ttl = self.default_ttl
141
+
142
+ cache_path = self._get_cache_path(key)
143
+ expiry = time.time() + ttl
144
+
145
+ cache_data = {
146
+ 'value': value,
147
+ 'expiry': expiry,
148
+ 'created': time.time()
149
+ }
150
+
151
+ with self._lock:
152
+ try:
153
+ with open(cache_path, 'w') as f:
154
+ json.dump(cache_data, f)
155
+ except IOError:
156
+ pass # Silently fail if can't write cache
157
+
158
+ def clear(self):
159
+ """Clear all cache files"""
160
+ with self._lock:
161
+ if os.path.exists(self.cache_dir):
162
+ for filename in os.listdir(self.cache_dir):
163
+ file_path = os.path.join(self.cache_dir, filename)
164
+ if os.path.isfile(file_path):
165
+ os.remove(file_path)
166
+
167
+ def clear_expired(self):
168
+ """Remove all expired cache files"""
169
+ current_time = time.time()
170
+ with self._lock:
171
+ if os.path.exists(self.cache_dir):
172
+ for filename in os.listdir(self.cache_dir):
173
+ file_path = os.path.join(self.cache_dir, filename)
174
+ if os.path.isfile(file_path):
175
+ try:
176
+ with open(file_path, 'r') as f:
177
+ cache_data = json.load(f)
178
+ if current_time >= cache_data['expiry']:
179
+ os.remove(file_path)
180
+ except (json.JSONDecodeError, KeyError, IOError):
181
+ os.remove(file_path)
182
+
183
+ def disable(self):
184
+ """Disable caching"""
185
+ self.enabled = False
186
+
187
+ def enable(self):
188
+ """Enable caching"""
189
+ self.enabled = True
190
+
191
+
192
+ def cached(ttl=86400, cache_type='memory'):
193
+ """
194
+ Decorator for caching function results
195
+
196
+ :param ttl: Time to live in seconds (default 24 hours)
197
+ :param cache_type: 'memory' or 'disk'
198
+ """
199
+ def decorator(func):
200
+ # Create cache instance for this function
201
+ if cache_type == 'disk':
202
+ cache = DiskCache(default_ttl=ttl)
203
+ else:
204
+ cache = CacheManager(default_ttl=ttl)
205
+
206
+ @wraps(func)
207
+ def wrapper(*args, **kwargs):
208
+ # Create cache key from function name and arguments
209
+ # Skip 'self' for instance methods
210
+ cache_args = args[1:] if args and hasattr(args[0], func.__name__) else args
211
+ cache_key = f"{func.__name__}:{str(cache_args)}:{str(kwargs)}"
212
+
213
+ # Try to get from cache
214
+ cached_value = cache.get(cache_key)
215
+ if cached_value is not None:
216
+ return cached_value
217
+
218
+ # Call function and cache result
219
+ result = func(*args, **kwargs)
220
+ if result is not None:
221
+ cache.set(cache_key, result, ttl)
222
+
223
+ return result
224
+
225
+ # Attach cache management methods
226
+ wrapper.clear_cache = cache.clear
227
+ wrapper.clear_expired = cache.clear_expired
228
+ wrapper.disable_cache = cache.disable
229
+ wrapper.enable_cache = cache.enable
230
+
231
+ return wrapper
232
+ return decorator
233
+