additory 0.1.0a3__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.
Files changed (120) hide show
  1. additory/__init__.py +58 -14
  2. additory/common/__init__.py +31 -147
  3. additory/common/column_selector.py +255 -0
  4. additory/common/distributions.py +286 -613
  5. additory/common/extractors.py +313 -0
  6. additory/common/knn_imputation.py +332 -0
  7. additory/common/result.py +380 -0
  8. additory/common/strategy_parser.py +243 -0
  9. additory/common/unit_conversions.py +338 -0
  10. additory/common/validation.py +283 -103
  11. additory/core/__init__.py +34 -22
  12. additory/core/backend.py +258 -0
  13. additory/core/config.py +177 -305
  14. additory/core/logging.py +230 -24
  15. additory/core/memory_manager.py +157 -495
  16. additory/expressions/__init__.py +2 -23
  17. additory/expressions/compiler.py +457 -0
  18. additory/expressions/engine.py +264 -487
  19. additory/expressions/integrity.py +179 -0
  20. additory/expressions/loader.py +263 -0
  21. additory/expressions/parser.py +363 -167
  22. additory/expressions/resolver.py +274 -0
  23. additory/functions/__init__.py +1 -0
  24. additory/functions/analyze/__init__.py +144 -0
  25. additory/functions/analyze/cardinality.py +58 -0
  26. additory/functions/analyze/correlations.py +66 -0
  27. additory/functions/analyze/distributions.py +53 -0
  28. additory/functions/analyze/duplicates.py +49 -0
  29. additory/functions/analyze/features.py +61 -0
  30. additory/functions/analyze/imputation.py +66 -0
  31. additory/functions/analyze/outliers.py +65 -0
  32. additory/functions/analyze/patterns.py +65 -0
  33. additory/functions/analyze/presets.py +72 -0
  34. additory/functions/analyze/quality.py +59 -0
  35. additory/functions/analyze/timeseries.py +53 -0
  36. additory/functions/analyze/types.py +45 -0
  37. additory/functions/expressions/__init__.py +161 -0
  38. additory/functions/snapshot/__init__.py +82 -0
  39. additory/functions/snapshot/filter.py +119 -0
  40. additory/functions/synthetic/__init__.py +113 -0
  41. additory/functions/synthetic/mode_detector.py +47 -0
  42. additory/functions/synthetic/strategies/__init__.py +1 -0
  43. additory/functions/synthetic/strategies/advanced.py +35 -0
  44. additory/functions/synthetic/strategies/augmentative.py +160 -0
  45. additory/functions/synthetic/strategies/generative.py +168 -0
  46. additory/functions/synthetic/strategies/presets.py +116 -0
  47. additory/functions/to/__init__.py +188 -0
  48. additory/functions/to/lookup.py +351 -0
  49. additory/functions/to/merge.py +189 -0
  50. additory/functions/to/sort.py +91 -0
  51. additory/functions/to/summarize.py +170 -0
  52. additory/functions/transform/__init__.py +140 -0
  53. additory/functions/transform/datetime.py +79 -0
  54. additory/functions/transform/extract.py +85 -0
  55. additory/functions/transform/harmonize.py +105 -0
  56. additory/functions/transform/knn.py +62 -0
  57. additory/functions/transform/onehotencoding.py +68 -0
  58. additory/functions/transform/transpose.py +42 -0
  59. additory-0.1.1a1.dist-info/METADATA +83 -0
  60. additory-0.1.1a1.dist-info/RECORD +62 -0
  61. additory/analysis/__init__.py +0 -48
  62. additory/analysis/cardinality.py +0 -126
  63. additory/analysis/correlations.py +0 -124
  64. additory/analysis/distributions.py +0 -376
  65. additory/analysis/quality.py +0 -158
  66. additory/analysis/scan.py +0 -400
  67. additory/common/backend.py +0 -371
  68. additory/common/column_utils.py +0 -191
  69. additory/common/exceptions.py +0 -62
  70. additory/common/lists.py +0 -229
  71. additory/common/patterns.py +0 -240
  72. additory/common/resolver.py +0 -567
  73. additory/common/sample_data.py +0 -182
  74. additory/core/ast_builder.py +0 -165
  75. additory/core/backends/__init__.py +0 -23
  76. additory/core/backends/arrow_bridge.py +0 -483
  77. additory/core/backends/cudf_bridge.py +0 -355
  78. additory/core/column_positioning.py +0 -358
  79. additory/core/compiler_polars.py +0 -166
  80. additory/core/enhanced_cache_manager.py +0 -1119
  81. additory/core/enhanced_matchers.py +0 -473
  82. additory/core/enhanced_version_manager.py +0 -325
  83. additory/core/executor.py +0 -59
  84. additory/core/integrity_manager.py +0 -477
  85. additory/core/loader.py +0 -190
  86. additory/core/namespace_manager.py +0 -657
  87. additory/core/parser.py +0 -176
  88. additory/core/polars_expression_engine.py +0 -601
  89. additory/core/registry.py +0 -176
  90. additory/core/sample_data_manager.py +0 -492
  91. additory/core/user_namespace.py +0 -751
  92. additory/core/validator.py +0 -27
  93. additory/dynamic_api.py +0 -304
  94. additory/expressions/proxy.py +0 -549
  95. additory/expressions/registry.py +0 -313
  96. additory/expressions/samples.py +0 -492
  97. additory/synthetic/__init__.py +0 -13
  98. additory/synthetic/column_name_resolver.py +0 -149
  99. additory/synthetic/distributions.py +0 -22
  100. additory/synthetic/forecast.py +0 -1132
  101. additory/synthetic/linked_list_parser.py +0 -415
  102. additory/synthetic/namespace_lookup.py +0 -129
  103. additory/synthetic/smote.py +0 -320
  104. additory/synthetic/strategies.py +0 -850
  105. additory/synthetic/synthesizer.py +0 -713
  106. additory/utilities/__init__.py +0 -53
  107. additory/utilities/encoding.py +0 -600
  108. additory/utilities/games.py +0 -300
  109. additory/utilities/keys.py +0 -8
  110. additory/utilities/lookup.py +0 -103
  111. additory/utilities/matchers.py +0 -216
  112. additory/utilities/resolvers.py +0 -286
  113. additory/utilities/settings.py +0 -167
  114. additory/utilities/units.py +0 -749
  115. additory/utilities/validators.py +0 -153
  116. additory-0.1.0a3.dist-info/METADATA +0 -288
  117. additory-0.1.0a3.dist-info/RECORD +0 -71
  118. additory-0.1.0a3.dist-info/licenses/LICENSE +0 -21
  119. {additory-0.1.0a3.dist-info → additory-0.1.1a1.dist-info}/WHEEL +0 -0
  120. {additory-0.1.0a3.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