tree-sitter-analyzer 0.9.2__py3-none-any.whl → 0.9.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.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (37) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/cli/commands/base_command.py +2 -3
  3. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  5. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  6. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  7. tree_sitter_analyzer/cli/info_commands.py +121 -121
  8. tree_sitter_analyzer/cli_main.py +307 -303
  9. tree_sitter_analyzer/core/analysis_engine.py +584 -576
  10. tree_sitter_analyzer/core/cache_service.py +6 -5
  11. tree_sitter_analyzer/core/query.py +502 -502
  12. tree_sitter_analyzer/encoding_utils.py +6 -2
  13. tree_sitter_analyzer/exceptions.py +400 -406
  14. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  15. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  16. tree_sitter_analyzer/interfaces/cli.py +1 -1
  17. tree_sitter_analyzer/interfaces/cli_adapter.py +3 -3
  18. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  19. tree_sitter_analyzer/language_detector.py +398 -398
  20. tree_sitter_analyzer/language_loader.py +224 -224
  21. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  22. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  23. tree_sitter_analyzer/mcp/server.py +30 -9
  24. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  25. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  26. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  27. tree_sitter_analyzer/models.py +470 -470
  28. tree_sitter_analyzer/project_detector.py +330 -317
  29. tree_sitter_analyzer/security/__init__.py +22 -22
  30. tree_sitter_analyzer/security/boundary_manager.py +243 -237
  31. tree_sitter_analyzer/security/regex_checker.py +297 -292
  32. tree_sitter_analyzer/table_formatter.py +703 -652
  33. tree_sitter_analyzer/utils.py +53 -22
  34. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/METADATA +13 -13
  35. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/RECORD +37 -37
  36. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/WHEEL +0 -0
  37. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/entry_points.txt +0 -0
