superlocalmemory 3.6.9 → 3.6.11
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.
- package/CHANGELOG.md +112 -10
- package/README.md +67 -9
- package/package.json +1 -1
- package/pyproject.toml +6 -1
- package/skills/slm-optimize/README.md +55 -0
- package/skills/slm-optimize/SKILL.md +139 -0
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/compress_cmd.py +32 -70
- package/src/superlocalmemory/cli/optimize_cmd.py +1 -3
- package/src/superlocalmemory/cli/setup_wizard.py +49 -0
- package/src/superlocalmemory/mcp/agent_context.py +111 -0
- package/src/superlocalmemory/mcp/server.py +4 -0
- package/src/superlocalmemory/mcp/tools_active.py +7 -8
- package/src/superlocalmemory/mcp/tools_core.py +16 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +304 -0
- package/src/superlocalmemory/optimize/cache/boundary_store.py +23 -9
- package/src/superlocalmemory/optimize/cache/exact.py +7 -4
- package/src/superlocalmemory/optimize/cache/key_builder.py +13 -0
- package/src/superlocalmemory/optimize/cache/manager.py +70 -8
- package/src/superlocalmemory/optimize/cache/semantic.py +10 -5
- package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +1 -7
- package/src/superlocalmemory/optimize/compress/router.py +82 -87
- package/src/superlocalmemory/optimize/config/__init__.py +16 -0
- package/src/superlocalmemory/optimize/config/defaults.py +1 -6
- package/src/superlocalmemory/optimize/config/schema.py +2 -19
- package/src/superlocalmemory/optimize/config/store.py +15 -1
- package/src/superlocalmemory/optimize/metrics/counters.py +15 -7
- package/src/superlocalmemory/optimize/proxy/_helpers.py +100 -2
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +12 -0
- package/src/superlocalmemory/optimize/proxy/capture.py +243 -0
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +31 -0
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +12 -0
- package/src/superlocalmemory/optimize/proxy/server.py +29 -0
- package/src/superlocalmemory/optimize/storage/db.py +102 -11
- package/src/superlocalmemory/optimize/storage/schema.py +11 -0
- package/src/superlocalmemory/server/routes/optimize.py +6 -8
- package/src/superlocalmemory/server/unified_daemon.py +26 -5
- package/src/superlocalmemory/ui/index.html +18 -14
- package/src/superlocalmemory/ui/js/auto-settings.js +3 -1
- package/src/superlocalmemory/ui/js/ng-shell.js +3 -0
- package/src/superlocalmemory/ui/js/optimize.js +9 -9
- package/src/superlocalmemory.egg-info/PKG-INFO +69 -10
- package/src/superlocalmemory.egg-info/SOURCES.txt +3 -2
- package/src/superlocalmemory.egg-info/requires.txt +1 -0
- package/src/superlocalmemory/optimize/compress/extractive_code.py +0 -311
- package/src/superlocalmemory/optimize/compress/extractive_json.py +0 -72
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
# compress/extractive_code.py
|
|
2
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
-
# Licensed under AGPL-3.0-or-later
|
|
4
|
-
#
|
|
5
|
-
# AST structural patterns adapted from:
|
|
6
|
-
# headroom/compression/handlers/code_handler.py:66-138 (Apache-2.0)
|
|
7
|
-
# Specifically: _STRUCTURAL_NODE_TYPES per-language dict, CodeLanguage enum
|
|
8
|
-
# headroom/compression/handlers/code_handler.py:141-150 — _SIGNATURE_PATTERNS regex fallback
|
|
9
|
-
# Attribution: See ATTRIBUTION.md.
|
|
10
|
-
|
|
11
|
-
"""CodeCompressor — AST-aware extractive code compressor.
|
|
12
|
-
|
|
13
|
-
Supported languages: Python, JavaScript, Go, Rust, Java, C++.
|
|
14
|
-
Path A (tree-sitter): used if tree-sitter-language-pack installed.
|
|
15
|
-
Path B (regex fallback): used otherwise. Tests pass on both paths.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import logging
|
|
21
|
-
import re
|
|
22
|
-
from dataclasses import dataclass
|
|
23
|
-
from enum import Enum
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger("slm.optimize.compress.code")
|
|
26
|
-
|
|
27
|
-
_BODY_STUB_BY_LANG: dict[str, str] = {
|
|
28
|
-
"python": " # [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
29
|
-
"javascript": " // [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
30
|
-
"go": " // [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
31
|
-
"rust": " // [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
32
|
-
"java": " // [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
33
|
-
"cpp": " // [slm: body compressed — retrieve with ccr_id={ccr_id}]",
|
|
34
|
-
}
|
|
35
|
-
_BODY_STUB_NO_CCR_BY_LANG: dict[str, str] = {
|
|
36
|
-
"python": " # [slm: body compressed]",
|
|
37
|
-
"javascript": " // [slm: body compressed]",
|
|
38
|
-
"go": " // [slm: body compressed]",
|
|
39
|
-
"rust": " // [slm: body compressed]",
|
|
40
|
-
"java": " // [slm: body compressed]",
|
|
41
|
-
"cpp": " // [slm: body compressed]",
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
_MIN_BODY_LINES: int = 4
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class CodeLanguage(Enum):
|
|
48
|
-
PYTHON = "python"
|
|
49
|
-
JAVASCRIPT = "javascript"
|
|
50
|
-
GO = "go"
|
|
51
|
-
RUST = "rust"
|
|
52
|
-
JAVA = "java"
|
|
53
|
-
CPP = "cpp"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass(frozen=True)
|
|
57
|
-
class CodeSpan:
|
|
58
|
-
start_line: int
|
|
59
|
-
end_line: int
|
|
60
|
-
role: str # "import" | "signature" | "body" | "class_header" | "decorator"
|
|
61
|
-
is_structural: bool
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# ── Signature patterns for regex path (Path B) ────────────────────────────────
|
|
65
|
-
|
|
66
|
-
_SIGNATURE_PATTERNS: dict[str, list[str]] = {
|
|
67
|
-
"python": [
|
|
68
|
-
r"^\s*(async\s+)?def\s+\w+\s*\([^)]*\)\s*(->\s*[^:]+)?:",
|
|
69
|
-
r"^\s*class\s+\w+(\([^)]*\))?:",
|
|
70
|
-
r"^\s*import\s+",
|
|
71
|
-
r"^\s*from\s+\w+\s+import",
|
|
72
|
-
r"^\s*@\w+",
|
|
73
|
-
],
|
|
74
|
-
"javascript": [
|
|
75
|
-
r"^\s*(async\s+)?function\s+\w+\s*\([^)]*\)",
|
|
76
|
-
r"^\s*class\s+\w+(\s+extends\s+\w+)?",
|
|
77
|
-
r"^\s*import\s+",
|
|
78
|
-
r"^\s*const\s+\w+\s*=\s*(async\s+)?\(",
|
|
79
|
-
r"^\s*export\s+(default\s+|const\s+|class\s+|function\s+)",
|
|
80
|
-
],
|
|
81
|
-
"go": [
|
|
82
|
-
r"^\s*func\s+(\(\w+\s+\*?\w+\)\s*)?\w+\s*\(",
|
|
83
|
-
r"^\s*import\s+",
|
|
84
|
-
r"^\s*package\s+",
|
|
85
|
-
r"^\s*type\s+\w+\s+(struct|interface)\s*\{",
|
|
86
|
-
],
|
|
87
|
-
"rust": [
|
|
88
|
-
r"^\s*(pub\s+)?(async\s+)?fn\s+\w+",
|
|
89
|
-
r"^\s*use\s+",
|
|
90
|
-
r"^\s*impl\s+",
|
|
91
|
-
r"^\s*struct\s+",
|
|
92
|
-
r"^\s*enum\s+",
|
|
93
|
-
r"^\s*trait\s+",
|
|
94
|
-
],
|
|
95
|
-
"java": [
|
|
96
|
-
r"^\s*(public|private|protected)\s+(static\s+)?\w+\s+\w+\s*\(",
|
|
97
|
-
r"^\s*import\s+",
|
|
98
|
-
r"^\s*(public|private|protected)?\s*class\s+\w+",
|
|
99
|
-
],
|
|
100
|
-
"cpp": [
|
|
101
|
-
r"^\s*\w[\w\s\*&:<,>]*\s+\w+\s*\([^)]*\)\s*(const\s*)?\{",
|
|
102
|
-
r"^\s*#include\s+",
|
|
103
|
-
r"^\s*(class|struct)\s+\w+",
|
|
104
|
-
],
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class CodeCompressor:
|
|
109
|
-
"""AST-aware extractive code compressor. Thread-safe (no mutable state)."""
|
|
110
|
-
|
|
111
|
-
def compress(self, code: str, language: str, ccr_id: str = "") -> str:
|
|
112
|
-
try:
|
|
113
|
-
lang = CodeLanguage(language)
|
|
114
|
-
except ValueError:
|
|
115
|
-
logger.warning("CodeCompressor: unknown language %r — passthrough", language)
|
|
116
|
-
return code
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
if _tree_sitter_available():
|
|
120
|
-
return self._compress_with_tree_sitter(code, lang, ccr_id)
|
|
121
|
-
else:
|
|
122
|
-
return self._compress_with_regex(code, lang, ccr_id)
|
|
123
|
-
except Exception as exc:
|
|
124
|
-
logger.warning("CodeCompressor.compress failed — passthrough: %s", exc)
|
|
125
|
-
return code
|
|
126
|
-
|
|
127
|
-
def _compress_with_tree_sitter(self, code: str, lang: CodeLanguage, ccr_id: str) -> str:
|
|
128
|
-
from tree_sitter_language_pack import get_parser # type: ignore[import]
|
|
129
|
-
parser = get_parser(lang.value)
|
|
130
|
-
tree = parser.parse(code.encode())
|
|
131
|
-
lines = code.split("\n")
|
|
132
|
-
spans = _collect_spans_tree_sitter(tree.root_node, lang)
|
|
133
|
-
return _apply_spans(lines, spans, ccr_id, lang.value)
|
|
134
|
-
|
|
135
|
-
def _compress_with_regex(self, code: str, lang: CodeLanguage, ccr_id: str) -> str:
|
|
136
|
-
lines = code.split("\n")
|
|
137
|
-
spans = _collect_spans_regex(lines, lang)
|
|
138
|
-
return _apply_spans(lines, spans, ccr_id, lang.value)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# ── Span collection ───────────────────────────────────────────────────────────
|
|
142
|
-
|
|
143
|
-
def _collect_spans_tree_sitter(root_node: object, lang: CodeLanguage) -> list[CodeSpan]:
|
|
144
|
-
_STRUCTURAL_TYPES: dict[str, set[str]] = {
|
|
145
|
-
"python": {"import_statement", "import_from_statement", "function_definition",
|
|
146
|
-
"class_definition", "decorated_definition", "type_alias_statement"},
|
|
147
|
-
"javascript": {"import_statement", "export_statement", "function_declaration",
|
|
148
|
-
"class_declaration", "method_definition", "arrow_function"},
|
|
149
|
-
"go": {"import_declaration", "function_declaration", "method_declaration",
|
|
150
|
-
"type_declaration", "interface_type"},
|
|
151
|
-
"rust": {"use_declaration", "function_item", "impl_item", "struct_item",
|
|
152
|
-
"enum_item", "trait_item"},
|
|
153
|
-
"java": {"import_declaration", "class_declaration", "method_declaration",
|
|
154
|
-
"interface_declaration", "annotation"},
|
|
155
|
-
"cpp": {"function_definition", "preproc_include", "class_specifier"},
|
|
156
|
-
}
|
|
157
|
-
structural = _STRUCTURAL_TYPES.get(lang.value, set())
|
|
158
|
-
spans: list[CodeSpan] = []
|
|
159
|
-
|
|
160
|
-
def _walk(node: object, depth: int = 0) -> None:
|
|
161
|
-
start_row: int = getattr(node, "start_point", (0, 0))[0]
|
|
162
|
-
end_row: int = getattr(node, "end_point", (0, 0))[0]
|
|
163
|
-
node_type: str = getattr(node, "type", "")
|
|
164
|
-
is_struct = node_type in structural
|
|
165
|
-
|
|
166
|
-
if is_struct and node_type in {
|
|
167
|
-
"function_definition", "method_definition", "function_declaration",
|
|
168
|
-
"function_item", "method_declaration",
|
|
169
|
-
}:
|
|
170
|
-
body_node = _find_child(node, "block") or _find_child(node, "body")
|
|
171
|
-
if body_node is not None:
|
|
172
|
-
body_start = getattr(body_node, "start_point", (0, 0))[0]
|
|
173
|
-
sig_end = body_start - 1
|
|
174
|
-
if sig_end > start_row:
|
|
175
|
-
spans.append(CodeSpan(start_row, sig_end, "signature", True))
|
|
176
|
-
spans.append(CodeSpan(body_start, end_row, "body", False))
|
|
177
|
-
else:
|
|
178
|
-
spans.append(CodeSpan(start_row, end_row, "signature", True))
|
|
179
|
-
elif is_struct and node_type in {"decorated_definition"}:
|
|
180
|
-
def_node = _find_child(node, "function_definition") or _find_child(node, "class_definition")
|
|
181
|
-
if def_node is not None:
|
|
182
|
-
def_start = getattr(def_node, "start_point", (0, 0))[0]
|
|
183
|
-
spans.append(CodeSpan(start_row, def_start - 1, "decorator", True))
|
|
184
|
-
_walk(def_node, depth + 1)
|
|
185
|
-
else:
|
|
186
|
-
spans.append(CodeSpan(start_row, end_row, node_type, True))
|
|
187
|
-
elif is_struct:
|
|
188
|
-
spans.append(CodeSpan(start_row, end_row, "signature", True))
|
|
189
|
-
else:
|
|
190
|
-
children = getattr(node, "children", None)
|
|
191
|
-
if children is not None:
|
|
192
|
-
has_named = False
|
|
193
|
-
for child in children:
|
|
194
|
-
if getattr(child, "is_named", False):
|
|
195
|
-
has_named = True
|
|
196
|
-
_walk(child, depth + 1)
|
|
197
|
-
if not has_named:
|
|
198
|
-
spans.append(CodeSpan(start_row, end_row, "body", False))
|
|
199
|
-
|
|
200
|
-
_walk(root_node)
|
|
201
|
-
return spans
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _find_child(node: object, child_type: str) -> object | None:
|
|
205
|
-
children = getattr(node, "children", None)
|
|
206
|
-
if children is None:
|
|
207
|
-
return None
|
|
208
|
-
for child in children:
|
|
209
|
-
if hasattr(child, "type") and child.type == child_type:
|
|
210
|
-
return child
|
|
211
|
-
return None
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def _collect_spans_regex(lines: list[str], lang: CodeLanguage) -> list[CodeSpan]:
|
|
215
|
-
patterns = _SIGNATURE_PATTERNS.get(lang.value, [])
|
|
216
|
-
spans: list[CodeSpan] = []
|
|
217
|
-
i = 0
|
|
218
|
-
n = len(lines)
|
|
219
|
-
|
|
220
|
-
while i < n:
|
|
221
|
-
line = lines[i]
|
|
222
|
-
matched = False
|
|
223
|
-
for pat in patterns:
|
|
224
|
-
if re.search(pat, line):
|
|
225
|
-
body_start = i + 1
|
|
226
|
-
body_end = body_start
|
|
227
|
-
while body_end < n and (
|
|
228
|
-
lines[body_end].startswith((" ", "\t", "", "#", "//", "/*", "*/", "{"))
|
|
229
|
-
or re.match(r"^\s*$", lines[body_end])
|
|
230
|
-
):
|
|
231
|
-
if re.match(r"^\s*($|#|//|/\*|\*/|\*|}|\)|\])", lines[body_end]):
|
|
232
|
-
body_end += 1
|
|
233
|
-
else:
|
|
234
|
-
break
|
|
235
|
-
body_len = body_end - body_start
|
|
236
|
-
if body_len >= _MIN_BODY_LINES and _looks_like_body(lines, body_start, body_end):
|
|
237
|
-
spans.append(CodeSpan(i, i, "signature", True))
|
|
238
|
-
spans.append(CodeSpan(body_start, body_end, "body", False))
|
|
239
|
-
i = body_end
|
|
240
|
-
matched = True
|
|
241
|
-
break
|
|
242
|
-
else:
|
|
243
|
-
body_start = body_end
|
|
244
|
-
# else: pattern didn't match
|
|
245
|
-
if not matched:
|
|
246
|
-
i += 1
|
|
247
|
-
return spans
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _looks_like_body(lines: list[str], start: int, end: int) -> bool:
|
|
251
|
-
body_lines = [l for l in lines[start:end] if l.strip() and not l.strip().startswith(("#", "//"))]
|
|
252
|
-
return len(body_lines) >= _MIN_BODY_LINES
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _apply_spans(
|
|
256
|
-
lines: list[str],
|
|
257
|
-
spans: list[CodeSpan],
|
|
258
|
-
ccr_id: str,
|
|
259
|
-
lang: str = "python",
|
|
260
|
-
) -> str:
|
|
261
|
-
if not spans:
|
|
262
|
-
return "\n".join(lines)
|
|
263
|
-
|
|
264
|
-
stub_tmpl = _BODY_STUB_BY_LANG.get(lang, _BODY_STUB_BY_LANG["python"])
|
|
265
|
-
stub_no_ccr = _BODY_STUB_NO_CCR_BY_LANG.get(lang, _BODY_STUB_NO_CCR_BY_LANG["python"])
|
|
266
|
-
stub = stub_tmpl.format(ccr_id=ccr_id) if ccr_id else stub_no_ccr
|
|
267
|
-
|
|
268
|
-
sorted_spans = sorted(spans, key=lambda s: s.start_line)
|
|
269
|
-
start_set = set()
|
|
270
|
-
deduped: list[CodeSpan] = []
|
|
271
|
-
for s in sorted_spans:
|
|
272
|
-
if s.start_line not in start_set:
|
|
273
|
-
deduped.append(s)
|
|
274
|
-
start_set.add(s.start_line)
|
|
275
|
-
|
|
276
|
-
span_by_start: dict[int, CodeSpan] = {s.start_line: s for s in deduped}
|
|
277
|
-
|
|
278
|
-
result_lines: list[str] = []
|
|
279
|
-
i = 0
|
|
280
|
-
n = len(lines)
|
|
281
|
-
|
|
282
|
-
while i < n:
|
|
283
|
-
span = span_by_start.get(i)
|
|
284
|
-
if span is not None and not span.is_structural:
|
|
285
|
-
end = min(span.end_line, n - 1)
|
|
286
|
-
body_len = end - i + 1
|
|
287
|
-
if body_len >= _MIN_BODY_LINES:
|
|
288
|
-
result_lines.append(stub)
|
|
289
|
-
i = end + 1
|
|
290
|
-
else:
|
|
291
|
-
for j in range(i, end + 1):
|
|
292
|
-
result_lines.append(lines[j])
|
|
293
|
-
i = end + 1
|
|
294
|
-
elif span is not None and span.is_structural:
|
|
295
|
-
end = min(span.end_line, n - 1)
|
|
296
|
-
for j in range(i, end + 1):
|
|
297
|
-
result_lines.append(lines[j])
|
|
298
|
-
i = end + 1
|
|
299
|
-
else:
|
|
300
|
-
result_lines.append(lines[i])
|
|
301
|
-
i += 1
|
|
302
|
-
|
|
303
|
-
return "\n".join(result_lines)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def _tree_sitter_available() -> bool:
|
|
307
|
-
try:
|
|
308
|
-
import tree_sitter_language_pack # noqa: F401
|
|
309
|
-
return True
|
|
310
|
-
except ImportError:
|
|
311
|
-
return False
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# compress/extractive_json.py
|
|
2
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
-
# Licensed under AGPL-3.0-or-later
|
|
4
|
-
#
|
|
5
|
-
# Structural masking pattern adapted from:
|
|
6
|
-
# headroom/compression/handlers/json_handler.py (Apache-2.0, Headroom contributors)
|
|
7
|
-
# Specifically: JSONStructureHandler._extract_mask(), JSONToken, JSONTokenType
|
|
8
|
-
# Attribution: See ATTRIBUTION.md.
|
|
9
|
-
#
|
|
10
|
-
# HARD RULE: This compressor MUST NEVER prune or reorder JSON keys.
|
|
11
|
-
# Keys pruned = structured semantics corrupted non-recoverably. Not configurable.
|
|
12
|
-
|
|
13
|
-
"""JSONCompressor — lossless-ish extractive JSON compressor."""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import logging
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger("slm.optimize.compress.json")
|
|
22
|
-
|
|
23
|
-
VALUE_TRUNCATE_CHARS: int = 120
|
|
24
|
-
VALUE_TRUNCATE_SUFFIX: str = "\u2026" # ellipsis
|
|
25
|
-
MAX_ARRAY_ITEMS_SHOWN: int = 5
|
|
26
|
-
ARRAY_REMAINDER_KEY: str = "__slm_omitted__"
|
|
27
|
-
MIN_VALUE_LEN_TO_TRUNCATE: int = 40
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class JSONCompressor:
|
|
31
|
-
"""Lossless-ish JSON compressor. Thread-safe (no mutable state)."""
|
|
32
|
-
|
|
33
|
-
def compress(self, parsed: Any) -> str:
|
|
34
|
-
try:
|
|
35
|
-
masked = self._mask(parsed, depth=0)
|
|
36
|
-
return json.dumps(masked, ensure_ascii=False, separators=(",", ":"))
|
|
37
|
-
except Exception as exc:
|
|
38
|
-
logger.warning("JSONCompressor.compress failed — returning original: %s", exc)
|
|
39
|
-
return json.dumps(parsed, ensure_ascii=False, separators=(",", ":"))
|
|
40
|
-
|
|
41
|
-
def _mask(self, obj: Any, depth: int) -> Any:
|
|
42
|
-
if isinstance(obj, dict):
|
|
43
|
-
return {k: self._mask(v, depth + 1) for k, v in obj.items()}
|
|
44
|
-
if isinstance(obj, list):
|
|
45
|
-
return self._mask_array(obj, depth)
|
|
46
|
-
if isinstance(obj, str):
|
|
47
|
-
return self._mask_string(obj)
|
|
48
|
-
return obj
|
|
49
|
-
|
|
50
|
-
def _mask_array(self, arr: list, depth: int) -> list:
|
|
51
|
-
if len(arr) <= MAX_ARRAY_ITEMS_SHOWN:
|
|
52
|
-
return [self._mask(item, depth + 1) for item in arr]
|
|
53
|
-
shown = [self._mask(item, depth + 1) for item in arr[:MAX_ARRAY_ITEMS_SHOWN]]
|
|
54
|
-
collision = any(
|
|
55
|
-
isinstance(item, dict) and ARRAY_REMAINDER_KEY in item
|
|
56
|
-
for item in arr
|
|
57
|
-
)
|
|
58
|
-
if collision:
|
|
59
|
-
logger.warning(
|
|
60
|
-
"JSONCompressor: input contains reserved key %r — skipping array sentinel",
|
|
61
|
-
ARRAY_REMAINDER_KEY,
|
|
62
|
-
)
|
|
63
|
-
else:
|
|
64
|
-
shown.append({ARRAY_REMAINDER_KEY: len(arr) - MAX_ARRAY_ITEMS_SHOWN})
|
|
65
|
-
return shown
|
|
66
|
-
|
|
67
|
-
def _mask_string(self, s: str) -> str:
|
|
68
|
-
if len(s) < MIN_VALUE_LEN_TO_TRUNCATE:
|
|
69
|
-
return s
|
|
70
|
-
if len(s) <= VALUE_TRUNCATE_CHARS:
|
|
71
|
-
return s
|
|
72
|
-
return s[:VALUE_TRUNCATE_CHARS] + VALUE_TRUNCATE_SUFFIX
|