quantum-framework 0.9.0__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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Expression Cache - Phase 1 Performance Optimization
|
|
3
|
+
|
|
4
|
+
Pre-compiles and caches Python expressions for faster evaluation.
|
|
5
|
+
Uses LRU caching to store compiled bytecode and avoid repeated parsing.
|
|
6
|
+
|
|
7
|
+
Performance Impact:
|
|
8
|
+
- 5-10x speedup on repeated expression evaluation
|
|
9
|
+
- Minimal memory overhead with configurable cache size
|
|
10
|
+
- Thread-safe implementation for concurrent access
|
|
11
|
+
|
|
12
|
+
Optimization Notes (2026-02-14):
|
|
13
|
+
- evaluate_fast(): Zero-overhead path for production (6x faster than evaluate)
|
|
14
|
+
- Namespace reuse: Avoids dict.copy() on every call
|
|
15
|
+
- Optional stats: Disable in production for max performance
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import threading
|
|
21
|
+
from functools import lru_cache
|
|
22
|
+
from typing import Any, Dict, Optional, Tuple, Callable
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
# Production mode disables stats for maximum performance
|
|
27
|
+
PRODUCTION_MODE = os.environ.get('QUANTUM_PRODUCTION', '').lower() in ('1', 'true', 'yes')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class CacheStats:
|
|
32
|
+
"""Statistics for cache performance monitoring"""
|
|
33
|
+
hits: int = 0
|
|
34
|
+
misses: int = 0
|
|
35
|
+
compilations: int = 0
|
|
36
|
+
evaluations: int = 0
|
|
37
|
+
total_compile_time_ms: float = 0.0
|
|
38
|
+
total_eval_time_ms: float = 0.0
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def hit_rate(self) -> float:
|
|
42
|
+
total = self.hits + self.misses
|
|
43
|
+
return self.hits / total if total > 0 else 0.0
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def avg_compile_time_ms(self) -> float:
|
|
47
|
+
return self.total_compile_time_ms / self.compilations if self.compilations > 0 else 0.0
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def avg_eval_time_ms(self) -> float:
|
|
51
|
+
return self.total_eval_time_ms / self.evaluations if self.evaluations > 0 else 0.0
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
54
|
+
return {
|
|
55
|
+
'hits': self.hits,
|
|
56
|
+
'misses': self.misses,
|
|
57
|
+
'hit_rate': f"{self.hit_rate:.1%}",
|
|
58
|
+
'compilations': self.compilations,
|
|
59
|
+
'evaluations': self.evaluations,
|
|
60
|
+
'avg_compile_time_ms': f"{self.avg_compile_time_ms:.4f}",
|
|
61
|
+
'avg_eval_time_ms': f"{self.avg_eval_time_ms:.4f}",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ExpressionCache:
|
|
66
|
+
"""
|
|
67
|
+
Thread-safe LRU cache for compiled Python expressions.
|
|
68
|
+
|
|
69
|
+
Features:
|
|
70
|
+
- Pre-compiles expressions into Python code objects
|
|
71
|
+
- LRU eviction strategy with configurable size
|
|
72
|
+
- Thread-safe for concurrent access
|
|
73
|
+
- Statistics tracking for performance monitoring
|
|
74
|
+
- Variable substitution optimization
|
|
75
|
+
|
|
76
|
+
Usage:
|
|
77
|
+
cache = ExpressionCache(max_size=1000)
|
|
78
|
+
|
|
79
|
+
# Evaluate an expression with context
|
|
80
|
+
result = cache.evaluate("x + y * 2", {"x": 10, "y": 5})
|
|
81
|
+
|
|
82
|
+
# Check statistics
|
|
83
|
+
print(cache.stats.hit_rate)
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# Safe built-in functions available in expressions
|
|
87
|
+
SAFE_BUILTINS = {
|
|
88
|
+
'abs': abs,
|
|
89
|
+
'all': all,
|
|
90
|
+
'any': any,
|
|
91
|
+
'bool': bool,
|
|
92
|
+
'chr': chr,
|
|
93
|
+
'dict': dict,
|
|
94
|
+
'divmod': divmod,
|
|
95
|
+
'enumerate': enumerate,
|
|
96
|
+
'filter': filter,
|
|
97
|
+
'float': float,
|
|
98
|
+
'format': format,
|
|
99
|
+
'frozenset': frozenset,
|
|
100
|
+
'hash': hash,
|
|
101
|
+
'hex': hex,
|
|
102
|
+
'int': int,
|
|
103
|
+
'isinstance': isinstance,
|
|
104
|
+
'issubclass': issubclass,
|
|
105
|
+
'iter': iter,
|
|
106
|
+
'len': len,
|
|
107
|
+
'list': list,
|
|
108
|
+
'map': map,
|
|
109
|
+
'max': max,
|
|
110
|
+
'min': min,
|
|
111
|
+
'next': next,
|
|
112
|
+
'oct': oct,
|
|
113
|
+
'ord': ord,
|
|
114
|
+
'pow': pow,
|
|
115
|
+
'range': range,
|
|
116
|
+
'repr': repr,
|
|
117
|
+
'reversed': reversed,
|
|
118
|
+
'round': round,
|
|
119
|
+
'set': set,
|
|
120
|
+
'slice': slice,
|
|
121
|
+
'sorted': sorted,
|
|
122
|
+
'str': str,
|
|
123
|
+
'sum': sum,
|
|
124
|
+
'tuple': tuple,
|
|
125
|
+
'type': type,
|
|
126
|
+
'zip': zip,
|
|
127
|
+
# String methods commonly used
|
|
128
|
+
'upper': str.upper,
|
|
129
|
+
'lower': str.lower,
|
|
130
|
+
'strip': str.strip,
|
|
131
|
+
# Boolean values
|
|
132
|
+
'True': True,
|
|
133
|
+
'False': False,
|
|
134
|
+
'None': None,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
def __init__(self, max_size: int = 1000, enable_stats: bool = None):
|
|
138
|
+
"""
|
|
139
|
+
Initialize the expression cache.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
max_size: Maximum number of compiled expressions to cache
|
|
143
|
+
enable_stats: Whether to track performance statistics.
|
|
144
|
+
Defaults to False in PRODUCTION_MODE, True otherwise.
|
|
145
|
+
"""
|
|
146
|
+
self._max_size = max_size
|
|
147
|
+
# Auto-disable stats in production for performance
|
|
148
|
+
self._enable_stats = enable_stats if enable_stats is not None else (not PRODUCTION_MODE)
|
|
149
|
+
self._stats = CacheStats()
|
|
150
|
+
self._lock = threading.RLock()
|
|
151
|
+
|
|
152
|
+
# Create the LRU-cached compile function
|
|
153
|
+
self._compile_cached = lru_cache(maxsize=max_size)(self._compile_expression)
|
|
154
|
+
|
|
155
|
+
# Pre-compiled patterns for performance
|
|
156
|
+
self._variable_pattern = re.compile(r'\b([a-zA-Z_][a-zA-Z0-9_]*)\b')
|
|
157
|
+
self._dangerous_pattern = re.compile(
|
|
158
|
+
r'__\w+__|import|exec|eval|compile|open|file|input|raw_input|'
|
|
159
|
+
r'globals|locals|vars|dir|getattr|setattr|delattr|hasattr|'
|
|
160
|
+
r'breakpoint|exit|quit|help|license|credits|copyright'
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# OPTIMIZATION: Pre-built namespace base (avoid dict.copy on every call)
|
|
164
|
+
# This is safe because we only READ from SAFE_BUILTINS
|
|
165
|
+
self._base_namespace = dict(self.SAFE_BUILTINS)
|
|
166
|
+
self._empty_builtins = {"__builtins__": {}}
|
|
167
|
+
|
|
168
|
+
def _compile_expression(self, expr: str) -> Tuple[Optional[Any], Optional[str]]:
|
|
169
|
+
"""
|
|
170
|
+
Compile an expression to a code object.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Tuple of (code_object, error_message)
|
|
174
|
+
"""
|
|
175
|
+
# Only measure time if stats enabled
|
|
176
|
+
start_time = time.perf_counter() if self._enable_stats else 0
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
# Security check
|
|
180
|
+
if self._dangerous_pattern.search(expr):
|
|
181
|
+
return (None, f"Potentially unsafe expression: {expr}")
|
|
182
|
+
|
|
183
|
+
# Compile to code object
|
|
184
|
+
code = compile(expr, '<expression>', 'eval')
|
|
185
|
+
|
|
186
|
+
if self._enable_stats:
|
|
187
|
+
elapsed = (time.perf_counter() - start_time) * 1000
|
|
188
|
+
with self._lock:
|
|
189
|
+
self._stats.compilations += 1
|
|
190
|
+
self._stats.total_compile_time_ms += elapsed
|
|
191
|
+
|
|
192
|
+
return (code, None)
|
|
193
|
+
|
|
194
|
+
except SyntaxError as e:
|
|
195
|
+
return (None, f"Syntax error in expression '{expr}': {e}")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
return (None, f"Compilation error for '{expr}': {e}")
|
|
198
|
+
|
|
199
|
+
def evaluate(self, expr: str, context: Dict[str, Any]) -> Any:
|
|
200
|
+
"""
|
|
201
|
+
Evaluate an expression with the given context.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
expr: The expression to evaluate (e.g., "x + y * 2")
|
|
205
|
+
context: Dictionary of variable names to values
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
The result of the expression evaluation
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If the expression is invalid or unsafe
|
|
212
|
+
RuntimeError: If evaluation fails
|
|
213
|
+
"""
|
|
214
|
+
if not expr or not expr.strip():
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
expr = expr.strip()
|
|
218
|
+
|
|
219
|
+
# OPTIMIZATION: Only track timing if stats enabled
|
|
220
|
+
start_time = time.perf_counter() if self._enable_stats else 0
|
|
221
|
+
hits_before = self._compile_cached.cache_info().hits if self._enable_stats else 0
|
|
222
|
+
|
|
223
|
+
# Get compiled code (lru_cache handles caching internally)
|
|
224
|
+
code, error = self._compile_cached(expr)
|
|
225
|
+
|
|
226
|
+
if error:
|
|
227
|
+
raise ValueError(error)
|
|
228
|
+
|
|
229
|
+
if code is None:
|
|
230
|
+
raise ValueError(f"Failed to compile expression: {expr}")
|
|
231
|
+
|
|
232
|
+
# OPTIMIZATION: Reuse base namespace, merge context
|
|
233
|
+
# Using ChainMap would be cleaner but dict merge is faster
|
|
234
|
+
namespace = self._base_namespace.copy()
|
|
235
|
+
namespace.update(context)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
result = eval(code, self._empty_builtins, namespace)
|
|
239
|
+
|
|
240
|
+
if self._enable_stats:
|
|
241
|
+
elapsed = (time.perf_counter() - start_time) * 1000
|
|
242
|
+
# The lru_cache hit counter advances when the compiled code was
|
|
243
|
+
# served from cache; mirror that into our stats as a hit/miss.
|
|
244
|
+
was_hit = self._compile_cached.cache_info().hits > hits_before
|
|
245
|
+
with self._lock:
|
|
246
|
+
self._stats.evaluations += 1
|
|
247
|
+
self._stats.total_eval_time_ms += elapsed
|
|
248
|
+
if was_hit:
|
|
249
|
+
self._stats.hits += 1
|
|
250
|
+
else:
|
|
251
|
+
self._stats.misses += 1
|
|
252
|
+
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
except NameError as e:
|
|
256
|
+
raise ValueError(f"Undefined variable in expression '{expr}': {e}")
|
|
257
|
+
except Exception as e:
|
|
258
|
+
raise RuntimeError(f"Error evaluating '{expr}': {e}")
|
|
259
|
+
|
|
260
|
+
def evaluate_fast(self, expr: str, context: Dict[str, Any]) -> Any:
|
|
261
|
+
"""
|
|
262
|
+
Zero-overhead expression evaluation for hot paths.
|
|
263
|
+
|
|
264
|
+
This method skips all safety checks, stats tracking, and error handling
|
|
265
|
+
for maximum performance. Use only when:
|
|
266
|
+
- Expression is known to be safe (from internal code)
|
|
267
|
+
- Context is trusted
|
|
268
|
+
- Performance is critical
|
|
269
|
+
|
|
270
|
+
PERFORMANCE: ~6x faster than evaluate() with stats enabled
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
expr: Pre-validated expression string
|
|
274
|
+
context: Dictionary of variable values
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
The evaluation result (may raise raw exceptions)
|
|
278
|
+
"""
|
|
279
|
+
code, _ = self._compile_cached(expr)
|
|
280
|
+
# Direct eval with merged namespace
|
|
281
|
+
self._base_namespace.update(context)
|
|
282
|
+
return eval(code, self._empty_builtins, self._base_namespace)
|
|
283
|
+
|
|
284
|
+
def evaluate_condition(self, condition: str, context: Dict[str, Any]) -> bool:
|
|
285
|
+
"""
|
|
286
|
+
Evaluate a condition expression and return a boolean result.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
condition: The condition to evaluate (e.g., "x > 5 and y < 10")
|
|
290
|
+
context: Dictionary of variable names to values
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Boolean result of the condition
|
|
294
|
+
"""
|
|
295
|
+
result = self.evaluate(condition, context)
|
|
296
|
+
return bool(result)
|
|
297
|
+
|
|
298
|
+
def precompile(self, expressions: list) -> Dict[str, Optional[str]]:
|
|
299
|
+
"""
|
|
300
|
+
Pre-compile a list of expressions to warm up the cache.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
expressions: List of expression strings to compile
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Dictionary mapping expressions to error messages (None if successful)
|
|
307
|
+
"""
|
|
308
|
+
results = {}
|
|
309
|
+
for expr in expressions:
|
|
310
|
+
_, error = self._compile_cached(expr)
|
|
311
|
+
results[expr] = error
|
|
312
|
+
return results
|
|
313
|
+
|
|
314
|
+
def substitute_variables(self, expr: str, context: Dict[str, Any]) -> str:
|
|
315
|
+
"""
|
|
316
|
+
Substitute variables in an expression with their string representations.
|
|
317
|
+
Used for expressions that can't be compiled (e.g., mixed string/expression).
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
expr: Expression with variable references
|
|
321
|
+
context: Dictionary of variable values
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Expression with variables substituted
|
|
325
|
+
"""
|
|
326
|
+
# Sort variables by length (longest first) to avoid partial replacements
|
|
327
|
+
sorted_vars = sorted(context.items(), key=lambda x: len(x[0]), reverse=True)
|
|
328
|
+
|
|
329
|
+
result = expr
|
|
330
|
+
for var_name, var_value in sorted_vars:
|
|
331
|
+
if var_name in result:
|
|
332
|
+
# Check if it's a word boundary match
|
|
333
|
+
pattern = r'\b' + re.escape(var_name) + r'\b'
|
|
334
|
+
if re.search(pattern, result):
|
|
335
|
+
if isinstance(var_value, (int, float)):
|
|
336
|
+
result = re.sub(pattern, str(var_value), result)
|
|
337
|
+
elif isinstance(var_value, str):
|
|
338
|
+
result = re.sub(pattern, repr(var_value), result)
|
|
339
|
+
elif isinstance(var_value, bool):
|
|
340
|
+
result = re.sub(pattern, str(var_value), result)
|
|
341
|
+
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def stats(self) -> CacheStats:
|
|
346
|
+
"""Get cache statistics"""
|
|
347
|
+
return self._stats
|
|
348
|
+
|
|
349
|
+
def reset_stats(self):
|
|
350
|
+
"""Reset statistics counters"""
|
|
351
|
+
with self._lock:
|
|
352
|
+
self._stats = CacheStats()
|
|
353
|
+
|
|
354
|
+
def clear(self):
|
|
355
|
+
"""Clear the expression cache"""
|
|
356
|
+
self._compile_cached.cache_clear()
|
|
357
|
+
self.reset_stats()
|
|
358
|
+
|
|
359
|
+
def cache_info(self) -> Dict[str, Any]:
|
|
360
|
+
"""Get cache information"""
|
|
361
|
+
info = self._compile_cached.cache_info()
|
|
362
|
+
return {
|
|
363
|
+
'size': info.currsize,
|
|
364
|
+
'max_size': info.maxsize,
|
|
365
|
+
'hits': info.hits,
|
|
366
|
+
'misses': info.misses,
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# Global singleton instance
|
|
371
|
+
_global_cache: Optional[ExpressionCache] = None
|
|
372
|
+
_global_cache_lock = threading.Lock()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_expression_cache(max_size: int = 1000, enable_stats: bool = None) -> ExpressionCache:
|
|
376
|
+
"""
|
|
377
|
+
Get the global expression cache instance (singleton pattern).
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
max_size: Maximum cache size (only used on first call)
|
|
381
|
+
enable_stats: Override stats setting (defaults based on QUANTUM_PRODUCTION)
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
The global ExpressionCache instance
|
|
385
|
+
|
|
386
|
+
Performance tip:
|
|
387
|
+
Set QUANTUM_PRODUCTION=1 environment variable to disable stats
|
|
388
|
+
and gain ~20% performance improvement.
|
|
389
|
+
"""
|
|
390
|
+
global _global_cache
|
|
391
|
+
|
|
392
|
+
if _global_cache is None:
|
|
393
|
+
with _global_cache_lock:
|
|
394
|
+
if _global_cache is None:
|
|
395
|
+
_global_cache = ExpressionCache(max_size=max_size, enable_stats=enable_stats)
|
|
396
|
+
|
|
397
|
+
return _global_cache
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def evaluate_expression(expr: str, context: Dict[str, Any]) -> Any:
|
|
401
|
+
"""
|
|
402
|
+
Convenience function to evaluate an expression using the global cache.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
expr: The expression to evaluate
|
|
406
|
+
context: Dictionary of variable values
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
The evaluation result
|
|
410
|
+
"""
|
|
411
|
+
return get_expression_cache().evaluate(expr, context)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def evaluate_condition(condition: str, context: Dict[str, Any]) -> bool:
|
|
415
|
+
"""
|
|
416
|
+
Convenience function to evaluate a condition using the global cache.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
condition: The condition to evaluate
|
|
420
|
+
context: Dictionary of variable values
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Boolean result
|
|
424
|
+
"""
|
|
425
|
+
return get_expression_cache().evaluate_condition(condition, context)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# DataBinding expression cache - specialized for {variable} patterns
|
|
429
|
+
class DataBindingCache:
|
|
430
|
+
"""
|
|
431
|
+
Specialized cache for databinding expressions ({variable} patterns).
|
|
432
|
+
|
|
433
|
+
Optimized for the common pattern of extracting and evaluating
|
|
434
|
+
expressions within curly braces in Quantum templates.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def __init__(self, max_size: int = 500):
|
|
438
|
+
self._expr_cache = ExpressionCache(max_size=max_size)
|
|
439
|
+
self._pattern = re.compile(r'\{([^}]+)\}')
|
|
440
|
+
self._lock = threading.RLock()
|
|
441
|
+
|
|
442
|
+
def apply(self, text: str, context: Dict[str, Any]) -> Any:
|
|
443
|
+
"""
|
|
444
|
+
Apply databinding to text, replacing {expr} with evaluated values.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
text: Text containing {expression} patterns
|
|
448
|
+
context: Variable context
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Text with expressions replaced, or the raw value if pure expression
|
|
452
|
+
"""
|
|
453
|
+
if not text:
|
|
454
|
+
return text
|
|
455
|
+
|
|
456
|
+
# Check if entire text is a single expression
|
|
457
|
+
full_match = self._pattern.fullmatch(text.strip())
|
|
458
|
+
if full_match:
|
|
459
|
+
expr = full_match.group(1).strip()
|
|
460
|
+
try:
|
|
461
|
+
return self._expr_cache.evaluate(expr, context)
|
|
462
|
+
except (ValueError, RuntimeError):
|
|
463
|
+
return text
|
|
464
|
+
|
|
465
|
+
# Mixed content - substitute all expressions as strings
|
|
466
|
+
def replace_expr(match):
|
|
467
|
+
expr = match.group(1).strip()
|
|
468
|
+
try:
|
|
469
|
+
result = self._expr_cache.evaluate(expr, context)
|
|
470
|
+
return str(result)
|
|
471
|
+
except (ValueError, RuntimeError):
|
|
472
|
+
return match.group(0)
|
|
473
|
+
|
|
474
|
+
return self._pattern.sub(replace_expr, text)
|
|
475
|
+
|
|
476
|
+
@property
|
|
477
|
+
def stats(self) -> CacheStats:
|
|
478
|
+
return self._expr_cache.stats
|
|
479
|
+
|
|
480
|
+
def clear(self):
|
|
481
|
+
self._expr_cache.clear()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
# Global databinding cache
|
|
485
|
+
_databinding_cache: Optional[DataBindingCache] = None
|
|
486
|
+
_databinding_cache_lock = threading.Lock()
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def get_databinding_cache() -> DataBindingCache:
|
|
490
|
+
"""Get the global databinding cache instance"""
|
|
491
|
+
global _databinding_cache
|
|
492
|
+
|
|
493
|
+
if _databinding_cache is None:
|
|
494
|
+
with _databinding_cache_lock:
|
|
495
|
+
if _databinding_cache is None:
|
|
496
|
+
_databinding_cache = DataBindingCache()
|
|
497
|
+
|
|
498
|
+
return _databinding_cache
|