praisonaiagents 0.0.23__py3-none-any.whl → 0.0.24__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.
@@ -0,0 +1,423 @@
1
+ """Tools for Python code execution and analysis.
2
+
3
+ Usage:
4
+ from praisonaiagents.tools import python_tools
5
+ result = python_tools.execute_code("print('Hello, World!')")
6
+
7
+ or
8
+ from praisonaiagents.tools import execute_code, analyze_code, format_code
9
+ result = execute_code("print('Hello, World!')")
10
+ """
11
+
12
+ import logging
13
+ from typing import Dict, List, Optional, Any
14
+ from importlib import util
15
+ import io
16
+ from contextlib import redirect_stdout, redirect_stderr
17
+ import traceback
18
+
19
+ class PythonTools:
20
+ """Tools for Python code execution and analysis."""
21
+
22
+ def __init__(self):
23
+ """Initialize PythonTools."""
24
+ self._check_dependencies()
25
+
26
+ def _check_dependencies(self):
27
+ """Check if required packages are installed."""
28
+ missing = []
29
+ for package in ['black', 'pylint', 'autopep8']:
30
+ if util.find_spec(package) is None:
31
+ missing.append(package)
32
+
33
+ if missing:
34
+ raise ImportError(
35
+ f"Required packages not available. Please install: {', '.join(missing)}\n"
36
+ f"Run: pip install {' '.join(missing)}"
37
+ )
38
+
39
+ def execute_code(
40
+ self,
41
+ code: str,
42
+ globals_dict: Optional[Dict[str, Any]] = None,
43
+ locals_dict: Optional[Dict[str, Any]] = None,
44
+ timeout: int = 30,
45
+ max_output_size: int = 10000
46
+ ) -> Dict[str, Any]:
47
+ """Execute Python code safely."""
48
+ try:
49
+ # Set up execution environment
50
+ if globals_dict is None:
51
+ globals_dict = {'__builtins__': __builtins__}
52
+ if locals_dict is None:
53
+ locals_dict = {}
54
+
55
+ # Capture output
56
+ stdout_buffer = io.StringIO()
57
+ stderr_buffer = io.StringIO()
58
+
59
+ try:
60
+ # Compile code
61
+ compiled_code = compile(code, '<string>', 'exec')
62
+
63
+ # Execute with output capture
64
+ with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
65
+ exec(compiled_code, globals_dict, locals_dict)
66
+
67
+ # Get last expression value if any
68
+ import ast
69
+ tree = ast.parse(code)
70
+ if tree.body and isinstance(tree.body[-1], ast.Expr):
71
+ result = eval(
72
+ compile(ast.Expression(tree.body[-1].value), '<string>', 'eval'),
73
+ globals_dict,
74
+ locals_dict
75
+ )
76
+ else:
77
+ result = None
78
+
79
+ # Get output
80
+ stdout = stdout_buffer.getvalue()
81
+ stderr = stderr_buffer.getvalue()
82
+
83
+ # Truncate output if too large
84
+ if len(stdout) > max_output_size:
85
+ stdout = stdout[:max_output_size] + "...[truncated]"
86
+ if len(stderr) > max_output_size:
87
+ stderr = stderr[:max_output_size] + "...[truncated]"
88
+
89
+ return {
90
+ 'result': result,
91
+ 'stdout': stdout,
92
+ 'stderr': stderr,
93
+ 'success': True
94
+ }
95
+
96
+ except Exception as e:
97
+ error_msg = f"Error executing code: {str(e)}"
98
+ logging.error(error_msg)
99
+ return {
100
+ 'result': None,
101
+ 'stdout': stdout_buffer.getvalue(),
102
+ 'stderr': error_msg,
103
+ 'success': False
104
+ }
105
+
106
+ except Exception as e:
107
+ error_msg = f"Error executing code: {str(e)}"
108
+ logging.error(error_msg)
109
+ return {
110
+ 'result': None,
111
+ 'stdout': '',
112
+ 'stderr': error_msg,
113
+ 'success': False
114
+ }
115
+
116
+ def analyze_code(
117
+ self,
118
+ code: str
119
+ ) -> Optional[Dict[str, Any]]:
120
+ """Analyze Python code structure and quality.
121
+
122
+ Args:
123
+ code: Python code to analyze
124
+
125
+ Returns:
126
+ Dictionary with analysis results
127
+ """
128
+ try:
129
+ # Import ast only when needed
130
+ import ast
131
+
132
+ # Parse code
133
+ tree = ast.parse(code)
134
+
135
+ # Analyze structure
136
+ analysis = {
137
+ 'imports': [],
138
+ 'functions': [],
139
+ 'classes': [],
140
+ 'variables': [],
141
+ 'complexity': {
142
+ 'lines': len(code.splitlines()),
143
+ 'functions': 0,
144
+ 'classes': 0,
145
+ 'branches': 0
146
+ }
147
+ }
148
+
149
+ # Analyze nodes
150
+ for node in ast.walk(tree):
151
+ if isinstance(node, ast.Import):
152
+ for name in node.names:
153
+ analysis['imports'].append(name.name)
154
+ elif isinstance(node, ast.ImportFrom):
155
+ module = node.module or ''
156
+ for name in node.names:
157
+ analysis['imports'].append(f"{module}.{name.name}")
158
+ elif isinstance(node, ast.FunctionDef):
159
+ analysis['functions'].append({
160
+ 'name': node.name,
161
+ 'args': [
162
+ arg.arg for arg in node.args.args
163
+ ],
164
+ 'decorators': [
165
+ ast.unparse(d) for d in node.decorator_list
166
+ ]
167
+ })
168
+ analysis['complexity']['functions'] += 1
169
+ elif isinstance(node, ast.ClassDef):
170
+ analysis['classes'].append({
171
+ 'name': node.name,
172
+ 'bases': [
173
+ ast.unparse(base) for base in node.bases
174
+ ],
175
+ 'decorators': [
176
+ ast.unparse(d) for d in node.decorator_list
177
+ ]
178
+ })
179
+ analysis['complexity']['classes'] += 1
180
+ elif isinstance(node, ast.Assign):
181
+ for target in node.targets:
182
+ if isinstance(target, ast.Name):
183
+ analysis['variables'].append(target.id)
184
+ elif isinstance(node, (ast.If, ast.While, ast.For)):
185
+ analysis['complexity']['branches'] += 1
186
+
187
+ return analysis
188
+ except Exception as e:
189
+ error_msg = f"Error analyzing code: {str(e)}"
190
+ logging.error(error_msg)
191
+ return None
192
+
193
+ def format_code(
194
+ self,
195
+ code: str,
196
+ style: str = 'black',
197
+ line_length: int = 88
198
+ ) -> Optional[str]:
199
+ """Format Python code according to style guide.
200
+
201
+ Args:
202
+ code: Python code to format
203
+ style: Formatting style ('black' or 'pep8')
204
+ line_length: Maximum line length
205
+
206
+ Returns:
207
+ Formatted code
208
+ """
209
+ try:
210
+ if util.find_spec(style) is None:
211
+ error_msg = f"{style} package is not available. Please install it using: pip install {style}"
212
+ logging.error(error_msg)
213
+ return None
214
+
215
+ if style == 'black':
216
+ import black
217
+ return black.format_str(
218
+ code,
219
+ mode=black.FileMode(
220
+ line_length=line_length
221
+ )
222
+ )
223
+ else: # pep8
224
+ import autopep8
225
+ return autopep8.fix_code(
226
+ code,
227
+ options={
228
+ 'max_line_length': line_length
229
+ }
230
+ )
231
+ except Exception as e:
232
+ error_msg = f"Error formatting code: {str(e)}"
233
+ logging.error(error_msg)
234
+ return None
235
+
236
+ def lint_code(
237
+ self,
238
+ code: str
239
+ ) -> Optional[Dict[str, List[Dict[str, Any]]]]:
240
+ """Lint Python code for potential issues.
241
+
242
+ Args:
243
+ code: Python code to lint
244
+
245
+ Returns:
246
+ Dictionary with linting results
247
+ """
248
+ try:
249
+ if util.find_spec('pylint') is None:
250
+ error_msg = "pylint package is not available. Please install it using: pip install pylint"
251
+ logging.error(error_msg)
252
+ return None
253
+
254
+ # Import pylint only when needed
255
+ from pylint.reporters import JSONReporter
256
+ from pylint.lint.run import Run
257
+
258
+ # Create temporary file for pylint
259
+ import tempfile
260
+ with tempfile.NamedTemporaryFile(
261
+ mode='w',
262
+ suffix='.py',
263
+ delete=False
264
+ ) as f:
265
+ f.write(code)
266
+ temp_path = f.name
267
+
268
+ # Run pylint
269
+ reporter = JSONReporter()
270
+ Run(
271
+ [temp_path],
272
+ reporter=reporter,
273
+ exit=False
274
+ )
275
+
276
+ # Process results
277
+ results = {
278
+ 'errors': [],
279
+ 'warnings': [],
280
+ 'conventions': []
281
+ }
282
+
283
+ for msg in reporter.messages:
284
+ item = {
285
+ 'type': msg.category,
286
+ 'module': msg.module,
287
+ 'obj': msg.obj,
288
+ 'line': msg.line,
289
+ 'column': msg.column,
290
+ 'path': msg.path,
291
+ 'symbol': msg.symbol,
292
+ 'message': msg.msg,
293
+ 'message-id': msg.msg_id
294
+ }
295
+
296
+ if msg.category in ['error', 'fatal']:
297
+ results['errors'].append(item)
298
+ elif msg.category == 'warning':
299
+ results['warnings'].append(item)
300
+ else:
301
+ results['conventions'].append(item)
302
+
303
+ # Clean up
304
+ import os
305
+ os.unlink(temp_path)
306
+
307
+ return results
308
+ except Exception as e:
309
+ error_msg = f"Error linting code: {str(e)}"
310
+ logging.error(error_msg)
311
+ return {
312
+ 'errors': [],
313
+ 'warnings': [],
314
+ 'conventions': []
315
+ }
316
+
317
+ def disassemble_code(
318
+ self,
319
+ code: str
320
+ ) -> Optional[str]:
321
+ """Disassemble Python code to bytecode.
322
+
323
+ Args:
324
+ code: Python code to disassemble
325
+
326
+ Returns:
327
+ Disassembled bytecode as string
328
+ """
329
+ try:
330
+ # Import dis only when needed
331
+ import dis
332
+
333
+ # Compile code
334
+ compiled_code = compile(code, '<string>', 'exec')
335
+
336
+ # Capture disassembly
337
+ output = io.StringIO()
338
+ with redirect_stdout(output):
339
+ dis.dis(compiled_code)
340
+
341
+ return output.getvalue()
342
+ except Exception as e:
343
+ error_msg = f"Error disassembling code: {str(e)}"
344
+ logging.error(error_msg)
345
+ return None
346
+
347
+ # Create instance for direct function access
348
+ _python_tools = PythonTools()
349
+ execute_code = _python_tools.execute_code
350
+ analyze_code = _python_tools.analyze_code
351
+ format_code = _python_tools.format_code
352
+ lint_code = _python_tools.lint_code
353
+ disassemble_code = _python_tools.disassemble_code
354
+
355
+ if __name__ == "__main__":
356
+ print("\n==================================================")
357
+ print("PythonTools Demonstration")
358
+ print("==================================================\n")
359
+
360
+ print("1. Execute Python Code")
361
+ print("------------------------------")
362
+ code = """
363
+ def greet(name):
364
+ return f"Hello, {name}!"
365
+
366
+ result = greet("World")
367
+ print(result)
368
+ """
369
+ print("Code to execute:")
370
+ print(code)
371
+ print("\nOutput:")
372
+ result = execute_code(code)
373
+ print(result["stdout"])
374
+ print()
375
+
376
+ print("2. Format Python Code")
377
+ print("------------------------------")
378
+ code = """
379
+ def messy_function(x,y, z):
380
+ if x>0:
381
+ return y+z
382
+ else:
383
+ return y-z
384
+ """
385
+ print("Before formatting:")
386
+ print(code)
387
+ print("\nAfter formatting:")
388
+ result = format_code(code)
389
+ print(result)
390
+ print()
391
+
392
+ print("3. Lint Python Code")
393
+ print("------------------------------")
394
+ code = """
395
+ def bad_function():
396
+ unused_var = 42
397
+ return 'result'
398
+ """
399
+ print("Code to lint:")
400
+ print(code)
401
+ print("\nLinting results:")
402
+ result = lint_code(code)
403
+ print(result)
404
+ print()
405
+
406
+ print("4. Execute Code with Variables")
407
+ print("------------------------------")
408
+ code = """
409
+ x = 10
410
+ y = 20
411
+ result = x + y
412
+ print(f"The sum of {x} and {y} is {result}")
413
+ """
414
+ print("Code to execute:")
415
+ print(code)
416
+ print("\nOutput:")
417
+ result = execute_code(code)
418
+ print(result["stdout"])
419
+ print()
420
+
421
+ print("==================================================")
422
+ print("Demonstration Complete")
423
+ print("==================================================")
@@ -0,0 +1,278 @@
1
+ """Tools for executing shell commands safely.
2
+
3
+ This module provides a safe interface for executing shell commands with:
4
+ - Timeout control
5
+ - Output capture
6
+ - Error handling
7
+ - Resource limits
8
+ """
9
+
10
+ import subprocess
11
+ import shlex
12
+ import logging
13
+ import os
14
+ import time
15
+ from typing import Dict, List, Optional, Union
16
+
17
+ class ShellTools:
18
+ """Tools for executing shell commands safely."""
19
+
20
+ def __init__(self):
21
+ """Initialize ShellTools."""
22
+ self._check_dependencies()
23
+
24
+ def _check_dependencies(self):
25
+ """Check if required packages are installed."""
26
+ try:
27
+ import psutil
28
+ except ImportError:
29
+ raise ImportError(
30
+ "Required package not available. Please install: psutil\n"
31
+ "Run: pip install psutil"
32
+ )
33
+
34
+ def execute_command(
35
+ self,
36
+ command: str,
37
+ cwd: Optional[str] = None,
38
+ timeout: int = 30,
39
+ shell: bool = False,
40
+ env: Optional[Dict[str, str]] = None,
41
+ max_output_size: int = 10000
42
+ ) -> Dict[str, Union[str, int, bool]]:
43
+ """Execute a shell command safely.
44
+
45
+ Args:
46
+ command: Command to execute
47
+ cwd: Working directory
48
+ timeout: Maximum execution time in seconds
49
+ shell: Whether to run command in shell
50
+ env: Environment variables
51
+ max_output_size: Maximum output size in bytes
52
+
53
+ Returns:
54
+ Dictionary with execution results
55
+ """
56
+ try:
57
+ # Split command if not using shell
58
+ if not shell:
59
+ command = shlex.split(command)
60
+
61
+ # Set up process environment
62
+ process_env = os.environ.copy()
63
+ if env:
64
+ process_env.update(env)
65
+
66
+ # Start process
67
+ start_time = time.time()
68
+ process = subprocess.Popen(
69
+ command,
70
+ stdout=subprocess.PIPE,
71
+ stderr=subprocess.PIPE,
72
+ cwd=cwd,
73
+ shell=shell,
74
+ env=process_env,
75
+ text=True
76
+ )
77
+
78
+ try:
79
+ # Wait for process with timeout
80
+ stdout, stderr = process.communicate(timeout=timeout)
81
+
82
+ # Truncate output if too large
83
+ if len(stdout) > max_output_size:
84
+ stdout = stdout[:max_output_size] + "...[truncated]"
85
+ if len(stderr) > max_output_size:
86
+ stderr = stderr[:max_output_size] + "...[truncated]"
87
+
88
+ return {
89
+ 'stdout': stdout,
90
+ 'stderr': stderr,
91
+ 'exit_code': process.returncode,
92
+ 'success': process.returncode == 0,
93
+ 'execution_time': time.time() - start_time
94
+ }
95
+
96
+ except subprocess.TimeoutExpired:
97
+ # Kill process on timeout
98
+ parent = psutil.Process(process.pid)
99
+ children = parent.children(recursive=True)
100
+ for child in children:
101
+ child.kill()
102
+ parent.kill()
103
+
104
+ return {
105
+ 'stdout': '',
106
+ 'stderr': f'Command timed out after {timeout} seconds',
107
+ 'exit_code': -1,
108
+ 'success': False,
109
+ 'execution_time': timeout
110
+ }
111
+
112
+ except Exception as e:
113
+ error_msg = f"Error executing command: {str(e)}"
114
+ logging.error(error_msg)
115
+ return {
116
+ 'stdout': '',
117
+ 'stderr': error_msg,
118
+ 'exit_code': -1,
119
+ 'success': False,
120
+ 'execution_time': 0
121
+ }
122
+
123
+ def list_processes(self) -> List[Dict[str, Union[int, str, float]]]:
124
+ """List running processes with their details.
125
+
126
+ Returns:
127
+ List of process information dictionaries
128
+ """
129
+ try:
130
+ processes = []
131
+ for proc in psutil.process_iter(['pid', 'name', 'username', 'memory_percent', 'cpu_percent']):
132
+ try:
133
+ pinfo = proc.info
134
+ processes.append({
135
+ 'pid': pinfo['pid'],
136
+ 'name': pinfo['name'],
137
+ 'username': pinfo['username'],
138
+ 'memory_percent': round(pinfo['memory_percent'], 2),
139
+ 'cpu_percent': round(pinfo['cpu_percent'], 2)
140
+ })
141
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
142
+ pass
143
+ return processes
144
+ except Exception as e:
145
+ error_msg = f"Error listing processes: {str(e)}"
146
+ logging.error(error_msg)
147
+ return []
148
+
149
+ def kill_process(
150
+ self,
151
+ pid: int,
152
+ force: bool = False
153
+ ) -> Dict[str, Union[bool, str]]:
154
+ """Kill a process by its PID.
155
+
156
+ Args:
157
+ pid: Process ID to kill
158
+ force: Whether to force kill (-9)
159
+
160
+ Returns:
161
+ Dictionary with operation results
162
+ """
163
+ try:
164
+ process = psutil.Process(pid)
165
+ if force:
166
+ process.kill() # SIGKILL
167
+ else:
168
+ process.terminate() # SIGTERM
169
+
170
+ return {
171
+ 'success': True,
172
+ 'message': f'Process {pid} killed successfully'
173
+ }
174
+ except psutil.NoSuchProcess:
175
+ return {
176
+ 'success': False,
177
+ 'message': f'No process found with PID {pid}'
178
+ }
179
+ except psutil.AccessDenied:
180
+ return {
181
+ 'success': False,
182
+ 'message': f'Access denied to kill process {pid}'
183
+ }
184
+ except Exception as e:
185
+ error_msg = f"Error killing process: {str(e)}"
186
+ logging.error(error_msg)
187
+ return {
188
+ 'success': False,
189
+ 'message': error_msg
190
+ }
191
+
192
+ def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]:
193
+ """Get system information.
194
+
195
+ Returns:
196
+ Dictionary with system information
197
+ """
198
+ try:
199
+ cpu_percent = psutil.cpu_percent(interval=1)
200
+ memory = psutil.virtual_memory()
201
+ disk = psutil.disk_usage('/')
202
+
203
+ return {
204
+ 'cpu': {
205
+ 'percent': cpu_percent,
206
+ 'cores': psutil.cpu_count(),
207
+ 'physical_cores': psutil.cpu_count(logical=False)
208
+ },
209
+ 'memory': {
210
+ 'total': memory.total,
211
+ 'available': memory.available,
212
+ 'percent': memory.percent,
213
+ 'used': memory.used,
214
+ 'free': memory.free
215
+ },
216
+ 'disk': {
217
+ 'total': disk.total,
218
+ 'used': disk.used,
219
+ 'free': disk.free,
220
+ 'percent': disk.percent
221
+ },
222
+ 'boot_time': psutil.boot_time(),
223
+ 'platform': os.uname().sysname
224
+ }
225
+ except Exception as e:
226
+ error_msg = f"Error getting system info: {str(e)}"
227
+ logging.error(error_msg)
228
+ return {}
229
+
230
+ # Create instance for direct function access
231
+ _shell_tools = ShellTools()
232
+ execute_command = _shell_tools.execute_command
233
+ list_processes = _shell_tools.list_processes
234
+ kill_process = _shell_tools.kill_process
235
+ get_system_info = _shell_tools.get_system_info
236
+
237
+ if __name__ == "__main__":
238
+ # Example usage
239
+ print("\n==================================================")
240
+ print("ShellTools Demonstration")
241
+ print("==================================================\n")
242
+
243
+ # 1. Execute command
244
+ print("1. Command Execution")
245
+ print("------------------------------")
246
+ result = execute_command("ls -la")
247
+ print(f"Success: {result['success']}")
248
+ print(f"Output:\n{result['stdout']}")
249
+ if result['stderr']:
250
+ print(f"Errors:\n{result['stderr']}")
251
+ print(f"Execution time: {result['execution_time']:.2f}s")
252
+ print()
253
+
254
+ # 2. System Information
255
+ print("2. System Information")
256
+ print("------------------------------")
257
+ info = get_system_info()
258
+ print(f"CPU Usage: {info['cpu']['percent']}%")
259
+ print(f"Memory Usage: {info['memory']['percent']}%")
260
+ print(f"Disk Usage: {info['disk']['percent']}%")
261
+ print(f"Platform: {info['platform']}")
262
+ print()
263
+
264
+ # 3. Process List
265
+ print("3. Process List (top 5 by CPU)")
266
+ print("------------------------------")
267
+ processes = sorted(
268
+ list_processes(),
269
+ key=lambda x: x['cpu_percent'],
270
+ reverse=True
271
+ )[:5]
272
+ for proc in processes:
273
+ print(f"PID: {proc['pid']}, Name: {proc['name']}, CPU: {proc['cpu_percent']}%")
274
+ print()
275
+
276
+ print("\n==================================================")
277
+ print("Demonstration Complete")
278
+ print("==================================================")