@@ -1,259 +1,259 @@
1
- #!/usr/bin/env python3
2
- """
3
- Python-specific table formatter.
4
- """
5
-
6
- from typing import Any
7
-
8
- from .base_formatter import BaseTableFormatter
9
-
10
-
11
- class PythonTableFormatter(BaseTableFormatter):
12
- """Table formatter specialized for Python"""
13
-
14
- def _format_full_table(self, data: dict[str, Any]) -> str:
15
- """Full table format for Python"""
16
- lines = []
17
-
18
- # Header - Python (multi-class supported)
19
- classes = data.get("classes", [])
20
- if len(classes) > 1:
21
- # If multiple classes exist, use filename
22
- file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
23
- lines.append(f"# {file_name}")
24
- else:
25
- # Single class: use class name
26
- class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
27
- lines.append(f"# {class_name}")
28
- lines.append("")
29
-
30
- # Imports
31
- imports = data.get("imports", [])
32
- if imports:
33
- lines.append("## Imports")
34
- lines.append("```python")
35
- for imp in imports:
36
- lines.append(str(imp.get("statement", "")))
37
- lines.append("```")
38
- lines.append("")
39
-
40
- # Classes - Python (multi-class aware)
41
- if len(classes) > 1:
42
- lines.append("## Classes")
43
- lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
44
- lines.append("|-------|------|------------|-------|---------|--------|")
45
-
46
- for class_info in classes:
47
- name = str(class_info.get("name", "Unknown"))
48
- class_type = str(class_info.get("type", "class"))
49
- visibility = str(class_info.get("visibility", "public"))
50
- line_range = class_info.get("line_range", {})
51
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
52
-
53
- # Count methods/fields within the class range
54
- class_methods = [
55
- m
56
- for m in data.get("methods", [])
57
- if line_range.get("start", 0)
58
- <= m.get("line_range", {}).get("start", 0)
59
- <= line_range.get("end", 0)
60
- ]
61
- class_fields = [
62
- f
63
- for f in data.get("fields", [])
64
- if line_range.get("start", 0)
65
- <= f.get("line_range", {}).get("start", 0)
66
- <= line_range.get("end", 0)
67
- ]
68
-
69
- lines.append(
70
- f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
71
- )
72
- else:
73
- # Single class details
74
- lines.append("## Class Info")
75
- lines.append("| Property | Value |")
76
- lines.append("|----------|-------|")
77
-
78
- class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
79
- stats = data.get("statistics") or {}
80
-
81
- lines.append("| Package | (default) |")
82
- lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
83
- lines.append(
84
- f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
85
- )
86
- lines.append(
87
- f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
88
- )
89
- lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
90
- lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
91
-
92
- lines.append("")
93
-
94
- # Fields
95
- fields = data.get("fields", [])
96
- if fields:
97
- lines.append("## Fields")
98
- lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
99
- lines.append("|------|------|-----|-----------|------|-----|")
100
-
101
- for field in fields:
102
- name = str(field.get("name", ""))
103
- field_type = str(field.get("type", ""))
104
- visibility = self._convert_visibility(str(field.get("visibility", "")))
105
- modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
106
- line = field.get("line_range", {}).get("start", 0)
107
- doc = str(field.get("javadoc", "")) or "-"
108
- doc = doc.replace("\n", " ").replace("|", "\\|")[:50]
109
-
110
- lines.append(
111
- f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
112
- )
113
- lines.append("")
114
-
115
- # Methods - Python (no constructor separation)
116
- methods = data.get("methods", [])
117
- if methods:
118
- lines.append("## Methods")
119
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
120
- lines.append("|--------|-----------|-----|-------|------|----|----|")
121
-
122
- for method in methods:
123
- lines.append(self._format_method_row(method))
124
- lines.append("")
125
-
126
- # Trim trailing blank lines
127
- while lines and lines[-1] == "":
128
- lines.pop()
129
-
130
- return "\n".join(lines)
131
-
132
- def _format_compact_table(self, data: dict[str, Any]) -> str:
133
- """Compact table format for Python"""
134
- lines = []
135
-
136
- # Header
137
- classes = data.get("classes", [])
138
- if len(classes) > 1:
139
- file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
140
- lines.append(f"# {file_name}")
141
- else:
142
- class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
143
- lines.append(f"# {class_name}")
144
- lines.append("")
145
-
146
- # Info
147
- stats = data.get("statistics") or {}
148
- lines.append("## Info")
149
- lines.append("| Property | Value |")
150
- lines.append("|----------|-------|")
151
- lines.append(f"| Classes | {len(classes)} |")
152
- lines.append(f"| Methods | {stats.get('method_count', 0)} |")
153
- lines.append(f"| Fields | {stats.get('field_count', 0)} |")
154
- lines.append("")
155
-
156
- # Methods (compact)
157
- methods = data.get("methods", [])
158
- if methods:
159
- lines.append("## Methods")
160
- lines.append("| Method | Sig | V | L | Cx | Doc |")
161
- lines.append("|--------|-----|---|---|----|----|")
162
-
163
- for method in methods:
164
- name = str(method.get("name", ""))
165
- signature = self._create_compact_signature(method)
166
- visibility = self._convert_visibility(str(method.get("visibility", "")))
167
- line_range = method.get("line_range", {})
168
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
169
- complexity = method.get("complexity_score", 0)
170
- doc = self._clean_csv_text(
171
- self._extract_doc_summary(str(method.get("javadoc", "")))
172
- )
173
-
174
- lines.append(
175
- f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
176
- )
177
- lines.append("")
178
-
179
- # Trim trailing blank lines
180
- while lines and lines[-1] == "":
181
- lines.pop()
182
-
183
- return "\n".join(lines)
184
-
185
- def _format_method_row(self, method: dict[str, Any]) -> str:
186
- """Format a method table row for Python"""
187
- name = str(method.get("name", ""))
188
- signature = self._create_full_signature(method)
189
- visibility = self._convert_visibility(str(method.get("visibility", "")))
190
- line_range = method.get("line_range", {})
191
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
192
- cols_str = "5-6" # default placeholder
193
- complexity = method.get("complexity_score", 0)
194
- doc = self._clean_csv_text(
195
- self._extract_doc_summary(str(method.get("javadoc", "")))
196
- )
197
-
198
- return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
199
-
200
- def _create_compact_signature(self, method: dict[str, Any]) -> str:
201
- """Create compact method signature for Python"""
202
- params = method.get("parameters", [])
203
- param_types = []
204
-
205
- for p in params:
206
- if isinstance(p, dict):
207
- param_types.append(self._shorten_type(p.get("type", "Any")))
208
- else:
209
- param_types.append("Any")
210
-
211
- params_str = ",".join(param_types)
212
- return_type = self._shorten_type(method.get("return_type", "Any"))
213
-
214
- return f"({params_str}):{return_type}"
215
-
216
- def _shorten_type(self, type_name: Any) -> str:
217
- """Shorten type name for Python tables"""
218
- if type_name is None:
219
- return "Any"
220
-
221
- if not isinstance(type_name, str):
222
- type_name = str(type_name)
223
-
224
- type_mapping = {
225
- "str": "s",
226
- "int": "i",
227
- "float": "f",
228
- "bool": "b",
229
- "None": "N",
230
- "Any": "A",
231
- "List": "L",
232
- "Dict": "D",
233
- "Optional": "O",
234
- "Union": "U",
235
- }
236
-
237
- # List[str] -> L[s]
238
- if "List[" in type_name:
239
- result = (
240
- type_name.replace("List[", "L[").replace("str", "s").replace("int", "i")
241
- )
242
- return str(result)
243
-
244
- # Dict[str, int] -> D[s,i]
245
- if "Dict[" in type_name:
246
- result = (
247
- type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
248
- )
249
- return str(result)
250
-
251
- # Optional[str] -> O[s]
252
- if "Optional[" in type_name:
253
- result = type_name.replace("Optional[", "O[").replace("str", "s")
254
- return str(result)
255
-
256
- result = type_mapping.get(
257
- type_name, type_name[:3] if len(type_name) > 3 else type_name
258
- )
259
- return str(result)
1
+ #!/usr/bin/env python3
2
+ """
3
+ Python-specific table formatter.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from .base_formatter import BaseTableFormatter
9
+
10
+
11
+ class PythonTableFormatter(BaseTableFormatter):
12
+ """Table formatter specialized for Python"""
13
+
14
+ def _format_full_table(self, data: dict[str, Any]) -> str:
15
+ """Full table format for Python"""
16
+ lines = []
17
+
18
+ # Header - Python (multi-class supported)
19
+ classes = data.get("classes", [])
20
+ if len(classes) > 1:
21
+ # If multiple classes exist, use filename
22
+ file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
23
+ lines.append(f"# {file_name}")
24
+ else:
25
+ # Single class: use class name
26
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
27
+ lines.append(f"# {class_name}")
28
+ lines.append("")
29
+
30
+ # Imports
31
+ imports = data.get("imports", [])
32
+ if imports:
33
+ lines.append("## Imports")
34
+ lines.append("```python")
35
+ for imp in imports:
36
+ lines.append(str(imp.get("statement", "")))
37
+ lines.append("```")
38
+ lines.append("")
39
+
40
+ # Classes - Python (multi-class aware)
41
+ if len(classes) > 1:
42
+ lines.append("## Classes")
43
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
44
+ lines.append("|-------|------|------------|-------|---------|--------|")
45
+
46
+ for class_info in classes:
47
+ name = str(class_info.get("name", "Unknown"))
48
+ class_type = str(class_info.get("type", "class"))
49
+ visibility = str(class_info.get("visibility", "public"))
50
+ line_range = class_info.get("line_range", {})
51
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
52
+
53
+ # Count methods/fields within the class range
54
+ class_methods = [
55
+ m
56
+ for m in data.get("methods", [])
57
+ if line_range.get("start", 0)
58
+ <= m.get("line_range", {}).get("start", 0)
59
+ <= line_range.get("end", 0)
60
+ ]
61
+ class_fields = [
62
+ f
63
+ for f in data.get("fields", [])
64
+ if line_range.get("start", 0)
65
+ <= f.get("line_range", {}).get("start", 0)
66
+ <= line_range.get("end", 0)
67
+ ]
68
+
69
+ lines.append(
70
+ f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
71
+ )
72
+ else:
73
+ # Single class details
74
+ lines.append("## Class Info")
75
+ lines.append("| Property | Value |")
76
+ lines.append("|----------|-------|")
77
+
78
+ class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
79
+ stats = data.get("statistics") or {}
80
+
81
+ lines.append("| Package | (default) |")
82
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
83
+ lines.append(
84
+ f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
85
+ )
86
+ lines.append(
87
+ f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
88
+ )
89
+ lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
90
+ lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
91
+
92
+ lines.append("")
93
+
94
+ # Fields
95
+ fields = data.get("fields", [])
96
+ if fields:
97
+ lines.append("## Fields")
98
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
99
+ lines.append("|------|------|-----|-----------|------|-----|")
100
+
101
+ for field in fields:
102
+ name = str(field.get("name", ""))
103
+ field_type = str(field.get("type", ""))
104
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
105
+ modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
106
+ line = field.get("line_range", {}).get("start", 0)
107
+ doc = str(field.get("javadoc", "")) or "-"
108
+ doc = doc.replace("\n", " ").replace("|", "\\|")[:50]
109
+
110
+ lines.append(
111
+ f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
112
+ )
113
+ lines.append("")
114
+
115
+ # Methods - Python (no constructor separation)
116
+ methods = data.get("methods", [])
117
+ if methods:
118
+ lines.append("## Methods")
119
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
120
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
121
+
122
+ for method in methods:
123
+ lines.append(self._format_method_row(method))
124
+ lines.append("")
125
+
126
+ # Trim trailing blank lines
127
+ while lines and lines[-1] == "":
128
+ lines.pop()
129
+
130
+ return "\n".join(lines)
131
+
132
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
133
+ """Compact table format for Python"""
134
+ lines = []
135
+
136
+ # Header
137
+ classes = data.get("classes", [])
138
+ if len(classes) > 1:
139
+ file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
140
+ lines.append(f"# {file_name}")
141
+ else:
142
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
143
+ lines.append(f"# {class_name}")
144
+ lines.append("")
145
+
146
+ # Info
147
+ stats = data.get("statistics") or {}
148
+ lines.append("## Info")
149
+ lines.append("| Property | Value |")
150
+ lines.append("|----------|-------|")
151
+ lines.append(f"| Classes | {len(classes)} |")
152
+ lines.append(f"| Methods | {stats.get('method_count', 0)} |")
153
+ lines.append(f"| Fields | {stats.get('field_count', 0)} |")
154
+ lines.append("")
155
+
156
+ # Methods (compact)
157
+ methods = data.get("methods", [])
158
+ if methods:
159
+ lines.append("## Methods")
160
+ lines.append("| Method | Sig | V | L | Cx | Doc |")
161
+ lines.append("|--------|-----|---|---|----|----|")
162
+
163
+ for method in methods:
164
+ name = str(method.get("name", ""))
165
+ signature = self._create_compact_signature(method)
166
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
167
+ line_range = method.get("line_range", {})
168
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
169
+ complexity = method.get("complexity_score", 0)
170
+ doc = self._clean_csv_text(
171
+ self._extract_doc_summary(str(method.get("javadoc", "")))
172
+ )
173
+
174
+ lines.append(
175
+ f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
176
+ )
177
+ lines.append("")
178
+
179
+ # Trim trailing blank lines
180
+ while lines and lines[-1] == "":
181
+ lines.pop()
182
+
183
+ return "\n".join(lines)
184
+
185
+ def _format_method_row(self, method: dict[str, Any]) -> str:
186
+ """Format a method table row for Python"""
187
+ name = str(method.get("name", ""))
188
+ signature = self._create_full_signature(method)
189
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
190
+ line_range = method.get("line_range", {})
191
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
192
+ cols_str = "5-6" # default placeholder
193
+ complexity = method.get("complexity_score", 0)
194
+ doc = self._clean_csv_text(
195
+ self._extract_doc_summary(str(method.get("javadoc", "")))
196
+ )
197
+
198
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
199
+
200
+ def _create_compact_signature(self, method: dict[str, Any]) -> str:
201
+ """Create compact method signature for Python"""
202
+ params = method.get("parameters", [])
203
+ param_types = []
204
+
205
+ for p in params:
206
+ if isinstance(p, dict):
207
+ param_types.append(self._shorten_type(p.get("type", "Any")))
208
+ else:
209
+ param_types.append("Any")
210
+
211
+ params_str = ",".join(param_types)
212
+ return_type = self._shorten_type(method.get("return_type", "Any"))
213
+
214
+ return f"({params_str}):{return_type}"
215
+
216
+ def _shorten_type(self, type_name: Any) -> str:
217
+ """Shorten type name for Python tables"""
218
+ if type_name is None:
219
+ return "Any"
220
+
221
+ if not isinstance(type_name, str):
222
+ type_name = str(type_name)
223
+
224
+ type_mapping = {
225
+ "str": "s",
226
+ "int": "i",
227
+ "float": "f",
228
+ "bool": "b",
229
+ "None": "N",
230
+ "Any": "A",
231
+ "List": "L",
232
+ "Dict": "D",
233
+ "Optional": "O",
234
+ "Union": "U",
235
+ }
236
+
237
+ # List[str] -> L[s]
238
+ if "List[" in type_name:
239
+ result = (
240
+ type_name.replace("List[", "L[").replace("str", "s").replace("int", "i")
241
+ )
242
+ return str(result)
243
+
244
+ # Dict[str, int] -> D[s,i]
245
+ if "Dict[" in type_name:
246
+ result = (
247
+ type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
248
+ )
249
+ return str(result)
250
+
251
+ # Optional[str] -> O[s]
252
+ if "Optional[" in type_name:
253
+ result = type_name.replace("Optional[", "O[").replace("str", "s")
254
+ return str(result)
255
+
256
+ result = type_mapping.get(
257
+ type_name, type_name[:3] if len(type_name) > 3 else type_name
258
+ )
259
+ return str(result)
@@ -54,7 +54,7 @@ For more information, visit: https://github.com/aimasteracc/tree-sitter-analyzer
54
54
 
55
55
  # Global options
56
56
  parser.add_argument(
57
- "--version", action="version", version="tree-sitter-analyzer 0.0.1"
57
+ "--version", action="version", version="tree-sitter-analyzer 0.9.3"
58
58
  )
59
59
  parser.add_argument(
60
60
  "--verbose", "-v", action="store_true", help="Enable verbose output"
@@ -53,7 +53,7 @@ class CLIAdapter:
53
53
  """
54
54
  try:
55
55
  self._engine = UnifiedAnalysisEngine()
56
- logger.info("CLIAdapter initialized successfully")
56
+ logger.debug("CLIAdapter initialized successfully")
57
57
  except Exception as e:
58
58
  logger.error(f"Failed to initialize CLIAdapter: {e}")
59
59
  raise
@@ -111,7 +111,7 @@ class CLIAdapter:
111
111
 
112
112
  # パフォーマンスログ
113
113
  elapsed_time = time.time() - start_time
114
- logger.info(f"CLI analysis completed: {file_path} in {elapsed_time:.3f}s")
114
+ logger.debug(f"CLI analysis completed: {file_path} in {elapsed_time:.3f}s")
115
115
 
116
116
  return result
117
117
 
@@ -251,7 +251,7 @@ class CLIAdapter:
251
251
  >>> adapter.clear_cache()
252
252
  """
253
253
  self._engine.clear_cache()
254
- logger.info("CLI adapter cache cleared")
254
+ logger.debug("CLI adapter cache cleared")
255
255
 
256
256
  def get_cache_stats(self) -> dict[str, Any]:
257
257
  """