additory 0.1.0a4__py3-none-any.whl → 0.1.1a1__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.
- additory/__init__.py +58 -14
- additory/common/__init__.py +31 -147
- additory/common/column_selector.py +255 -0
- additory/common/distributions.py +286 -613
- additory/common/extractors.py +313 -0
- additory/common/knn_imputation.py +332 -0
- additory/common/result.py +380 -0
- additory/common/strategy_parser.py +243 -0
- additory/common/unit_conversions.py +338 -0
- additory/common/validation.py +283 -103
- additory/core/__init__.py +34 -22
- additory/core/backend.py +258 -0
- additory/core/config.py +177 -305
- additory/core/logging.py +230 -24
- additory/core/memory_manager.py +157 -495
- additory/expressions/__init__.py +2 -23
- additory/expressions/compiler.py +457 -0
- additory/expressions/engine.py +264 -487
- additory/expressions/integrity.py +179 -0
- additory/expressions/loader.py +263 -0
- additory/expressions/parser.py +363 -167
- additory/expressions/resolver.py +274 -0
- additory/functions/__init__.py +1 -0
- additory/functions/analyze/__init__.py +144 -0
- additory/functions/analyze/cardinality.py +58 -0
- additory/functions/analyze/correlations.py +66 -0
- additory/functions/analyze/distributions.py +53 -0
- additory/functions/analyze/duplicates.py +49 -0
- additory/functions/analyze/features.py +61 -0
- additory/functions/analyze/imputation.py +66 -0
- additory/functions/analyze/outliers.py +65 -0
- additory/functions/analyze/patterns.py +65 -0
- additory/functions/analyze/presets.py +72 -0
- additory/functions/analyze/quality.py +59 -0
- additory/functions/analyze/timeseries.py +53 -0
- additory/functions/analyze/types.py +45 -0
- additory/functions/expressions/__init__.py +161 -0
- additory/functions/snapshot/__init__.py +82 -0
- additory/functions/snapshot/filter.py +119 -0
- additory/functions/synthetic/__init__.py +113 -0
- additory/functions/synthetic/mode_detector.py +47 -0
- additory/functions/synthetic/strategies/__init__.py +1 -0
- additory/functions/synthetic/strategies/advanced.py +35 -0
- additory/functions/synthetic/strategies/augmentative.py +160 -0
- additory/functions/synthetic/strategies/generative.py +168 -0
- additory/functions/synthetic/strategies/presets.py +116 -0
- additory/functions/to/__init__.py +188 -0
- additory/functions/to/lookup.py +351 -0
- additory/functions/to/merge.py +189 -0
- additory/functions/to/sort.py +91 -0
- additory/functions/to/summarize.py +170 -0
- additory/functions/transform/__init__.py +140 -0
- additory/functions/transform/datetime.py +79 -0
- additory/functions/transform/extract.py +85 -0
- additory/functions/transform/harmonize.py +105 -0
- additory/functions/transform/knn.py +62 -0
- additory/functions/transform/onehotencoding.py +68 -0
- additory/functions/transform/transpose.py +42 -0
- additory-0.1.1a1.dist-info/METADATA +83 -0
- additory-0.1.1a1.dist-info/RECORD +62 -0
- additory/analysis/__init__.py +0 -48
- additory/analysis/cardinality.py +0 -126
- additory/analysis/correlations.py +0 -124
- additory/analysis/distributions.py +0 -376
- additory/analysis/quality.py +0 -158
- additory/analysis/scan.py +0 -400
- additory/common/backend.py +0 -371
- additory/common/column_utils.py +0 -191
- additory/common/exceptions.py +0 -62
- additory/common/lists.py +0 -229
- additory/common/patterns.py +0 -240
- additory/common/resolver.py +0 -567
- additory/common/sample_data.py +0 -182
- additory/core/ast_builder.py +0 -165
- additory/core/backends/__init__.py +0 -23
- additory/core/backends/arrow_bridge.py +0 -483
- additory/core/backends/cudf_bridge.py +0 -355
- additory/core/column_positioning.py +0 -358
- additory/core/compiler_polars.py +0 -166
- additory/core/enhanced_cache_manager.py +0 -1119
- additory/core/enhanced_matchers.py +0 -473
- additory/core/enhanced_version_manager.py +0 -325
- additory/core/executor.py +0 -59
- additory/core/integrity_manager.py +0 -477
- additory/core/loader.py +0 -190
- additory/core/namespace_manager.py +0 -657
- additory/core/parser.py +0 -176
- additory/core/polars_expression_engine.py +0 -601
- additory/core/registry.py +0 -177
- additory/core/sample_data_manager.py +0 -492
- additory/core/user_namespace.py +0 -751
- additory/core/validator.py +0 -27
- additory/dynamic_api.py +0 -352
- additory/expressions/proxy.py +0 -549
- additory/expressions/registry.py +0 -313
- additory/expressions/samples.py +0 -492
- additory/synthetic/__init__.py +0 -13
- additory/synthetic/column_name_resolver.py +0 -149
- additory/synthetic/deduce.py +0 -259
- additory/synthetic/distributions.py +0 -22
- additory/synthetic/forecast.py +0 -1132
- additory/synthetic/linked_list_parser.py +0 -415
- additory/synthetic/namespace_lookup.py +0 -129
- additory/synthetic/smote.py +0 -320
- additory/synthetic/strategies.py +0 -926
- additory/synthetic/synthesizer.py +0 -713
- additory/utilities/__init__.py +0 -53
- additory/utilities/encoding.py +0 -600
- additory/utilities/games.py +0 -300
- additory/utilities/keys.py +0 -8
- additory/utilities/lookup.py +0 -103
- additory/utilities/matchers.py +0 -216
- additory/utilities/resolvers.py +0 -286
- additory/utilities/settings.py +0 -167
- additory/utilities/units.py +0 -749
- additory/utilities/validators.py +0 -153
- additory-0.1.0a4.dist-info/METADATA +0 -311
- additory-0.1.0a4.dist-info/RECORD +0 -72
- additory-0.1.0a4.dist-info/licenses/LICENSE +0 -21
- {additory-0.1.0a4.dist-info → additory-0.1.1a1.dist-info}/WHEEL +0 -0
- {additory-0.1.0a4.dist-info → additory-0.1.1a1.dist-info}/top_level.txt +0 -0
|
@@ -1,657 +0,0 @@
|
|
|
1
|
-
# namespace_manager.py
|
|
2
|
-
# Dual namespace management for additory expressions
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
from typing import Dict, List, Optional, Set
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from .logging import log_info, log_warning
|
|
10
|
-
from .enhanced_version_manager import EnhancedVersionManager, VersionInfo
|
|
11
|
-
from .integrity_manager import IntegrityManager
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass
|
|
15
|
-
class NamespaceInfo:
|
|
16
|
-
"""Information about a namespace"""
|
|
17
|
-
name: str
|
|
18
|
-
path: str
|
|
19
|
-
is_builtin: bool
|
|
20
|
-
is_writable: bool
|
|
21
|
-
expression_count: int
|
|
22
|
-
available_versions: List[str]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class NamespaceError(Exception):
|
|
26
|
-
"""Raised when namespace operations fail"""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class NamespaceManager:
|
|
31
|
-
"""Manages dual namespace system for built-in and user expressions"""
|
|
32
|
-
|
|
33
|
-
def __init__(self):
|
|
34
|
-
# Hardcoded paths for testing (as per requirements)
|
|
35
|
-
self.builtin_path = "reference/expressions_definitions/"
|
|
36
|
-
self.user_path = "user_expressions/"
|
|
37
|
-
|
|
38
|
-
# Track if paths are remote URLs
|
|
39
|
-
self._is_remote_builtin = False
|
|
40
|
-
self._is_remote_user = False
|
|
41
|
-
|
|
42
|
-
# Cache paths for each namespace
|
|
43
|
-
self.cache_paths = {
|
|
44
|
-
"builtin": os.path.expanduser("~/.additory/cache/expressions/core/"),
|
|
45
|
-
"user": os.path.expanduser("~/.additory/cache/expressions/user/")
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# Initialize managers
|
|
49
|
-
self.version_manager = EnhancedVersionManager()
|
|
50
|
-
self.integrity_manager = IntegrityManager()
|
|
51
|
-
|
|
52
|
-
# Namespace state
|
|
53
|
-
self._custom_user_path = None
|
|
54
|
-
self._custom_builtin_path = None
|
|
55
|
-
self._namespace_cache = {}
|
|
56
|
-
|
|
57
|
-
# Valid namespace names
|
|
58
|
-
self.valid_namespaces = {"builtin", "user"}
|
|
59
|
-
|
|
60
|
-
def get_namespace_path(self, namespace: str) -> str:
|
|
61
|
-
"""
|
|
62
|
-
Get the path for a specific namespace
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
namespace: Namespace name ("builtin" or "user")
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
Path to the namespace directory (local or remote URL)
|
|
69
|
-
|
|
70
|
-
Raises:
|
|
71
|
-
NamespaceError: If namespace is invalid
|
|
72
|
-
"""
|
|
73
|
-
if namespace not in self.valid_namespaces:
|
|
74
|
-
raise NamespaceError(f"Invalid namespace: {namespace}. Valid namespaces: {self.valid_namespaces}")
|
|
75
|
-
|
|
76
|
-
if namespace == "builtin":
|
|
77
|
-
return self._custom_builtin_path or self.builtin_path
|
|
78
|
-
elif namespace == "user":
|
|
79
|
-
return self._custom_user_path or self.user_path
|
|
80
|
-
|
|
81
|
-
raise NamespaceError(f"Unknown namespace: {namespace}")
|
|
82
|
-
|
|
83
|
-
def is_remote_url(self, path: str) -> bool:
|
|
84
|
-
"""
|
|
85
|
-
Check if path is a remote HTTP/HTTPS URL
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
path: Path or URL to check
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
True if path is HTTP/HTTPS URL
|
|
92
|
-
"""
|
|
93
|
-
if not path:
|
|
94
|
-
return False
|
|
95
|
-
return path.startswith(('http://', 'https://'))
|
|
96
|
-
|
|
97
|
-
def normalize_path(self, path: str) -> str:
|
|
98
|
-
"""
|
|
99
|
-
Normalize path for cross-platform compatibility
|
|
100
|
-
|
|
101
|
-
Handles:
|
|
102
|
-
- Remote URLs (return as-is)
|
|
103
|
-
- Local paths (normalize for platform)
|
|
104
|
-
- ~ expansion
|
|
105
|
-
- Relative to absolute conversion
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
path: Path to normalize
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
Normalized path or URL
|
|
112
|
-
"""
|
|
113
|
-
if not path:
|
|
114
|
-
return path
|
|
115
|
-
|
|
116
|
-
# If remote URL, return as-is
|
|
117
|
-
if self.is_remote_url(path):
|
|
118
|
-
return path
|
|
119
|
-
|
|
120
|
-
# Local path - normalize
|
|
121
|
-
from pathlib import Path
|
|
122
|
-
|
|
123
|
-
# Expand ~ to home directory
|
|
124
|
-
path = os.path.expanduser(path)
|
|
125
|
-
|
|
126
|
-
# Use pathlib for cross-platform handling
|
|
127
|
-
path_obj = Path(path)
|
|
128
|
-
|
|
129
|
-
# Convert to absolute path
|
|
130
|
-
abs_path = path_obj.resolve()
|
|
131
|
-
|
|
132
|
-
return str(abs_path)
|
|
133
|
-
|
|
134
|
-
def fetch_remote_file(self, url: str, filename: str, namespace: str) -> str:
|
|
135
|
-
"""
|
|
136
|
-
Fetch file from remote URL and cache locally
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
url: Base URL (e.g., https://github.com/.../expressions/)
|
|
140
|
-
filename: File to fetch (e.g., manifest.json or bmi_0.1.add)
|
|
141
|
-
namespace: "builtin" or "user" (for cache location)
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Local path to cached file
|
|
145
|
-
|
|
146
|
-
Raises:
|
|
147
|
-
NamespaceError: If fetch fails
|
|
148
|
-
"""
|
|
149
|
-
try:
|
|
150
|
-
import requests
|
|
151
|
-
except ImportError:
|
|
152
|
-
raise NamespaceError(
|
|
153
|
-
"requests library required for remote expressions. "
|
|
154
|
-
"Install with: pip install requests"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# Ensure URL ends with /
|
|
158
|
-
if not url.endswith('/'):
|
|
159
|
-
url += '/'
|
|
160
|
-
|
|
161
|
-
file_url = url + filename
|
|
162
|
-
|
|
163
|
-
# Get cache directory
|
|
164
|
-
cache_dir = self.get_cache_path(namespace)
|
|
165
|
-
os.makedirs(cache_dir, exist_ok=True)
|
|
166
|
-
|
|
167
|
-
# Cache file path
|
|
168
|
-
cache_file = os.path.join(cache_dir, filename)
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
log_info(f"[namespace] Fetching remote file: {file_url}")
|
|
172
|
-
|
|
173
|
-
response = requests.get(file_url, timeout=10)
|
|
174
|
-
response.raise_for_status()
|
|
175
|
-
|
|
176
|
-
# Write to cache
|
|
177
|
-
with open(cache_file, 'w', encoding='utf-8') as f:
|
|
178
|
-
f.write(response.text)
|
|
179
|
-
|
|
180
|
-
log_info(f"[namespace] Cached {filename} for {namespace} namespace")
|
|
181
|
-
|
|
182
|
-
return cache_file
|
|
183
|
-
|
|
184
|
-
except Exception as e:
|
|
185
|
-
raise NamespaceError(
|
|
186
|
-
f"Failed to fetch {filename} from {file_url}: {e}"
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
def get_namespace_path(self, namespace: str) -> str:
|
|
190
|
-
"""
|
|
191
|
-
Get the path for a specific namespace
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
namespace: Namespace name ("builtin" or "user")
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
Path to the namespace directory
|
|
198
|
-
|
|
199
|
-
Raises:
|
|
200
|
-
NamespaceError: If namespace is invalid
|
|
201
|
-
"""
|
|
202
|
-
if namespace not in self.valid_namespaces:
|
|
203
|
-
raise NamespaceError(f"Invalid namespace: {namespace}. Valid namespaces: {self.valid_namespaces}")
|
|
204
|
-
|
|
205
|
-
if namespace == "builtin":
|
|
206
|
-
return self.builtin_path
|
|
207
|
-
elif namespace == "user":
|
|
208
|
-
return self._custom_user_path or self.user_path
|
|
209
|
-
|
|
210
|
-
raise NamespaceError(f"Unknown namespace: {namespace}")
|
|
211
|
-
|
|
212
|
-
def get_cache_path(self, namespace: str) -> str:
|
|
213
|
-
"""
|
|
214
|
-
Get the cache path for a specific namespace
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
namespace: Namespace name
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
Path to the namespace cache directory
|
|
221
|
-
"""
|
|
222
|
-
if namespace not in self.cache_paths:
|
|
223
|
-
raise NamespaceError(f"No cache path defined for namespace: {namespace}")
|
|
224
|
-
|
|
225
|
-
return self.cache_paths[namespace]
|
|
226
|
-
|
|
227
|
-
def set_builtin_path(self, path: str) -> bool:
|
|
228
|
-
"""
|
|
229
|
-
Set built-in expressions path (local or remote)
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
path: Local path or remote URL
|
|
233
|
-
|
|
234
|
-
Returns:
|
|
235
|
-
True if successful
|
|
236
|
-
|
|
237
|
-
Raises:
|
|
238
|
-
NamespaceError: If path is invalid
|
|
239
|
-
"""
|
|
240
|
-
if not path:
|
|
241
|
-
raise NamespaceError("Built-in path cannot be empty")
|
|
242
|
-
|
|
243
|
-
normalized = self.normalize_path(path)
|
|
244
|
-
|
|
245
|
-
# Validate
|
|
246
|
-
if self.is_remote_url(normalized):
|
|
247
|
-
# Remote URL - validate by fetching manifest
|
|
248
|
-
try:
|
|
249
|
-
self.fetch_remote_file(normalized, "manifest.json", "builtin")
|
|
250
|
-
self._is_remote_builtin = True
|
|
251
|
-
log_info(f"[namespace] Built-in expressions set to remote: {normalized}")
|
|
252
|
-
except Exception as e:
|
|
253
|
-
raise NamespaceError(f"Invalid remote URL for built-in: {e}")
|
|
254
|
-
else:
|
|
255
|
-
# Local path - validate exists
|
|
256
|
-
if not os.path.exists(normalized):
|
|
257
|
-
raise NamespaceError(f"Built-in path does not exist: {normalized}")
|
|
258
|
-
if not os.path.isdir(normalized):
|
|
259
|
-
raise NamespaceError(f"Built-in path is not a directory: {normalized}")
|
|
260
|
-
self._is_remote_builtin = False
|
|
261
|
-
log_info(f"[namespace] Built-in expressions set to local: {normalized}")
|
|
262
|
-
|
|
263
|
-
self._custom_builtin_path = normalized
|
|
264
|
-
self._clear_namespace_cache("builtin")
|
|
265
|
-
return True
|
|
266
|
-
|
|
267
|
-
def set_user_path(self, path: str) -> bool:
|
|
268
|
-
"""
|
|
269
|
-
Set custom user expressions path (local or remote)
|
|
270
|
-
|
|
271
|
-
Args:
|
|
272
|
-
path: Path to user expressions directory or HTTP URL
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
True if path was set successfully
|
|
276
|
-
|
|
277
|
-
Raises:
|
|
278
|
-
NamespaceError: If path is invalid
|
|
279
|
-
"""
|
|
280
|
-
if not path:
|
|
281
|
-
raise NamespaceError("User path cannot be empty")
|
|
282
|
-
|
|
283
|
-
normalized = self.normalize_path(path)
|
|
284
|
-
|
|
285
|
-
# Validate
|
|
286
|
-
if self.is_remote_url(normalized):
|
|
287
|
-
# Remote URL - validate by fetching manifest
|
|
288
|
-
try:
|
|
289
|
-
self.fetch_remote_file(normalized, "manifest.json", "user")
|
|
290
|
-
self._is_remote_user = True
|
|
291
|
-
log_info(f"[namespace] User expressions set to remote: {normalized}")
|
|
292
|
-
except Exception as e:
|
|
293
|
-
raise NamespaceError(f"Invalid remote URL for user: {e}")
|
|
294
|
-
else:
|
|
295
|
-
# Local path - normalize for cross-platform
|
|
296
|
-
# Validate path exists
|
|
297
|
-
if not os.path.exists(normalized):
|
|
298
|
-
raise NamespaceError(f"Path does not exist: {normalized}")
|
|
299
|
-
|
|
300
|
-
# Validate path is a directory
|
|
301
|
-
if not os.path.isdir(normalized):
|
|
302
|
-
raise NamespaceError(f"Path is not a directory: {normalized}")
|
|
303
|
-
|
|
304
|
-
# Check if path is writable (warning only)
|
|
305
|
-
if not os.access(normalized, os.W_OK):
|
|
306
|
-
log_warning(f"[namespace] User path is not writable: {normalized}")
|
|
307
|
-
|
|
308
|
-
self._is_remote_user = False
|
|
309
|
-
log_info(f"[namespace] User expressions set to local: {normalized}")
|
|
310
|
-
|
|
311
|
-
# Set the custom path
|
|
312
|
-
self._custom_user_path = normalized
|
|
313
|
-
|
|
314
|
-
# Clear namespace cache to force reload
|
|
315
|
-
self._clear_namespace_cache("user")
|
|
316
|
-
|
|
317
|
-
return True
|
|
318
|
-
|
|
319
|
-
def get_user_path(self) -> str:
|
|
320
|
-
"""
|
|
321
|
-
Get current user expressions path
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
Current user expressions path
|
|
325
|
-
"""
|
|
326
|
-
if self._custom_user_path:
|
|
327
|
-
return self._custom_user_path
|
|
328
|
-
return self.user_path
|
|
329
|
-
|
|
330
|
-
def validate_namespace(self, namespace: str) -> bool:
|
|
331
|
-
"""
|
|
332
|
-
Validate that a namespace is properly configured
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
namespace: Namespace name to validate
|
|
336
|
-
|
|
337
|
-
Returns:
|
|
338
|
-
True if namespace is valid and accessible
|
|
339
|
-
"""
|
|
340
|
-
try:
|
|
341
|
-
# Check if namespace name is valid
|
|
342
|
-
if namespace not in self.valid_namespaces:
|
|
343
|
-
log_warning(f"[namespace] Invalid namespace name: {namespace}")
|
|
344
|
-
return False
|
|
345
|
-
|
|
346
|
-
# Get namespace path
|
|
347
|
-
path = self.get_namespace_path(namespace)
|
|
348
|
-
|
|
349
|
-
# Check if path exists
|
|
350
|
-
if not os.path.exists(path):
|
|
351
|
-
log_warning(f"[namespace] Namespace path does not exist: {path}")
|
|
352
|
-
return False
|
|
353
|
-
|
|
354
|
-
# Check if path is a directory
|
|
355
|
-
if not os.path.isdir(path):
|
|
356
|
-
log_warning(f"[namespace] Namespace path is not a directory: {path}")
|
|
357
|
-
return False
|
|
358
|
-
|
|
359
|
-
# For built-in namespace, check if it's protected (read-only is OK)
|
|
360
|
-
if namespace == "builtin":
|
|
361
|
-
# Built-in should exist and be readable
|
|
362
|
-
if not os.access(path, os.R_OK):
|
|
363
|
-
log_warning(f"[namespace] Built-in namespace is not readable: {path}")
|
|
364
|
-
return False
|
|
365
|
-
|
|
366
|
-
# For user namespace, check if it's writable
|
|
367
|
-
elif namespace == "user":
|
|
368
|
-
if not os.access(path, os.W_OK):
|
|
369
|
-
log_warning(f"[namespace] User namespace is not writable: {path}")
|
|
370
|
-
# This is a warning, not a failure - user might have read-only expressions
|
|
371
|
-
|
|
372
|
-
# Check for manifest file
|
|
373
|
-
manifest_path = os.path.join(path, "manifest.json")
|
|
374
|
-
if not os.path.exists(manifest_path):
|
|
375
|
-
log_warning(f"[namespace] No manifest found in namespace: {manifest_path}")
|
|
376
|
-
return False
|
|
377
|
-
|
|
378
|
-
# Try to load manifest
|
|
379
|
-
try:
|
|
380
|
-
manifest = self.version_manager.load_manifest(path)
|
|
381
|
-
if not manifest:
|
|
382
|
-
log_warning(f"[namespace] Empty or invalid manifest in namespace: {namespace}")
|
|
383
|
-
return False
|
|
384
|
-
except Exception as e:
|
|
385
|
-
log_warning(f"[namespace] Failed to load manifest for namespace {namespace}: {e}")
|
|
386
|
-
return False
|
|
387
|
-
|
|
388
|
-
log_info(f"[namespace] Namespace validation passed: {namespace}")
|
|
389
|
-
return True
|
|
390
|
-
|
|
391
|
-
except Exception as e:
|
|
392
|
-
log_warning(f"[namespace] Namespace validation failed for {namespace}: {e}")
|
|
393
|
-
return False
|
|
394
|
-
|
|
395
|
-
def get_namespace_info(self, namespace: str) -> NamespaceInfo:
|
|
396
|
-
"""
|
|
397
|
-
Get detailed information about a namespace
|
|
398
|
-
|
|
399
|
-
Args:
|
|
400
|
-
namespace: Namespace name
|
|
401
|
-
|
|
402
|
-
Returns:
|
|
403
|
-
NamespaceInfo object with namespace details
|
|
404
|
-
"""
|
|
405
|
-
path = self.get_namespace_path(namespace)
|
|
406
|
-
is_builtin = (namespace == "builtin")
|
|
407
|
-
|
|
408
|
-
# Check if path is writable
|
|
409
|
-
is_writable = os.access(path, os.W_OK) if os.path.exists(path) else False
|
|
410
|
-
|
|
411
|
-
# Get expression count and versions
|
|
412
|
-
expression_count = 0
|
|
413
|
-
available_versions = []
|
|
414
|
-
|
|
415
|
-
try:
|
|
416
|
-
if os.path.exists(path):
|
|
417
|
-
manifest = self.version_manager.load_manifest(path)
|
|
418
|
-
available_versions = list(manifest.keys())
|
|
419
|
-
|
|
420
|
-
# Count total expressions across all versions
|
|
421
|
-
for version_info in manifest.values():
|
|
422
|
-
expression_count += len(version_info.expressions)
|
|
423
|
-
except Exception as e:
|
|
424
|
-
log_warning(f"[namespace] Failed to get info for namespace {namespace}: {e}")
|
|
425
|
-
|
|
426
|
-
return NamespaceInfo(
|
|
427
|
-
name=namespace,
|
|
428
|
-
path=path,
|
|
429
|
-
is_builtin=is_builtin,
|
|
430
|
-
is_writable=is_writable,
|
|
431
|
-
expression_count=expression_count,
|
|
432
|
-
available_versions=available_versions
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
def list_expressions(self, namespace: str, version: str = None) -> Dict[str, str]:
|
|
436
|
-
"""
|
|
437
|
-
List all expressions in a namespace
|
|
438
|
-
|
|
439
|
-
Args:
|
|
440
|
-
namespace: Namespace name
|
|
441
|
-
version: Specific version to list (optional)
|
|
442
|
-
|
|
443
|
-
Returns:
|
|
444
|
-
Dictionary mapping expression names to filenames
|
|
445
|
-
"""
|
|
446
|
-
try:
|
|
447
|
-
path = self.get_namespace_path(namespace)
|
|
448
|
-
manifest = self.version_manager.load_manifest(path)
|
|
449
|
-
|
|
450
|
-
if version:
|
|
451
|
-
if version not in manifest:
|
|
452
|
-
raise NamespaceError(f"Version {version} not found in namespace {namespace}")
|
|
453
|
-
return manifest[version].expressions.copy()
|
|
454
|
-
else:
|
|
455
|
-
# Return expressions from latest version
|
|
456
|
-
latest_version = self.version_manager.get_latest_version(manifest)
|
|
457
|
-
if latest_version:
|
|
458
|
-
return manifest[latest_version].expressions.copy()
|
|
459
|
-
else:
|
|
460
|
-
return {}
|
|
461
|
-
|
|
462
|
-
except Exception as e:
|
|
463
|
-
log_warning(f"[namespace] Failed to list expressions for {namespace}: {e}")
|
|
464
|
-
return {}
|
|
465
|
-
|
|
466
|
-
def check_expression_exists(self, namespace: str, expression_name: str, version: str = None) -> bool:
|
|
467
|
-
"""
|
|
468
|
-
Check if an expression exists in a namespace
|
|
469
|
-
|
|
470
|
-
Args:
|
|
471
|
-
namespace: Namespace name
|
|
472
|
-
expression_name: Name of the expression
|
|
473
|
-
version: Specific version to check (optional)
|
|
474
|
-
|
|
475
|
-
Returns:
|
|
476
|
-
True if expression exists
|
|
477
|
-
"""
|
|
478
|
-
expressions = self.list_expressions(namespace, version)
|
|
479
|
-
return expression_name in expressions
|
|
480
|
-
|
|
481
|
-
def detect_namespace_conflicts(self) -> Dict[str, List[str]]:
|
|
482
|
-
"""
|
|
483
|
-
Detect conflicts between namespaces (same expression names)
|
|
484
|
-
|
|
485
|
-
Returns:
|
|
486
|
-
Dictionary mapping expression names to list of namespaces that have them
|
|
487
|
-
"""
|
|
488
|
-
conflicts = {}
|
|
489
|
-
|
|
490
|
-
for namespace in self.valid_namespaces:
|
|
491
|
-
try:
|
|
492
|
-
if not self.validate_namespace(namespace):
|
|
493
|
-
continue
|
|
494
|
-
|
|
495
|
-
expressions = self.list_expressions(namespace)
|
|
496
|
-
|
|
497
|
-
for expr_name in expressions:
|
|
498
|
-
if expr_name not in conflicts:
|
|
499
|
-
conflicts[expr_name] = []
|
|
500
|
-
conflicts[expr_name].append(namespace)
|
|
501
|
-
|
|
502
|
-
except Exception as e:
|
|
503
|
-
log_warning(f"[namespace] Failed to check conflicts for {namespace}: {e}")
|
|
504
|
-
|
|
505
|
-
# Filter to only actual conflicts (expressions in multiple namespaces)
|
|
506
|
-
actual_conflicts = {
|
|
507
|
-
expr_name: namespaces
|
|
508
|
-
for expr_name, namespaces in conflicts.items()
|
|
509
|
-
if len(namespaces) > 1
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
return actual_conflicts
|
|
513
|
-
|
|
514
|
-
def prevent_builtin_modification(self, namespace: str) -> bool:
|
|
515
|
-
"""
|
|
516
|
-
Prevent modification of built-in namespace
|
|
517
|
-
|
|
518
|
-
Args:
|
|
519
|
-
namespace: Namespace to check
|
|
520
|
-
|
|
521
|
-
Returns:
|
|
522
|
-
True if modification is allowed, False if prevented
|
|
523
|
-
|
|
524
|
-
Raises:
|
|
525
|
-
NamespaceError: If attempting to modify built-in namespace
|
|
526
|
-
"""
|
|
527
|
-
if namespace == "builtin":
|
|
528
|
-
raise NamespaceError("Cannot modify built-in expressions namespace. Built-in expressions are immutable.")
|
|
529
|
-
|
|
530
|
-
return True
|
|
531
|
-
|
|
532
|
-
def ensure_cache_directories(self):
|
|
533
|
-
"""Ensure cache directories exist for all namespaces"""
|
|
534
|
-
for namespace, cache_path in self.cache_paths.items():
|
|
535
|
-
try:
|
|
536
|
-
os.makedirs(cache_path, exist_ok=True)
|
|
537
|
-
log_info(f"[namespace] Ensured cache directory exists: {cache_path}")
|
|
538
|
-
except Exception as e:
|
|
539
|
-
log_warning(f"[namespace] Failed to create cache directory {cache_path}: {e}")
|
|
540
|
-
|
|
541
|
-
def get_expression_file_path(self, namespace: str, expression_name: str, version: str = None) -> Optional[str]:
|
|
542
|
-
"""
|
|
543
|
-
Get the full path to an expression file (handles remote URLs)
|
|
544
|
-
|
|
545
|
-
Args:
|
|
546
|
-
namespace: Namespace name
|
|
547
|
-
expression_name: Name of the expression
|
|
548
|
-
version: Specific version (optional)
|
|
549
|
-
|
|
550
|
-
Returns:
|
|
551
|
-
Full path to expression file (local or cached from remote) or None if not found
|
|
552
|
-
"""
|
|
553
|
-
try:
|
|
554
|
-
path = self.get_namespace_path(namespace)
|
|
555
|
-
|
|
556
|
-
# Load manifest (handles remote automatically)
|
|
557
|
-
if self.is_remote_url(path):
|
|
558
|
-
# Fetch and cache manifest
|
|
559
|
-
manifest_file = self.fetch_remote_file(path, "manifest.json", namespace)
|
|
560
|
-
with open(manifest_file, 'r', encoding='utf-8') as f:
|
|
561
|
-
import json
|
|
562
|
-
manifest = json.load(f)
|
|
563
|
-
else:
|
|
564
|
-
# Local manifest
|
|
565
|
-
manifest = self.version_manager.load_manifest(path)
|
|
566
|
-
|
|
567
|
-
# Use specified version or latest
|
|
568
|
-
target_version = version or self.version_manager.get_latest_version(manifest)
|
|
569
|
-
|
|
570
|
-
if not target_version or target_version not in manifest.get('versions', {}):
|
|
571
|
-
return None
|
|
572
|
-
|
|
573
|
-
version_info = manifest['versions'][target_version]
|
|
574
|
-
expressions = version_info.get('expressions', {})
|
|
575
|
-
|
|
576
|
-
if expression_name not in expressions:
|
|
577
|
-
return None
|
|
578
|
-
|
|
579
|
-
filename = expressions[expression_name]
|
|
580
|
-
|
|
581
|
-
# Check if remote
|
|
582
|
-
if self.is_remote_url(path):
|
|
583
|
-
# Fetch and cache expression file
|
|
584
|
-
return self.fetch_remote_file(path, filename, namespace)
|
|
585
|
-
else:
|
|
586
|
-
# Local path
|
|
587
|
-
return os.path.join(path, filename)
|
|
588
|
-
|
|
589
|
-
except Exception as e:
|
|
590
|
-
log_warning(f"[namespace] Failed to get expression file path: {e}")
|
|
591
|
-
return None
|
|
592
|
-
|
|
593
|
-
def validate_expression_integrity(self, namespace: str, expression_name: str, version: str = None) -> bool:
|
|
594
|
-
"""
|
|
595
|
-
Validate integrity of an expression file
|
|
596
|
-
|
|
597
|
-
Args:
|
|
598
|
-
namespace: Namespace name
|
|
599
|
-
expression_name: Name of the expression
|
|
600
|
-
version: Specific version (optional)
|
|
601
|
-
|
|
602
|
-
Returns:
|
|
603
|
-
True if integrity is valid
|
|
604
|
-
"""
|
|
605
|
-
file_path = self.get_expression_file_path(namespace, expression_name, version)
|
|
606
|
-
|
|
607
|
-
if not file_path or not os.path.exists(file_path):
|
|
608
|
-
log_warning(f"[namespace] Expression file not found: {expression_name}")
|
|
609
|
-
return False
|
|
610
|
-
|
|
611
|
-
try:
|
|
612
|
-
return self.integrity_manager.validate_integrity(file_path)
|
|
613
|
-
except Exception as e:
|
|
614
|
-
log_warning(f"[namespace] Integrity validation failed for {expression_name}: {e}")
|
|
615
|
-
return False
|
|
616
|
-
|
|
617
|
-
def _clear_namespace_cache(self, namespace: str = None):
|
|
618
|
-
"""Clear namespace cache"""
|
|
619
|
-
if namespace:
|
|
620
|
-
self._namespace_cache.pop(namespace, None)
|
|
621
|
-
else:
|
|
622
|
-
self._namespace_cache.clear()
|
|
623
|
-
|
|
624
|
-
# Also clear version manager cache
|
|
625
|
-
self.version_manager.clear_cache()
|
|
626
|
-
|
|
627
|
-
def reset_user_path(self):
|
|
628
|
-
"""Reset user path to default"""
|
|
629
|
-
self._custom_user_path = None
|
|
630
|
-
self._clear_namespace_cache("user")
|
|
631
|
-
log_info("[namespace] User path reset to default")
|
|
632
|
-
|
|
633
|
-
def get_namespace_summary(self) -> Dict[str, NamespaceInfo]:
|
|
634
|
-
"""
|
|
635
|
-
Get summary of all namespaces
|
|
636
|
-
|
|
637
|
-
Returns:
|
|
638
|
-
Dictionary mapping namespace names to NamespaceInfo objects
|
|
639
|
-
"""
|
|
640
|
-
summary = {}
|
|
641
|
-
|
|
642
|
-
for namespace in self.valid_namespaces:
|
|
643
|
-
try:
|
|
644
|
-
summary[namespace] = self.get_namespace_info(namespace)
|
|
645
|
-
except Exception as e:
|
|
646
|
-
log_warning(f"[namespace] Failed to get summary for {namespace}: {e}")
|
|
647
|
-
# Create minimal info for failed namespace
|
|
648
|
-
summary[namespace] = NamespaceInfo(
|
|
649
|
-
name=namespace,
|
|
650
|
-
path="unknown",
|
|
651
|
-
is_builtin=(namespace == "builtin"),
|
|
652
|
-
is_writable=False,
|
|
653
|
-
expression_count=0,
|
|
654
|
-
available_versions=[]
|
|
655
|
-
)
|
|
656
|
-
|
|
657
|
-
return summary
|