Coverage for aipyapp/exec/python/executor.py: 21%
85 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
4import sys
5import json
6import traceback
7from io import StringIO
9from loguru import logger
11from .mod_obj import ObjectImporter
12from .mod_dict import DictModuleImporter
14INIT_IMPORTS = """
15import os
16import re
17import sys
18import json
19import time
20import random
21import traceback
22"""
24def is_json_serializable(obj):
25 try:
26 json.dumps(obj, ensure_ascii=False, default=str)
27 return True
28 except (TypeError, OverflowError):
29 return False
31def diff_dicts(dict1, dict2):
32 diff = {}
33 for key, value in dict1.items():
34 if key not in dict2:
35 diff[key] = value
36 continue
38 try:
39 if value != dict2[key]:
40 diff[key] = value
41 except Exception:
42 pass
43 return diff
45class PythonExecutor():
46 name = 'python'
48 def __init__(self, runtime):
49 self.runtime = runtime
50 self.log = logger.bind(src='PythonExecutor')
51 self._globals = {'__name__': '__main__', 'input': self.runtime.input}
52 self.block_importer = DictModuleImporter()
53 self.runtime_importer = ObjectImporter({'utils': runtime})
54 exec(INIT_IMPORTS, self._globals)
56 def __repr__(self):
57 return "<PythonExecutor>"
59 @property
60 def globals(self):
61 return self._globals
63 def __call__(self, block):
64 result = {}
65 try:
66 co = compile(block.code, block.abs_path or block.name, 'exec')
67 except SyntaxError as e:
68 result['errstr'] = f"Syntax error: {str(e)}"
69 result['traceback'] = traceback.format_exc()
70 return result
72 runtime = self.runtime
73 old_stdout, old_stderr = sys.stdout, sys.stderr
74 captured_stdout = StringIO()
75 captured_stderr = StringIO()
76 sys.stdout, sys.stderr = captured_stdout, captured_stderr
77 gs = self._globals.copy()
78 runtime.start_block(block)
79 try:
80 with self.block_importer, self.runtime_importer:
81 exec(co, gs)
82 self.block_importer.add_module(block.name, co)
83 except (SystemExit, Exception) as e:
84 self.runtime.set_state(success=False, error=str(e))
85 self.log.error(f"Error in code block {block.name}: {str(e)}")
86 result['errstr'] = str(e)
87 result['traceback'] = traceback.format_exc()
88 finally:
89 sys.stdout = old_stdout
90 sys.stderr = old_stderr
92 s = captured_stdout.getvalue().strip()
93 if s: result['stdout'] = s if is_json_serializable(s) else '<filtered: cannot json-serialize>'
94 s = captured_stderr.getvalue().strip()
95 if s: result['stderr'] = s if is_json_serializable(s) else '<filtered: cannot json-serialize>'
97 vars = runtime.current_state
98 if vars:
99 result['__state__'] = self.filter_result(vars)
101 return result
103 def filter_result(self, vars):
104 if isinstance(vars, dict):
105 ret = {}
106 for key in vars.keys():
107 if key in self.runtime.envs:
108 ret[key] = '<masked>'
109 else:
110 ret[key] = self.filter_result(vars[key])
111 elif isinstance(vars, list):
112 ret = [self.filter_result(v) for v in vars]
113 else:
114 ret = vars if is_json_serializable(vars) else '<filtered>'
115 return ret