tree-sitter-analyzer 1.9.17.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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unified Cache Service - Common Cache System for CLI and MCP
|
|
4
|
+
|
|
5
|
+
This module provides a memory-efficient hierarchical cache system.
|
|
6
|
+
Achieves optimal performance with a 3-tier structure: L1 (fast), L2 (medium-term), L3 (long-term).
|
|
7
|
+
|
|
8
|
+
Roo Code compliance:
|
|
9
|
+
- Type hints: Required for all functions
|
|
10
|
+
- MCP logging: Log output at each step
|
|
11
|
+
- docstring: Google Style docstring
|
|
12
|
+
- Performance-focused: Optimization of memory efficiency and access speed
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import threading
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from cachetools import LRUCache, TTLCache # type: ignore[import-untyped]
|
|
22
|
+
|
|
23
|
+
from ..utils import log_debug, log_info
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class CacheEntry:
|
|
28
|
+
"""
|
|
29
|
+
Cache Entry
|
|
30
|
+
|
|
31
|
+
Data class that holds cached values and metadata.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
value: Cached value
|
|
35
|
+
created_at: Creation timestamp
|
|
36
|
+
expires_at: Expiration time
|
|
37
|
+
access_count: Access count
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
value: Any
|
|
41
|
+
created_at: datetime
|
|
42
|
+
expires_at: datetime | None = None
|
|
43
|
+
access_count: int = 0
|
|
44
|
+
|
|
45
|
+
def is_expired(self) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Expiration check
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
bool: True if expired
|
|
51
|
+
"""
|
|
52
|
+
if self.expires_at is None:
|
|
53
|
+
return False
|
|
54
|
+
return datetime.now() > self.expires_at
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CacheService:
|
|
58
|
+
"""
|
|
59
|
+
Unified Cache Service
|
|
60
|
+
|
|
61
|
+
Provides hierarchical cache system and shares cache between CLI and MCP.
|
|
62
|
+
3-tier structure optimized for memory efficiency and access speed.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
_l1_cache: L1 cache (for fast access)
|
|
66
|
+
_l2_cache: L2 cache (for medium-term storage)
|
|
67
|
+
_l3_cache: L3 cache (for long-term storage)
|
|
68
|
+
_lock: Lock for thread safety
|
|
69
|
+
_stats: Cache statistics
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
l1_maxsize: int = 100,
|
|
75
|
+
l2_maxsize: int = 1000,
|
|
76
|
+
l3_maxsize: int = 10000,
|
|
77
|
+
ttl_seconds: int = 3600,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Initialization
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
l1_maxsize: Maximum size of L1 cache
|
|
84
|
+
l2_maxsize: Maximum size of L2 cache
|
|
85
|
+
l3_maxsize: Maximum size of L3 cache
|
|
86
|
+
ttl_seconds: Default TTL (seconds)
|
|
87
|
+
"""
|
|
88
|
+
# Initialize hierarchical cache
|
|
89
|
+
self._l1_cache: LRUCache[str, CacheEntry] = LRUCache(maxsize=l1_maxsize)
|
|
90
|
+
self._l2_cache: TTLCache[str, CacheEntry] = TTLCache(
|
|
91
|
+
maxsize=l2_maxsize, ttl=ttl_seconds
|
|
92
|
+
)
|
|
93
|
+
self._l3_cache: LRUCache[str, CacheEntry] = LRUCache(maxsize=l3_maxsize)
|
|
94
|
+
|
|
95
|
+
# Lock for thread safety
|
|
96
|
+
self._lock = threading.RLock()
|
|
97
|
+
|
|
98
|
+
# Cache statistics
|
|
99
|
+
self._stats = {
|
|
100
|
+
"hits": 0,
|
|
101
|
+
"misses": 0,
|
|
102
|
+
"l1_hits": 0,
|
|
103
|
+
"l2_hits": 0,
|
|
104
|
+
"l3_hits": 0,
|
|
105
|
+
"sets": 0,
|
|
106
|
+
"evictions": 0,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# デフォルト設定
|
|
110
|
+
self._default_ttl = ttl_seconds
|
|
111
|
+
|
|
112
|
+
log_debug(
|
|
113
|
+
f"CacheService initialized: L1={l1_maxsize}, L2={l2_maxsize}, "
|
|
114
|
+
f"L3={l3_maxsize}, TTL={ttl_seconds}s"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
async def get(self, key: str) -> Any | None:
|
|
118
|
+
"""
|
|
119
|
+
キャッシュから値を取得
|
|
120
|
+
|
|
121
|
+
階層キャッシュを順番にチェックし、見つかった場合は
|
|
122
|
+
上位キャッシュに昇格させる。
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
key: キャッシュキー
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
キャッシュされた値、見つからない場合はNone
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ValueError: 無効なキーの場合
|
|
132
|
+
"""
|
|
133
|
+
if not key or key is None:
|
|
134
|
+
raise ValueError("Cache key cannot be empty or None")
|
|
135
|
+
|
|
136
|
+
with self._lock:
|
|
137
|
+
# L1キャッシュをチェック
|
|
138
|
+
entry = self._l1_cache.get(key)
|
|
139
|
+
if entry and not entry.is_expired():
|
|
140
|
+
self._stats["hits"] += 1
|
|
141
|
+
self._stats["l1_hits"] += 1
|
|
142
|
+
log_debug(f"Cache L1 hit: {key}")
|
|
143
|
+
return entry.value
|
|
144
|
+
|
|
145
|
+
# L2キャッシュをチェック
|
|
146
|
+
entry = self._l2_cache.get(key)
|
|
147
|
+
if entry and not entry.is_expired():
|
|
148
|
+
self._stats["hits"] += 1
|
|
149
|
+
self._stats["l2_hits"] += 1
|
|
150
|
+
# L1に昇格
|
|
151
|
+
self._l1_cache[key] = entry
|
|
152
|
+
log_debug(f"Cache L2 hit: {key} (promoted to L1)")
|
|
153
|
+
return entry.value
|
|
154
|
+
|
|
155
|
+
# L3キャッシュをチェック
|
|
156
|
+
entry = self._l3_cache.get(key)
|
|
157
|
+
if entry and not entry.is_expired():
|
|
158
|
+
self._stats["hits"] += 1
|
|
159
|
+
self._stats["l3_hits"] += 1
|
|
160
|
+
# L2とL1に昇格
|
|
161
|
+
self._l2_cache[key] = entry
|
|
162
|
+
self._l1_cache[key] = entry
|
|
163
|
+
log_debug(f"Cache L3 hit: {key} (promoted to L1/L2)")
|
|
164
|
+
return entry.value
|
|
165
|
+
|
|
166
|
+
# キャッシュミス
|
|
167
|
+
self._stats["misses"] += 1
|
|
168
|
+
log_debug(f"Cache miss: {key}")
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
async def set(self, key: str, value: Any, ttl_seconds: int | None = None) -> None:
|
|
172
|
+
"""
|
|
173
|
+
キャッシュに値を設定
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
key: キャッシュキー
|
|
177
|
+
value: キャッシュする値
|
|
178
|
+
ttl_seconds: TTL(秒)、Noneの場合はデフォルト値
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: 無効なキーの場合
|
|
182
|
+
TypeError: シリアライズできない値の場合
|
|
183
|
+
"""
|
|
184
|
+
if not key or key is None:
|
|
185
|
+
raise ValueError("Cache key cannot be empty or None")
|
|
186
|
+
|
|
187
|
+
# シリアライズ可能性チェック(安全のため標準の pickle を最小限に使用)
|
|
188
|
+
import pickle # nosec B403
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
pickle.dumps(value)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
# 具体的なエラー型に依存せず、直感的な TypeError に正規化
|
|
194
|
+
raise TypeError(f"Value is not serializable: {e}") from e
|
|
195
|
+
|
|
196
|
+
ttl = ttl_seconds or self._default_ttl
|
|
197
|
+
expires_at = datetime.now() + timedelta(seconds=ttl)
|
|
198
|
+
|
|
199
|
+
entry = CacheEntry(
|
|
200
|
+
value=value,
|
|
201
|
+
created_at=datetime.now(),
|
|
202
|
+
expires_at=expires_at,
|
|
203
|
+
access_count=0,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
with self._lock:
|
|
207
|
+
# 全階層に設定
|
|
208
|
+
self._l1_cache[key] = entry
|
|
209
|
+
self._l2_cache[key] = entry
|
|
210
|
+
self._l3_cache[key] = entry
|
|
211
|
+
|
|
212
|
+
self._stats["sets"] += 1
|
|
213
|
+
log_debug(f"Cache set: {key} (TTL={ttl}s)")
|
|
214
|
+
|
|
215
|
+
def clear(self) -> None:
|
|
216
|
+
"""
|
|
217
|
+
全キャッシュをクリア
|
|
218
|
+
"""
|
|
219
|
+
with self._lock:
|
|
220
|
+
self._l1_cache.clear()
|
|
221
|
+
self._l2_cache.clear()
|
|
222
|
+
self._l3_cache.clear()
|
|
223
|
+
|
|
224
|
+
# 統計をリセット
|
|
225
|
+
for key in self._stats:
|
|
226
|
+
self._stats[key] = 0
|
|
227
|
+
|
|
228
|
+
# Only log if not in quiet mode (check log level)
|
|
229
|
+
import logging
|
|
230
|
+
|
|
231
|
+
if logging.getLogger("tree_sitter_analyzer").level <= logging.INFO:
|
|
232
|
+
log_info("All caches cleared")
|
|
233
|
+
|
|
234
|
+
def size(self) -> int:
|
|
235
|
+
"""
|
|
236
|
+
キャッシュサイズを取得
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
L1キャッシュのサイズ(最も頻繁にアクセスされるアイテム数)
|
|
240
|
+
"""
|
|
241
|
+
with self._lock:
|
|
242
|
+
return len(self._l1_cache)
|
|
243
|
+
|
|
244
|
+
def get_stats(self) -> dict[str, Any]:
|
|
245
|
+
"""
|
|
246
|
+
キャッシュ統計を取得
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
統計情報辞書
|
|
250
|
+
"""
|
|
251
|
+
with self._lock:
|
|
252
|
+
total_requests = self._stats["hits"] + self._stats["misses"]
|
|
253
|
+
hit_rate = (
|
|
254
|
+
self._stats["hits"] / total_requests if total_requests > 0 else 0.0
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
**self._stats,
|
|
259
|
+
"hit_rate": hit_rate,
|
|
260
|
+
"total_requests": total_requests,
|
|
261
|
+
"l1_size": len(self._l1_cache),
|
|
262
|
+
"l2_size": len(self._l2_cache),
|
|
263
|
+
"l3_size": len(self._l3_cache),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
def generate_cache_key(
|
|
267
|
+
self, file_path: str, language: str, options: dict[str, Any]
|
|
268
|
+
) -> str:
|
|
269
|
+
"""
|
|
270
|
+
キャッシュキーを生成
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
file_path: ファイルパス
|
|
274
|
+
language: プログラミング言語
|
|
275
|
+
options: 解析オプション
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
ハッシュ化されたキャッシュキー
|
|
279
|
+
"""
|
|
280
|
+
# 一意なキーを生成するための文字列を構築
|
|
281
|
+
key_components = [
|
|
282
|
+
file_path,
|
|
283
|
+
language,
|
|
284
|
+
str(sorted(options.items())), # 辞書を安定した文字列に変換
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
key_string = ":".join(key_components)
|
|
288
|
+
|
|
289
|
+
# SHA256でハッシュ化
|
|
290
|
+
return hashlib.sha256(key_string.encode("utf-8")).hexdigest()
|
|
291
|
+
|
|
292
|
+
async def invalidate_pattern(self, pattern: str) -> int:
|
|
293
|
+
"""
|
|
294
|
+
パターンに一致するキーを無効化
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
pattern: 無効化するキーのパターン
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
無効化されたキー数
|
|
301
|
+
"""
|
|
302
|
+
invalidated_count = 0
|
|
303
|
+
|
|
304
|
+
with self._lock:
|
|
305
|
+
# 各階層からパターンに一致するキーを削除
|
|
306
|
+
for cache in [self._l1_cache, self._l2_cache, self._l3_cache]:
|
|
307
|
+
keys_to_remove = [key for key in cache.keys() if pattern in key]
|
|
308
|
+
|
|
309
|
+
for key in keys_to_remove:
|
|
310
|
+
if key in cache:
|
|
311
|
+
del cache[key]
|
|
312
|
+
invalidated_count += 1
|
|
313
|
+
|
|
314
|
+
log_info(
|
|
315
|
+
f"Invalidated {invalidated_count} cache entries matching pattern: {pattern}"
|
|
316
|
+
)
|
|
317
|
+
return invalidated_count
|
|
318
|
+
|
|
319
|
+
def __del__(self) -> None:
|
|
320
|
+
"""デストラクタ - リソースクリーンアップ"""
|
|
321
|
+
try:
|
|
322
|
+
# Only clear if not in shutdown mode
|
|
323
|
+
import sys
|
|
324
|
+
|
|
325
|
+
if sys.meta_path is not None: # Check if Python is not shutting down
|
|
326
|
+
# Clear caches without logging to avoid shutdown issues
|
|
327
|
+
with self._lock:
|
|
328
|
+
self._l1_cache.clear()
|
|
329
|
+
self._l2_cache.clear()
|
|
330
|
+
self._l3_cache.clear()
|
|
331
|
+
except Exception:
|
|
332
|
+
# Silently ignore all errors during shutdown to prevent ImportError
|
|
333
|
+
pass # nosec
|