quantalogic 0.61.2__py3-none-any.whl → 0.61.3__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 (29) hide show
  1. quantalogic/codeact/agent.py +1 -1
  2. quantalogic/codeact/cli.py +5 -5
  3. quantalogic/codeact/tools_manager.py +1 -1
  4. quantalogic/tools/action_gen_safe.py +340 -0
  5. quantalogic/tools/write_file_tool.py +7 -8
  6. {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/METADATA +2 -1
  7. {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/RECORD +10 -28
  8. quantalogic/python_interpreter/__init__.py +0 -23
  9. quantalogic/python_interpreter/assignment_visitors.py +0 -63
  10. quantalogic/python_interpreter/base_visitors.py +0 -20
  11. quantalogic/python_interpreter/class_visitors.py +0 -22
  12. quantalogic/python_interpreter/comprehension_visitors.py +0 -172
  13. quantalogic/python_interpreter/context_visitors.py +0 -59
  14. quantalogic/python_interpreter/control_flow_visitors.py +0 -88
  15. quantalogic/python_interpreter/exception_visitors.py +0 -109
  16. quantalogic/python_interpreter/exceptions.py +0 -39
  17. quantalogic/python_interpreter/execution.py +0 -202
  18. quantalogic/python_interpreter/function_utils.py +0 -386
  19. quantalogic/python_interpreter/function_visitors.py +0 -209
  20. quantalogic/python_interpreter/import_visitors.py +0 -28
  21. quantalogic/python_interpreter/interpreter_core.py +0 -358
  22. quantalogic/python_interpreter/literal_visitors.py +0 -74
  23. quantalogic/python_interpreter/misc_visitors.py +0 -148
  24. quantalogic/python_interpreter/operator_visitors.py +0 -108
  25. quantalogic/python_interpreter/scope.py +0 -10
  26. quantalogic/python_interpreter/visit_handlers.py +0 -110
  27. {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/LICENSE +0 -0
  28. {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/WHEEL +0 -0
  29. {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/entry_points.txt +0 -0
@@ -1,172 +0,0 @@
1
- import ast
2
- from typing import Any, Dict, List
3
-
4
- from .interpreter_core import ASTInterpreter
5
- from .exceptions import WrappedException
6
-
7
- async def visit_ListComp(self: ASTInterpreter, node: ast.ListComp, wrap_exceptions: bool = True) -> List[Any]:
8
- result = []
9
- base_frame = self.env_stack[-1].copy()
10
- self.env_stack.append(base_frame)
11
-
12
- async def rec(gen_idx: int):
13
- if gen_idx == len(node.generators):
14
- element = await self.visit(node.elt, wrap_exceptions=wrap_exceptions)
15
- result.append(element)
16
- else:
17
- comp = node.generators[gen_idx]
18
- iterable = await self.visit(comp.iter, wrap_exceptions=wrap_exceptions)
19
- if hasattr(iterable, '__aiter__'):
20
- async for item in iterable:
21
- new_frame = self.env_stack[-1].copy()
22
- self.env_stack.append(new_frame)
23
- await self.assign(comp.target, item)
24
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
25
- if all(conditions):
26
- await rec(gen_idx + 1)
27
- self.env_stack.pop()
28
- else:
29
- try:
30
- for item in iterable:
31
- new_frame = self.env_stack[-1].copy()
32
- self.env_stack.append(new_frame)
33
- await self.assign(comp.target, item)
34
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
35
- if all(conditions):
36
- await rec(gen_idx + 1)
37
- self.env_stack.pop()
38
- except TypeError as e:
39
- lineno = getattr(node, "lineno", 1)
40
- col = getattr(node, "col_offset", 0)
41
- context_line = self.source_lines[lineno - 1] if self.source_lines and lineno <= len(self.source_lines) else ""
42
- raise WrappedException(f"Object {iterable} is not iterable", e, lineno, col, context_line) from e
43
-
44
- await rec(0)
45
- self.env_stack.pop()
46
- return result
47
-
48
- async def visit_DictComp(self: ASTInterpreter, node: ast.DictComp, wrap_exceptions: bool = True) -> Dict[Any, Any]:
49
- result = {}
50
- base_frame = self.env_stack[-1].copy()
51
- self.env_stack.append(base_frame)
52
-
53
- async def rec(gen_idx: int):
54
- if gen_idx == len(node.generators):
55
- key = await self.visit(node.key, wrap_exceptions=True)
56
- val = await self.visit(node.value, wrap_exceptions=True)
57
- result[key] = val
58
- else:
59
- comp = node.generators[gen_idx]
60
- iterable = await self.visit(comp.iter, wrap_exceptions=wrap_exceptions)
61
- if hasattr(iterable, '__aiter__'):
62
- async for item in iterable:
63
- new_frame = self.env_stack[-1].copy()
64
- self.env_stack.append(new_frame)
65
- await self.assign(comp.target, item)
66
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
67
- if all(conditions):
68
- await rec(gen_idx + 1)
69
- self.env_stack.pop()
70
- else:
71
- try:
72
- for item in iterable:
73
- new_frame = self.env_stack[-1].copy()
74
- self.env_stack.append(new_frame)
75
- await self.assign(comp.target, item)
76
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
77
- if all(conditions):
78
- await rec(gen_idx + 1)
79
- self.env_stack.pop()
80
- except TypeError as e:
81
- lineno = getattr(node, "lineno", 1)
82
- col = getattr(node, "col_offset", 0)
83
- context_line = self.source_lines[lineno - 1] if self.source_lines and lineno <= len(self.source_lines) else ""
84
- raise WrappedException(f"Object {iterable} is not iterable", e, lineno, col, context_line) from e
85
-
86
- await rec(0)
87
- self.env_stack.pop()
88
- return result
89
-
90
- async def visit_SetComp(self: ASTInterpreter, node: ast.SetComp, wrap_exceptions: bool = True) -> set:
91
- result = set()
92
- base_frame = self.env_stack[-1].copy()
93
- self.env_stack.append(base_frame)
94
-
95
- async def rec(gen_idx: int):
96
- if gen_idx == len(node.generators):
97
- result.add(await self.visit(node.elt, wrap_exceptions=True))
98
- else:
99
- comp = node.generators[gen_idx]
100
- iterable = await self.visit(comp.iter, wrap_exceptions=wrap_exceptions)
101
- if hasattr(iterable, '__aiter__'):
102
- async for item in iterable:
103
- new_frame = self.env_stack[-1].copy()
104
- self.env_stack.append(new_frame)
105
- await self.assign(comp.target, item)
106
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
107
- if all(conditions):
108
- await rec(gen_idx + 1)
109
- self.env_stack.pop()
110
- else:
111
- try:
112
- for item in iterable:
113
- new_frame = self.env_stack[-1].copy()
114
- self.env_stack.append(new_frame)
115
- await self.assign(comp.target, item)
116
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
117
- if all(conditions):
118
- await rec(gen_idx + 1)
119
- self.env_stack.pop()
120
- except TypeError as e:
121
- lineno = getattr(node, "lineno", 1)
122
- col = getattr(node, "col_offset", 0)
123
- context_line = self.source_lines[lineno - 1] if self.source_lines and lineno <= len(self.source_lines) else ""
124
- raise WrappedException(f"Object {iterable} is not iterable", e, lineno, col, context_line) from e
125
-
126
- await rec(0)
127
- self.env_stack.pop()
128
- return result
129
-
130
- async def visit_GeneratorExp(self: ASTInterpreter, node: ast.GeneratorExp, wrap_exceptions: bool = True) -> Any:
131
- base_frame: Dict[str, Any] = self.env_stack[-1].copy()
132
- self.env_stack.append(base_frame)
133
-
134
- async def gen():
135
- async def rec(gen_idx: int):
136
- if gen_idx == len(node.generators):
137
- yield await self.visit(node.elt, wrap_exceptions=True)
138
- else:
139
- comp = node.generators[gen_idx]
140
- iterable = await self.visit(comp.iter, wrap_exceptions=wrap_exceptions)
141
- if hasattr(iterable, '__aiter__'):
142
- async for item in iterable:
143
- new_frame = self.env_stack[-1].copy()
144
- self.env_stack.append(new_frame)
145
- await self.assign(comp.target, item)
146
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
147
- if all(conditions):
148
- async for val in rec(gen_idx + 1):
149
- yield val
150
- self.env_stack.pop()
151
- else:
152
- try:
153
- for item in iterable:
154
- new_frame = self.env_stack[-1].copy()
155
- self.env_stack.append(new_frame)
156
- await self.assign(comp.target, item)
157
- conditions = [await self.visit(if_clause, wrap_exceptions=True) for if_clause in comp.ifs]
158
- if all(conditions):
159
- async for val in rec(gen_idx + 1):
160
- yield val
161
- self.env_stack.pop()
162
- except TypeError as e:
163
- lineno = getattr(node, "lineno", 1)
164
- col = getattr(node, "col_offset", 0)
165
- context_line = self.source_lines[lineno - 1] if self.source_lines and lineno <= len(self.source_lines) else ""
166
- raise WrappedException(f"Object {iterable} is not iterable", e, lineno, col, context_line) from e
167
-
168
- async for val in rec(0):
169
- yield val
170
-
171
- self.env_stack.pop()
172
- return gen()
@@ -1,59 +0,0 @@
1
- import ast
2
- from typing import Any
3
-
4
- from .exceptions import ReturnException
5
- from .interpreter_core import ASTInterpreter
6
-
7
- async def visit_With(self: ASTInterpreter, node: ast.With, wrap_exceptions: bool = True) -> Any:
8
- result = None
9
- contexts = []
10
- for item in node.items:
11
- ctx = await self.visit(item.context_expr, wrap_exceptions=wrap_exceptions)
12
- val = ctx.__enter__()
13
- contexts.append((ctx, val))
14
- if item.optional_vars:
15
- await self.assign(item.optional_vars, val)
16
- try:
17
- for stmt in node.body:
18
- result = await self.visit(stmt, wrap_exceptions=wrap_exceptions)
19
- except ReturnException as ret:
20
- for ctx, _ in reversed(contexts):
21
- ctx.__exit__(None, None, None)
22
- raise ret
23
- except Exception as e:
24
- exc_type, exc_value, tb = type(e), e, e.__traceback__
25
- for ctx, _ in reversed(contexts):
26
- if not ctx.__exit__(exc_type, exc_value, tb):
27
- raise
28
- raise
29
- else:
30
- for ctx, _ in reversed(contexts):
31
- ctx.__exit__(None, None, None)
32
- return result
33
-
34
- async def visit_AsyncWith(self: ASTInterpreter, node: ast.AsyncWith, wrap_exceptions: bool = True) -> Any:
35
- result = None
36
- contexts = []
37
- for item in node.items:
38
- ctx = await self.visit(item.context_expr, wrap_exceptions=wrap_exceptions)
39
- val = await ctx.__aenter__()
40
- contexts.append((ctx, val))
41
- if item.optional_vars:
42
- await self.assign(item.optional_vars, val)
43
- try:
44
- for stmt in node.body:
45
- result = await self.visit(stmt, wrap_exceptions=wrap_exceptions)
46
- except ReturnException as ret:
47
- for ctx, _ in reversed(contexts):
48
- await ctx.__aexit__(None, None, None)
49
- raise ret
50
- except Exception as e:
51
- exc_type, exc_value, tb = type(e), e, e.__traceback__
52
- for ctx, _ in reversed(contexts):
53
- if not await ctx.__aexit__(exc_type, exc_value, tb):
54
- raise
55
- raise
56
- else:
57
- for ctx, _ in reversed(contexts):
58
- await ctx.__aexit__(None, None, None)
59
- return result
@@ -1,88 +0,0 @@
1
- import ast
2
- from typing import Any
3
-
4
- from .exceptions import BreakException, ContinueException, ReturnException
5
- from .interpreter_core import ASTInterpreter
6
-
7
- async def visit_If(self: ASTInterpreter, node: ast.If, wrap_exceptions: bool = True) -> Any:
8
- if await self.visit(node.test, wrap_exceptions=wrap_exceptions):
9
- branch = node.body
10
- else:
11
- branch = node.orelse
12
- result = None
13
- if branch:
14
- for stmt in branch[:-1]:
15
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
16
- result = await self.visit(branch[-1], wrap_exceptions=wrap_exceptions)
17
- return result
18
-
19
- async def visit_While(self: ASTInterpreter, node: ast.While, wrap_exceptions: bool = True) -> None:
20
- while await self.visit(node.test, wrap_exceptions=wrap_exceptions):
21
- try:
22
- for stmt in node.body:
23
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
24
- except BreakException:
25
- break
26
- except ContinueException:
27
- continue
28
- for stmt in node.orelse:
29
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
30
-
31
- async def visit_For(self: ASTInterpreter, node: ast.For, wrap_exceptions: bool = True) -> None:
32
- iter_obj: Any = await self.visit(node.iter, wrap_exceptions=wrap_exceptions)
33
- broke = False
34
- if hasattr(iter_obj, '__aiter__'):
35
- async for item in iter_obj:
36
- await self.assign(node.target, item)
37
- try:
38
- for stmt in node.body:
39
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
40
- except BreakException:
41
- broke = True
42
- break
43
- except ContinueException:
44
- continue
45
- else:
46
- for item in iter_obj:
47
- await self.assign(node.target, item)
48
- try:
49
- for stmt in node.body:
50
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
51
- except BreakException:
52
- broke = True
53
- break
54
- except ContinueException:
55
- continue
56
- if not broke:
57
- for stmt in node.orelse:
58
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
59
-
60
- async def visit_AsyncFor(self: ASTInterpreter, node: ast.AsyncFor, wrap_exceptions: bool = True) -> None:
61
- iterable = await self.visit(node.iter, wrap_exceptions=wrap_exceptions)
62
- broke = False
63
- async for value in iterable:
64
- await self.assign(node.target, value)
65
- try:
66
- for stmt in node.body:
67
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
68
- except BreakException:
69
- broke = True
70
- break
71
- except ContinueException:
72
- continue
73
- if not broke:
74
- for stmt in node.orelse:
75
- await self.visit(stmt, wrap_exceptions=wrap_exceptions)
76
-
77
- async def visit_Break(self: ASTInterpreter, node: ast.Break, wrap_exceptions: bool = True) -> None:
78
- raise BreakException()
79
-
80
- async def visit_Continue(self: ASTInterpreter, node: ast.Continue, wrap_exceptions: bool = True) -> None:
81
- raise ContinueException()
82
-
83
- async def visit_Return(self: ASTInterpreter, node: ast.Return, wrap_exceptions: bool = True) -> None:
84
- value: Any = await self.visit(node.value, wrap_exceptions=wrap_exceptions) if node.value is not None else None
85
- raise ReturnException(value)
86
-
87
- async def visit_IfExp(self: ASTInterpreter, node: ast.IfExp, wrap_exceptions: bool = True) -> Any:
88
- return await self.visit(node.body, wrap_exceptions=wrap_exceptions) if await self.visit(node.test, wrap_exceptions=wrap_exceptions) else await self.visit(node.orelse, wrap_exceptions=wrap_exceptions)
@@ -1,109 +0,0 @@
1
- import ast
2
- from typing import Any, Optional, Tuple
3
-
4
- from .exceptions import BaseExceptionGroup, ReturnException, WrappedException
5
- from .interpreter_core import ASTInterpreter
6
-
7
- async def visit_Try(self: ASTInterpreter, node: ast.Try, wrap_exceptions: bool = True) -> Any:
8
- result: Any = None
9
- try:
10
- for stmt in node.body:
11
- result = await self.visit(stmt, wrap_exceptions=False)
12
- except ReturnException as ret:
13
- raise ret
14
- except Exception as e:
15
- original_e = e.original_exception if isinstance(e, WrappedException) else e
16
- for handler in node.handlers:
17
- exc_type = await self._resolve_exception_type(handler.type)
18
- if exc_type and isinstance(original_e, exc_type):
19
- if handler.name:
20
- self.set_variable(handler.name, original_e)
21
- handler_result = None
22
- try:
23
- for stmt in handler.body:
24
- handler_result = await self.visit(stmt, wrap_exceptions=True)
25
- except ReturnException as ret:
26
- raise ret
27
- if handler_result is not None:
28
- result = handler_result
29
- break
30
- else:
31
- raise
32
- else:
33
- for stmt in node.orelse:
34
- result = await self.visit(stmt, wrap_exceptions=True)
35
- finally:
36
- for stmt in node.finalbody:
37
- await self.visit(stmt, wrap_exceptions=True)
38
- return result
39
-
40
- async def visit_TryStar(self: ASTInterpreter, node: ast.TryStar, wrap_exceptions: bool = True) -> Any:
41
- result: Any = None
42
- exc_info: Optional[Tuple] = None
43
-
44
- try:
45
- for stmt in node.body:
46
- result = await self.visit(stmt, wrap_exceptions=False)
47
- except BaseException as e:
48
- exc_info = (type(e), e, e.__traceback__)
49
- handled = False
50
- if isinstance(e, BaseExceptionGroup):
51
- remaining_exceptions = []
52
- for handler in node.handlers:
53
- if handler.type is None:
54
- exc_type = BaseException
55
- elif isinstance(handler.type, ast.Name):
56
- exc_type = self.get_variable(handler.type.id)
57
- else:
58
- exc_type = await self.visit(handler.type, wrap_exceptions=True)
59
- matching_exceptions = [ex for ex in e.exceptions if isinstance(ex, exc_type)]
60
- if matching_exceptions:
61
- if handler.name:
62
- self.set_variable(handler.name, BaseExceptionGroup("", matching_exceptions))
63
- for stmt in handler.body:
64
- result = await self.visit(stmt, wrap_exceptions=True)
65
- handled = True
66
- remaining_exceptions.extend([ex for ex in e.exceptions if not isinstance(ex, exc_type)])
67
- if remaining_exceptions and not handled:
68
- raise BaseExceptionGroup("Uncaught exceptions", remaining_exceptions)
69
- if handled:
70
- exc_info = None
71
- else:
72
- for handler in node.handlers:
73
- if handler.type is None:
74
- exc_type = BaseException
75
- elif isinstance(handler.type, ast.Name):
76
- exc_type = self.get_variable(handler.type.id)
77
- else:
78
- exc_type = await self.visit(handler.type, wrap_exceptions=True)
79
- if exc_info and issubclass(exc_info[0], exc_type):
80
- if handler.name:
81
- self.set_variable(handler.name, exc_info[1])
82
- for stmt in handler.body:
83
- result = await self.visit(stmt, wrap_exceptions=True)
84
- exc_info = None
85
- handled = True
86
- break
87
- if exc_info and not handled:
88
- raise exc_info[1]
89
- else:
90
- for stmt in node.orelse:
91
- result = await self.visit(stmt, wrap_exceptions=True)
92
- finally:
93
- for stmt in node.finalbody:
94
- try:
95
- await self.visit(stmt, wrap_exceptions=True)
96
- except ReturnException:
97
- raise
98
- except Exception:
99
- if exc_info:
100
- raise exc_info[1]
101
- raise
102
-
103
- return result
104
-
105
- async def visit_Raise(self: ASTInterpreter, node: ast.Raise, wrap_exceptions: bool = True) -> None:
106
- exc = await self.visit(node.exc, wrap_exceptions=wrap_exceptions) if node.exc else None
107
- if exc:
108
- raise exc
109
- raise Exception("Raise with no exception specified")
@@ -1,39 +0,0 @@
1
- import ast
2
- from typing import Any, List
3
-
4
- class ReturnException(Exception):
5
- def __init__(self, value: Any) -> None:
6
- self.value: Any = value
7
-
8
- class BreakException(Exception):
9
- pass
10
-
11
- class ContinueException(Exception):
12
- pass
13
-
14
- class BaseExceptionGroup(Exception):
15
- def __init__(self, message: str, exceptions: List[Exception]):
16
- super().__init__(message)
17
- self.exceptions = exceptions
18
- self.message = message
19
-
20
- def __str__(self):
21
- return f"{self.message}: {', '.join(str(e) for e in self.exceptions)}"
22
-
23
- class WrappedException(Exception):
24
- def __init__(self, message: str, original_exception: Exception, lineno: int, col: int, context_line: str):
25
- super().__init__(message)
26
- self.original_exception: Exception = original_exception
27
- self.lineno: int = lineno
28
- self.col: int = col
29
- self.context_line: str = context_line
30
- self.message = original_exception.args[0] if original_exception.args else str(original_exception)
31
-
32
- def __str__(self):
33
- return f"Error line {self.lineno}, col {self.col}:\n{self.context_line}\nDescription: {self.message}"
34
-
35
- def has_await(node: ast.AST) -> bool:
36
- for child in ast.walk(node):
37
- if isinstance(child, ast.Await):
38
- return True
39
- return False
@@ -1,202 +0,0 @@
1
- import ast
2
- import asyncio
3
- import textwrap
4
- import time
5
- from dataclasses import dataclass
6
- from typing import Any, Dict, List, Optional, Tuple
7
-
8
- from .interpreter_core import ASTInterpreter
9
- from .function_utils import Function, AsyncFunction
10
- from .exceptions import WrappedException
11
-
12
- @dataclass
13
- class AsyncExecutionResult:
14
- result: Any
15
- error: Optional[str]
16
- execution_time: float
17
- local_variables: Optional[Dict[str, Any]] = None # Added to store local variables
18
-
19
- def optimize_ast(tree: ast.AST) -> ast.AST:
20
- """Perform constant folding and basic optimizations on the AST."""
21
- class ConstantFolder(ast.NodeTransformer):
22
- def visit_BinOp(self, node):
23
- self.generic_visit(node)
24
- if isinstance(node.left, ast.Constant) and isinstance(node.right, ast.Constant):
25
- left, right = node.left.value, node.right.value
26
- if isinstance(left, (int, float)) and isinstance(right, (int, float)):
27
- if isinstance(node.op, ast.Add):
28
- return ast.Constant(value=left + right)
29
- elif isinstance(node.op, ast.Sub):
30
- return ast.Constant(value=left - right)
31
- elif isinstance(node.op, ast.Mult):
32
- return ast.Constant(value=left * right)
33
- elif isinstance(node.op, ast.Div) and right != 0:
34
- return ast.Constant(value=left / right)
35
- return node
36
-
37
- def visit_If(self, node):
38
- self.generic_visit(node)
39
- if isinstance(node.test, ast.Constant):
40
- if node.test.value:
41
- return ast.Module(body=node.body, type_ignores=[])
42
- else:
43
- return ast.Module(body=node.orelse, type_ignores=[])
44
- return node
45
-
46
- return ConstantFolder().visit(tree)
47
-
48
- class ControlledEventLoop:
49
- """Encapsulated event loop management to prevent unauthorized access"""
50
- def __init__(self):
51
- self._loop = None
52
- self._created = False
53
- self._lock = asyncio.Lock()
54
-
55
- async def get_loop(self) -> asyncio.AbstractEventLoop:
56
- async with self._lock:
57
- if self._loop is None:
58
- self._loop = asyncio.new_event_loop()
59
- self._created = True
60
- return self._loop
61
-
62
- async def cleanup(self):
63
- async with self._lock:
64
- if self._created and self._loop and not self._loop.is_closed():
65
- for task in asyncio.all_tasks(self._loop):
66
- task.cancel()
67
- await asyncio.gather(*asyncio.all_tasks(self._loop), return_exceptions=True)
68
- self._loop.close()
69
- self._loop = None
70
- self._created = False
71
-
72
- async def run_task(self, coro, timeout: float) -> Any:
73
- return await asyncio.wait_for(coro, timeout=timeout)
74
-
75
- async def execute_async(
76
- code: str,
77
- entry_point: Optional[str] = None,
78
- args: Optional[Tuple] = None,
79
- kwargs: Optional[Dict[str, Any]] = None,
80
- timeout: float = 30,
81
- allowed_modules: List[str] = ['asyncio'],
82
- namespace: Optional[Dict[str, Any]] = None,
83
- max_memory_mb: int = 1024
84
- ) -> AsyncExecutionResult:
85
- start_time = time.time()
86
- event_loop_manager = ControlledEventLoop()
87
-
88
- try:
89
- ast_tree = optimize_ast(ast.parse(textwrap.dedent(code)))
90
- loop = await event_loop_manager.get_loop()
91
-
92
- # Remove direct asyncio access from builtins
93
- safe_namespace = namespace.copy() if namespace else {}
94
- safe_namespace.pop('asyncio', None) # Prevent direct asyncio access
95
-
96
- interpreter = ASTInterpreter(
97
- allowed_modules=allowed_modules,
98
- restrict_os=True,
99
- namespace=safe_namespace,
100
- max_memory_mb=max_memory_mb,
101
- source=code # Pass source code for better error context
102
- )
103
- interpreter.loop = loop
104
-
105
- async def run_execution():
106
- return await interpreter.execute_async(ast_tree)
107
-
108
- await event_loop_manager.run_task(run_execution(), timeout=timeout)
109
-
110
- if entry_point:
111
- func = interpreter.env_stack[0].get(entry_point)
112
- if not func:
113
- raise NameError(f"Function '{entry_point}' not found in the code")
114
- args = args or ()
115
- kwargs = kwargs or {}
116
- if isinstance(func, AsyncFunction) or asyncio.iscoroutinefunction(func):
117
- # Expect a tuple (result, local_vars) from AsyncFunction
118
- execution_result = await event_loop_manager.run_task(func(*args, **kwargs), timeout=timeout)
119
- if isinstance(execution_result, tuple) and len(execution_result) == 2:
120
- result, local_vars = execution_result
121
- else:
122
- result, local_vars = execution_result, {}
123
- elif isinstance(func, Function):
124
- result = await func(*args, **kwargs)
125
- local_vars = {} # Non-async functions don't yet support local var return
126
- else:
127
- result = func(*args, **kwargs)
128
- if asyncio.iscoroutine(result):
129
- result = await event_loop_manager.run_task(result, timeout=timeout)
130
- local_vars = {}
131
- if asyncio.iscoroutine(result):
132
- result = await event_loop_manager.run_task(result, timeout=timeout)
133
- else:
134
- result = await interpreter.execute_async(ast_tree)
135
- local_vars = {k: v for k, v in interpreter.env_stack[-1].items() if not k.startswith('__')}
136
-
137
- # Filter out internal variables if not already filtered
138
- filtered_local_vars = local_vars if local_vars else {}
139
- if not entry_point: # Apply filtering only for module-level execution
140
- filtered_local_vars = {k: v for k, v in local_vars.items() if not k.startswith('__')}
141
-
142
- return AsyncExecutionResult(
143
- result=result,
144
- error=None,
145
- execution_time=time.time() - start_time,
146
- local_variables=filtered_local_vars
147
- )
148
- except asyncio.TimeoutError as e:
149
- return AsyncExecutionResult(
150
- result=None,
151
- error=f'TimeoutError: Execution exceeded {timeout} seconds',
152
- execution_time=time.time() - start_time
153
- )
154
- except WrappedException as e:
155
- return AsyncExecutionResult(
156
- result=None,
157
- error=str(e),
158
- execution_time=time.time() - start_time
159
- )
160
- except Exception as e:
161
- error_type = type(getattr(e, 'original_exception', e)).__name__
162
- error_msg = f'{error_type}: {str(e)}'
163
- if hasattr(e, 'lineno') and hasattr(e, 'col_offset'):
164
- error_msg += f' at line {e.lineno}, col {e.col_offset}'
165
- return AsyncExecutionResult(
166
- result=None,
167
- error=error_msg,
168
- execution_time=time.time() - start_time
169
- )
170
- finally:
171
- await event_loop_manager.cleanup()
172
-
173
- def interpret_ast(ast_tree: ast.AST, allowed_modules: List[str], source: str = "", restrict_os: bool = False, namespace: Optional[Dict[str, Any]] = None) -> Any:
174
- ast_tree = optimize_ast(ast_tree)
175
- event_loop_manager = ControlledEventLoop()
176
-
177
- # Remove asyncio from namespace
178
- safe_namespace = namespace.copy() if namespace else {}
179
- safe_namespace.pop('asyncio', None)
180
-
181
- interpreter = ASTInterpreter(allowed_modules=allowed_modules, source=source, restrict_os=restrict_os, namespace=safe_namespace)
182
-
183
- async def run_interpreter():
184
- loop = await event_loop_manager.get_loop()
185
- interpreter.loop = loop
186
- result = await interpreter.visit(ast_tree, wrap_exceptions=True)
187
- return result
188
-
189
- try:
190
- loop = asyncio.new_event_loop()
191
- asyncio.set_event_loop(loop)
192
- interpreter.loop = loop
193
- result = loop.run_until_complete(run_interpreter())
194
- return result
195
- finally:
196
- if not loop.is_closed():
197
- loop.close()
198
-
199
- def interpret_code(source_code: str, allowed_modules: List[str], restrict_os: bool = False, namespace: Optional[Dict[str, Any]] = None) -> Any:
200
- dedented_source = textwrap.dedent(source_code).strip()
201
- tree: ast.AST = ast.parse(dedented_source)
202
- return interpret_ast(tree, allowed_modules, source=dedented_source, restrict_os=restrict_os, namespace=namespace)