additory 0.1.0a1__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 (87) hide show
  1. additory/__init__.py +15 -0
  2. additory/analysis/__init__.py +48 -0
  3. additory/analysis/cardinality.py +126 -0
  4. additory/analysis/correlations.py +124 -0
  5. additory/analysis/distributions.py +376 -0
  6. additory/analysis/quality.py +158 -0
  7. additory/analysis/scan.py +400 -0
  8. additory/augment/__init__.py +24 -0
  9. additory/augment/augmentor.py +653 -0
  10. additory/augment/builtin_lists.py +430 -0
  11. additory/augment/distributions.py +22 -0
  12. additory/augment/forecast.py +1132 -0
  13. additory/augment/list_registry.py +177 -0
  14. additory/augment/smote.py +320 -0
  15. additory/augment/strategies.py +883 -0
  16. additory/common/__init__.py +157 -0
  17. additory/common/backend.py +355 -0
  18. additory/common/column_utils.py +191 -0
  19. additory/common/distributions.py +737 -0
  20. additory/common/exceptions.py +62 -0
  21. additory/common/lists.py +229 -0
  22. additory/common/patterns.py +240 -0
  23. additory/common/resolver.py +567 -0
  24. additory/common/sample_data.py +182 -0
  25. additory/common/validation.py +197 -0
  26. additory/core/__init__.py +27 -0
  27. additory/core/ast_builder.py +165 -0
  28. additory/core/backends/__init__.py +23 -0
  29. additory/core/backends/arrow_bridge.py +476 -0
  30. additory/core/backends/cudf_bridge.py +355 -0
  31. additory/core/column_positioning.py +358 -0
  32. additory/core/compiler_polars.py +166 -0
  33. additory/core/config.py +342 -0
  34. additory/core/enhanced_cache_manager.py +1119 -0
  35. additory/core/enhanced_matchers.py +473 -0
  36. additory/core/enhanced_version_manager.py +325 -0
  37. additory/core/executor.py +59 -0
  38. additory/core/integrity_manager.py +477 -0
  39. additory/core/loader.py +190 -0
  40. additory/core/logging.py +24 -0
  41. additory/core/memory_manager.py +547 -0
  42. additory/core/namespace_manager.py +657 -0
  43. additory/core/parser.py +176 -0
  44. additory/core/polars_expression_engine.py +551 -0
  45. additory/core/registry.py +176 -0
  46. additory/core/sample_data_manager.py +492 -0
  47. additory/core/user_namespace.py +751 -0
  48. additory/core/validator.py +27 -0
  49. additory/dynamic_api.py +308 -0
  50. additory/expressions/__init__.py +26 -0
  51. additory/expressions/engine.py +551 -0
  52. additory/expressions/parser.py +176 -0
  53. additory/expressions/proxy.py +546 -0
  54. additory/expressions/registry.py +313 -0
  55. additory/expressions/samples.py +492 -0
  56. additory/synthetic/__init__.py +101 -0
  57. additory/synthetic/api.py +220 -0
  58. additory/synthetic/common_integration.py +314 -0
  59. additory/synthetic/config.py +262 -0
  60. additory/synthetic/engines.py +529 -0
  61. additory/synthetic/exceptions.py +180 -0
  62. additory/synthetic/file_managers.py +518 -0
  63. additory/synthetic/generator.py +702 -0
  64. additory/synthetic/generator_parser.py +68 -0
  65. additory/synthetic/integration.py +319 -0
  66. additory/synthetic/models.py +241 -0
  67. additory/synthetic/pattern_resolver.py +573 -0
  68. additory/synthetic/performance.py +469 -0
  69. additory/synthetic/polars_integration.py +464 -0
  70. additory/synthetic/proxy.py +60 -0
  71. additory/synthetic/schema_parser.py +685 -0
  72. additory/synthetic/validator.py +553 -0
  73. additory/utilities/__init__.py +53 -0
  74. additory/utilities/encoding.py +600 -0
  75. additory/utilities/games.py +300 -0
  76. additory/utilities/keys.py +8 -0
  77. additory/utilities/lookup.py +103 -0
  78. additory/utilities/matchers.py +216 -0
  79. additory/utilities/resolvers.py +286 -0
  80. additory/utilities/settings.py +167 -0
  81. additory/utilities/units.py +746 -0
  82. additory/utilities/validators.py +153 -0
  83. additory-0.1.0a1.dist-info/METADATA +293 -0
  84. additory-0.1.0a1.dist-info/RECORD +87 -0
  85. additory-0.1.0a1.dist-info/WHEEL +5 -0
  86. additory-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  87. additory-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced Expression Proxy for the Enhanced Expressions System
4
+ Integrates version management, caching, integrity validation, and Polars processing
5
+ """
6
+
7
+ import os
8
+ import pandas as pd
9
+ import polars as pl
10
+ from typing import Optional, Union, Dict, Any, List
11
+ from dataclasses import dataclass
12
+
13
+ from ..core.enhanced_version_manager import EnhancedVersionManager
14
+ from ..core.enhanced_cache_manager import get_cache_manager
15
+ from ..core.polars_expression_engine import PolarsExpressionEngine
16
+ from ..core.namespace_manager import NamespaceManager
17
+ from ..core.sample_data_manager import get_sample_data_manager, SampleDataManager
18
+ from ..core.parser import parse_expression
19
+ from ..core.logging import log_info, log_warning
20
+
21
+
22
+ @dataclass
23
+ class SampleDataRequest:
24
+ """Marker class for sample data requests"""
25
+ sample_type: str # "clean" or "unclean"
26
+
27
+ def __repr__(self):
28
+ return f"SampleDataRequest(type='{self.sample_type}')"
29
+
30
+
31
+ # Global sample data request instances
32
+ sample_data = SampleDataRequest("clean")
33
+ sample_data_unclean = SampleDataRequest("unclean")
34
+
35
+
36
+ class ExpressionNotFoundError(Exception):
37
+ """Raised when an expression cannot be found"""
38
+ pass
39
+
40
+
41
+ class ExpressionExecutionError(Exception):
42
+ """Raised when expression execution fails"""
43
+ pass
44
+
45
+
46
+ class EnhancedExpressionCallable:
47
+ """
48
+ Callable object representing a specific expression
49
+ Handles execution, sample data, and version resolution
50
+ """
51
+
52
+ def __init__(self, expression_name: str, namespace: str, proxy: 'EnhancedExpressionProxy'):
53
+ self.expression_name = expression_name
54
+ self.namespace = namespace
55
+ self.proxy = proxy
56
+
57
+ def __call__(self, df_or_request, version: Optional[str] = None,
58
+ backend: Optional[str] = None, insert_at: Optional[str] = None, **kwargs):
59
+ """
60
+ Execute expression or return sample data
61
+
62
+ Args:
63
+ df_or_request: DataFrame to process or SampleDataRequest
64
+ version: Specific version to use (optional)
65
+ backend: Backend hint (ignored - always uses Polars)
66
+ insert_at: Output column name (defaults to expression name)
67
+ **kwargs: Additional execution parameters
68
+
69
+ Returns:
70
+ DataFrame with expression results or sample data
71
+ """
72
+ try:
73
+ # Handle sample data requests
74
+ if isinstance(df_or_request, SampleDataRequest):
75
+ return self.proxy._handle_sample_request(
76
+ self.expression_name, df_or_request.sample_type, version
77
+ )
78
+
79
+ # Handle normal expression execution
80
+ return self.proxy._execute_expression(
81
+ self.expression_name, df_or_request, version, insert_at, **kwargs
82
+ )
83
+
84
+ except Exception as e:
85
+ log_warning(f"[expression_proxy] Failed to execute {self.expression_name}: {e}")
86
+ raise ExpressionExecutionError(f"Failed to execute {self.expression_name}: {e}")
87
+
88
+ def info(self, version: Optional[str] = None) -> Dict[str, Any]:
89
+ """Get detailed information about this expression"""
90
+ return self.proxy.get_expression_info(self.expression_name, version)
91
+
92
+ def sample(self, clean: bool = True) -> pd.DataFrame:
93
+ """Get sample data for this expression"""
94
+ sample_type = "clean" if clean else "unclean"
95
+ return self.proxy._handle_sample_request(self.expression_name, sample_type)
96
+
97
+ def refresh(self, version: Optional[str] = None) -> bool:
98
+ """Force refresh this expression from cache"""
99
+ resolved_version = version or self.proxy.version_manager.default_version
100
+ return self.proxy.cache_manager.force_refresh_expression(
101
+ self.expression_name, resolved_version, self.namespace
102
+ )
103
+
104
+
105
+ class EnhancedExpressionProxy:
106
+ """
107
+ Enhanced expression proxy with full pipeline integration
108
+ Supports dual namespaces, caching, version management, and Polars processing
109
+ """
110
+
111
+ def __init__(self, namespace: str = "builtin"):
112
+ """
113
+ Initialize enhanced expression proxy
114
+
115
+ Args:
116
+ namespace: Namespace to operate in ("builtin" or "user")
117
+ """
118
+ self.namespace = namespace
119
+
120
+ # Initialize core components
121
+ self.version_manager = EnhancedVersionManager()
122
+ self.cache_manager = get_cache_manager()
123
+ self.polars_engine = PolarsExpressionEngine()
124
+ self.namespace_manager = NamespaceManager()
125
+ self.sample_data_manager = get_sample_data_manager()
126
+
127
+ # Configuration
128
+ self.default_version = None # Use version manager's default
129
+ self.auto_cache = True
130
+ self.validate_integrity = True
131
+
132
+ log_info(f"[expression_proxy] Initialized for namespace: {namespace}")
133
+
134
+ def __getattr__(self, expression_name: str) -> EnhancedExpressionCallable:
135
+ """
136
+ Get expression callable for dynamic access
137
+
138
+ Args:
139
+ expression_name: Name of the expression
140
+
141
+ Returns:
142
+ EnhancedExpressionCallable for the expression
143
+ """
144
+ return EnhancedExpressionCallable(expression_name, self.namespace, self)
145
+
146
+ def _resolve_expression(self, expression_name: str, version: Optional[str] = None) -> Dict[str, Any]:
147
+ """
148
+ Resolve expression from cache or source
149
+
150
+ Args:
151
+ expression_name: Name of the expression
152
+ version: Specific version (optional)
153
+
154
+ Returns:
155
+ Dictionary with expression data and metadata
156
+
157
+ Raises:
158
+ ExpressionNotFoundError: If expression cannot be found
159
+ """
160
+ try:
161
+ # Determine version to use
162
+ resolved_version = version or self.default_version or self.version_manager.default_version
163
+
164
+ # Try to get from cache first
165
+ cached_path = self.cache_manager.get_cached_expression(
166
+ expression_name, resolved_version, self.namespace
167
+ )
168
+
169
+ if cached_path:
170
+ log_info(f"[expression_proxy] Using cached expression: {expression_name} v{resolved_version}")
171
+ return self._load_expression_from_file(cached_path, expression_name, resolved_version)
172
+
173
+ # Not in cache - try to load and cache from source
174
+ if self.auto_cache:
175
+ source_path = self._find_source_expression(expression_name, resolved_version)
176
+ if source_path:
177
+ # Cache the expression
178
+ success = self.cache_manager.cache_expression(
179
+ source_path, self.namespace, expression_name, resolved_version
180
+ )
181
+
182
+ if success:
183
+ cached_path = self.cache_manager.get_cached_expression(
184
+ expression_name, resolved_version, self.namespace
185
+ )
186
+ if cached_path:
187
+ return self._load_expression_from_file(cached_path, expression_name, resolved_version)
188
+
189
+ # Fallback to direct loading
190
+ return self._load_expression_from_file(source_path, expression_name, resolved_version)
191
+
192
+ raise ExpressionNotFoundError(
193
+ f"Expression '{expression_name}' version '{resolved_version}' not found in namespace '{self.namespace}'"
194
+ )
195
+
196
+ except Exception as e:
197
+ if isinstance(e, ExpressionNotFoundError):
198
+ raise
199
+ log_warning(f"[expression_proxy] Failed to resolve expression {expression_name}: {e}")
200
+ raise ExpressionNotFoundError(f"Failed to resolve expression {expression_name}: {e}")
201
+
202
+ def _find_source_expression(self, expression_name: str, version: str) -> Optional[str]:
203
+ """Find source expression file"""
204
+ try:
205
+ # Get namespace path
206
+ if self.namespace == "builtin":
207
+ base_path = self.namespace_manager.builtin_path
208
+ else:
209
+ base_path = self.namespace_manager.user_path
210
+
211
+ # Try to load manifest to get exact filename
212
+ try:
213
+ manifest = self.version_manager.load_manifest(base_path)
214
+ if version in manifest and expression_name in manifest[version].expressions:
215
+ filename = manifest[version].expressions[expression_name]
216
+ source_path = os.path.join(base_path, filename)
217
+ if os.path.exists(source_path):
218
+ return source_path
219
+ except Exception:
220
+ pass
221
+
222
+ # Fallback: try standard filename pattern
223
+ filename = f"{expression_name}_{version}.add"
224
+ source_path = os.path.join(base_path, filename)
225
+ if os.path.exists(source_path):
226
+ return source_path
227
+
228
+ return None
229
+
230
+ except Exception as e:
231
+ log_warning(f"[expression_proxy] Failed to find source for {expression_name}: {e}")
232
+ return None
233
+
234
+ def _load_expression_from_file(self, file_path: str, expression_name: str, version: str) -> Dict[str, Any]:
235
+ """Load and parse expression from file"""
236
+ try:
237
+ # Read file content
238
+ with open(file_path, 'r', encoding='utf-8') as f:
239
+ file_content = f.read()
240
+
241
+ # Parse the expression file
242
+ parsed_data = parse_expression(file_content)
243
+
244
+ return {
245
+ "name": expression_name,
246
+ "version": version,
247
+ "file_path": file_path,
248
+ "parsed_data": parsed_data,
249
+ "ast": parsed_data.ast,
250
+ "expression": parsed_data.expression,
251
+ "sample_clean": parsed_data.sample_clean,
252
+ "sample_unclean": parsed_data.sample_unclean,
253
+ "metadata": parsed_data.metadata
254
+ }
255
+
256
+ except Exception as e:
257
+ log_warning(f"[expression_proxy] Failed to load expression from {file_path}: {e}")
258
+ raise ExpressionNotFoundError(f"Failed to load expression from {file_path}: {e}")
259
+
260
+ def _execute_expression(self, expression_name: str, df: Union[pd.DataFrame, pl.DataFrame],
261
+ version: Optional[str] = None, output_column: Optional[str] = None,
262
+ **kwargs) -> Union[pd.DataFrame, pl.DataFrame]:
263
+ """
264
+ Execute expression on dataframe
265
+
266
+ Args:
267
+ expression_name: Name of the expression
268
+ df: Input dataframe
269
+ version: Specific version (optional)
270
+ output_column: Output column name (defaults to expression name)
271
+ **kwargs: Additional execution parameters
272
+
273
+ Returns:
274
+ DataFrame with expression results
275
+ """
276
+ try:
277
+ # Resolve expression
278
+ expression_data = self._resolve_expression(expression_name, version)
279
+
280
+ # Determine output column
281
+ output_col = output_column or expression_name
282
+
283
+ # Detect input backend
284
+ if isinstance(df, pd.DataFrame):
285
+ backend_type = "pandas"
286
+ elif isinstance(df, pl.DataFrame):
287
+ backend_type = "polars"
288
+ else:
289
+ # Try to detect other types
290
+ backend_type = self.polars_engine.arrow_bridge.detect_backend(df)
291
+
292
+ # Execute using Polars engine
293
+ result = self.polars_engine.execute_expression(
294
+ df, expression_data["expression"], output_col, backend_type
295
+ )
296
+
297
+ # Extract DataFrame from ExpressionResult if needed
298
+ if hasattr(result, 'dataframe'):
299
+ result = result.dataframe
300
+
301
+ log_info(f"[expression_proxy] Executed {expression_name} v{expression_data['version']}")
302
+ return result
303
+
304
+ except Exception as e:
305
+ log_warning(f"[expression_proxy] Expression execution failed: {e}")
306
+ raise ExpressionExecutionError(f"Expression execution failed: {e}")
307
+
308
+ def _handle_sample_request(self, expression_name: str, sample_type: str,
309
+ version: Optional[str] = None) -> pd.DataFrame:
310
+ """
311
+ Handle sample data requests using enhanced sample data manager
312
+
313
+ Args:
314
+ expression_name: Name of the expression
315
+ sample_type: "clean" or "unclean"
316
+ version: Specific version (optional)
317
+
318
+ Returns:
319
+ DataFrame with sample data
320
+ """
321
+ try:
322
+ # Use enhanced sample data manager
323
+ if sample_type == "clean":
324
+ return self.sample_data_manager.get_clean_sample(
325
+ expression_name, self.namespace, version
326
+ )
327
+ else:
328
+ return self.sample_data_manager.get_unclean_sample(
329
+ expression_name, self.namespace, version
330
+ )
331
+
332
+ except Exception as e:
333
+ log_warning(f"[expression_proxy] Failed to get sample data for {expression_name}: {e}")
334
+ return pd.DataFrame({
335
+ "error": [f"Failed to get {sample_type} sample data: {e}"]
336
+ })
337
+
338
+ def get_expression_info(self, expression_name: str, version: Optional[str] = None) -> Dict[str, Any]:
339
+ """
340
+ Get detailed information about an expression
341
+
342
+ Args:
343
+ expression_name: Name of the expression
344
+ version: Specific version (optional)
345
+
346
+ Returns:
347
+ Dictionary with expression information
348
+ """
349
+ try:
350
+ resolved_version = version or self.default_version or self.version_manager.default_version
351
+
352
+ # Get cache info
353
+ cache_info = self.cache_manager.get_expression_cache_info(
354
+ expression_name, resolved_version, self.namespace
355
+ )
356
+
357
+ # Get expression data
358
+ try:
359
+ expression_data = self._resolve_expression(expression_name, version)
360
+ metadata = expression_data.get("metadata", {})
361
+ except Exception:
362
+ metadata = {}
363
+
364
+ return {
365
+ "name": expression_name,
366
+ "version": resolved_version,
367
+ "namespace": self.namespace,
368
+ "metadata": metadata,
369
+ "cache_info": cache_info,
370
+ "has_clean_sample": bool(expression_data.get("sample_clean")) if 'expression_data' in locals() else False,
371
+ "has_unclean_sample": bool(expression_data.get("sample_unclean")) if 'expression_data' in locals() else False
372
+ }
373
+
374
+ except Exception as e:
375
+ log_warning(f"[expression_proxy] Failed to get info for {expression_name}: {e}")
376
+ return {
377
+ "name": expression_name,
378
+ "version": resolved_version,
379
+ "namespace": self.namespace,
380
+ "error": str(e)
381
+ }
382
+
383
+ def list_expressions(self, version: Optional[str] = None) -> Dict[str, Any]:
384
+ """
385
+ List all available expressions in this namespace
386
+
387
+ Args:
388
+ version: Specific version (optional)
389
+
390
+ Returns:
391
+ Dictionary with expression list and metadata
392
+ """
393
+ try:
394
+ resolved_version = version or self.default_version or self.version_manager.default_version
395
+
396
+ # Get namespace path
397
+ if self.namespace == "builtin":
398
+ base_path = self.namespace_manager.builtin_path
399
+ else:
400
+ base_path = self.namespace_manager.user_path
401
+
402
+ # Try to load manifest
403
+ expressions = {}
404
+ try:
405
+ manifest = self.version_manager.load_manifest(base_path)
406
+ if resolved_version in manifest:
407
+ expressions = manifest[resolved_version].expressions
408
+ except Exception:
409
+ # Fallback: scan directory for .add files
410
+ if os.path.exists(base_path):
411
+ for filename in os.listdir(base_path):
412
+ if filename.endswith(f"_{resolved_version}.add"):
413
+ expr_name = filename.replace(f"_{resolved_version}.add", "")
414
+ expressions[expr_name] = filename
415
+
416
+ return {
417
+ "namespace": self.namespace,
418
+ "version": resolved_version,
419
+ "expressions": expressions,
420
+ "count": len(expressions)
421
+ }
422
+
423
+ except Exception as e:
424
+ log_warning(f"[expression_proxy] Failed to list expressions: {e}")
425
+ return {
426
+ "namespace": self.namespace,
427
+ "version": resolved_version,
428
+ "expressions": {},
429
+ "count": 0,
430
+ "error": str(e)
431
+ }
432
+
433
+ def set_default_version(self, version: str):
434
+ """Set default version for this proxy"""
435
+ self.default_version = version
436
+ log_info(f"[expression_proxy] Set default version to {version} for {self.namespace}")
437
+
438
+ def refresh_cache(self) -> Dict[str, int]:
439
+ """Refresh cache for this namespace"""
440
+ return self.cache_manager.refresh_cache(self.namespace)
441
+
442
+ def get_cache_status(self) -> Dict[str, Any]:
443
+ """Get cache status for this namespace"""
444
+ full_status = self.cache_manager.get_cache_status()
445
+ return {
446
+ "namespace": self.namespace,
447
+ "health": full_status.get("health", "unknown"),
448
+ "namespace_health": full_status.get("namespace_health", {}).get(self.namespace, {}),
449
+ "global_stats": full_status.get("global_stats", {})
450
+ }
451
+
452
+ def get_sample_info(self, expression_name: str, version: Optional[str] = None) -> Dict[str, Any]:
453
+ """
454
+ Get comprehensive sample data information
455
+
456
+ Args:
457
+ expression_name: Name of the expression
458
+ version: Specific version (optional)
459
+
460
+ Returns:
461
+ Dictionary with sample data information
462
+ """
463
+ try:
464
+ sample_info = self.sample_data_manager.get_sample_info(
465
+ expression_name, self.namespace, version
466
+ )
467
+
468
+ return {
469
+ "expression_name": sample_info.expression_name,
470
+ "version": sample_info.version,
471
+ "has_clean_sample": sample_info.has_clean,
472
+ "has_unclean_sample": sample_info.has_unclean,
473
+ "clean_rows": sample_info.clean_rows,
474
+ "unclean_rows": sample_info.unclean_rows,
475
+ "educational_comments": sample_info.educational_comments,
476
+ "validation_errors": sample_info.validation_errors,
477
+ "namespace": self.namespace
478
+ }
479
+
480
+ except Exception as e:
481
+ log_warning(f"[expression_proxy] Failed to get sample info for {expression_name}: {e}")
482
+ return {"error": str(e)}
483
+
484
+ def validate_sample_data(self, expression_name: str, sample_data: Dict[str, Any],
485
+ sample_type: str = "clean", version: Optional[str] = None) -> Dict[str, Any]:
486
+ """
487
+ Validate sample data format and content
488
+
489
+ Args:
490
+ expression_name: Name of the expression
491
+ sample_data: Sample data to validate
492
+ sample_type: "clean" or "unclean"
493
+ version: Specific version (optional)
494
+
495
+ Returns:
496
+ Dictionary with validation results
497
+ """
498
+ try:
499
+ is_valid, errors = self.sample_data_manager.validate_sample_data(
500
+ sample_data, expression_name, sample_type
501
+ )
502
+
503
+ return {
504
+ "is_valid": is_valid,
505
+ "errors": errors,
506
+ "sample_type": sample_type,
507
+ "expression_name": expression_name,
508
+ "namespace": self.namespace
509
+ }
510
+
511
+ except Exception as e:
512
+ log_warning(f"[expression_proxy] Failed to validate sample data for {expression_name}: {e}")
513
+ return {"error": str(e), "is_valid": False}
514
+
515
+ def create_sample_template(self, expression_name: str, columns: List[str]) -> Dict[str, Any]:
516
+ """
517
+ Create sample data template for an expression
518
+
519
+ Args:
520
+ expression_name: Name of the expression
521
+ columns: List of required columns
522
+
523
+ Returns:
524
+ Dictionary with sample data templates
525
+ """
526
+ try:
527
+ template = self.sample_data_manager.create_sample_template(expression_name, columns)
528
+
529
+ return {
530
+ "expression_name": expression_name,
531
+ "namespace": self.namespace,
532
+ "template": template,
533
+ "usage": {
534
+ "clean": "Use for normal testing and validation",
535
+ "unclean": "Use for error handling and edge case testing"
536
+ }
537
+ }
538
+
539
+ except Exception as e:
540
+ log_warning(f"[expression_proxy] Failed to create sample template for {expression_name}: {e}")
541
+ return {"error": str(e)}
542
+
543
+
544
+ # Backward compatibility aliases
545
+ ExpressionProxy = EnhancedExpressionProxy
546
+ ExpressionCallable = EnhancedExpressionCallable