auto-coder 0.1.327__py3-none-any.whl → 0.1.329__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

@@ -0,0 +1,413 @@
1
+ """
2
+ Module for compiling/checking Python code.
3
+ This module provides functionality to check Python code syntax and imports.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import subprocess
9
+ import tempfile
10
+ import time
11
+ import re
12
+ from typing import Dict, List, Any, Optional, Tuple
13
+
14
+ from autocoder.compilers.base_compiler import BaseCompiler
15
+ from autocoder.compilers.models import (
16
+ CompilationError,
17
+ FileCompilationResult,
18
+ ProjectCompilationResult,
19
+ CompilationErrorPosition,
20
+ CompilationErrorSeverity
21
+ )
22
+
23
+ class PythonCompiler(BaseCompiler):
24
+ """
25
+ A class that provides compilation/checking functionality for Python code.
26
+ For Python, "compilation" means checking syntax and imports.
27
+ """
28
+
29
+ def __init__(self, verbose: bool = False):
30
+ """
31
+ Initialize the PythonCompiler.
32
+
33
+ Args:
34
+ verbose (bool): Whether to display verbose output.
35
+ """
36
+ super().__init__(verbose)
37
+
38
+ def get_supported_extensions(self) -> List[str]:
39
+ """
40
+ Get the list of file extensions supported by this compiler.
41
+
42
+ Returns:
43
+ List[str]: List of supported file extensions.
44
+ """
45
+ return ['.py']
46
+
47
+ def _check_dependencies(self) -> bool:
48
+ """
49
+ Check if required dependencies are installed.
50
+
51
+ Returns:
52
+ bool: True if all dependencies are available, False otherwise.
53
+ """
54
+ try:
55
+ # Check if python is installed
56
+ subprocess.run([sys.executable, "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
57
+ return True
58
+ except (subprocess.SubprocessError, FileNotFoundError):
59
+ return False
60
+
61
+ def _check_syntax(self, file_path: str) -> Dict[str, Any]:
62
+ """
63
+ Check Python file syntax without executing the code.
64
+
65
+ Args:
66
+ file_path (str): Path to the file to check.
67
+
68
+ Returns:
69
+ Dict[str, Any]: Dictionary containing syntax check results.
70
+ """
71
+ result = {
72
+ 'success': True,
73
+ 'errors': [],
74
+ 'error_count': 0,
75
+ 'warning_count': 0
76
+ }
77
+
78
+ try:
79
+ # Use Python's built-in compiler to check syntax
80
+ with open(file_path, 'r', encoding='utf-8') as f:
81
+ source = f.read()
82
+
83
+ # Check syntax using compile
84
+ try:
85
+ compile(source, file_path, 'exec')
86
+ except SyntaxError as e:
87
+ # Create error details
88
+ error = CompilationError(
89
+ message=str(e),
90
+ severity=CompilationErrorSeverity.ERROR,
91
+ position=CompilationErrorPosition(
92
+ line=e.lineno if e.lineno is not None else 1,
93
+ column=e.offset if e.offset is not None else 1
94
+ ),
95
+ file_path=file_path,
96
+ code="syntax-error"
97
+ )
98
+
99
+ # Try to get the source line
100
+ if e.text:
101
+ error.source = e.text
102
+
103
+ result['errors'].append(error)
104
+ result['error_count'] += 1
105
+ result['success'] = False
106
+
107
+ except Exception as e:
108
+ # Handle file reading errors
109
+ result['success'] = False
110
+ result['error_message'] = f"Error reading file: {str(e)}"
111
+
112
+ return result
113
+
114
+ def _check_imports(self, file_path: str) -> Dict[str, Any]:
115
+ """
116
+ Check if all imports in the Python file can be resolved.
117
+
118
+ Args:
119
+ file_path (str): Path to the file to check.
120
+
121
+ Returns:
122
+ Dict[str, Any]: Dictionary containing import check results.
123
+ """
124
+ result = {
125
+ 'success': True,
126
+ 'errors': [],
127
+ 'error_count': 0,
128
+ 'warning_count': 0
129
+ }
130
+
131
+ try:
132
+ # Use a temporary directory for PYTHONPATH to avoid polluting the real environment
133
+ with tempfile.TemporaryDirectory() as temp_dir:
134
+ # Get the directory of the file to check
135
+ file_dir = os.path.dirname(os.path.abspath(file_path))
136
+
137
+ # Create a temporary file to check imports
138
+ temp_file = os.path.join(temp_dir, "import_checker.py")
139
+ with open(temp_file, 'w', encoding='utf-8') as f:
140
+ f.write(f"""
141
+ import sys
142
+ import os
143
+ import importlib.util
144
+ import re
145
+
146
+ # Add file directory to path
147
+ sys.path.insert(0, "{file_dir}")
148
+
149
+ # Try to extract imports from the file
150
+ with open("{file_path}", "r", encoding="utf-8") as source_file:
151
+ source = source_file.read()
152
+
153
+ # Regular expressions to match import statements
154
+ import_patterns = [
155
+ r'^\\s*import\\s+([\\w\\.]+)', # import module
156
+ r'^\\s*from\\s+([\\w\\.]+)\\s+import', # from module import ...
157
+ ]
158
+
159
+ # Find all imports
160
+ imports = []
161
+ for line in source.split('\\n'):
162
+ for pattern in import_patterns:
163
+ match = re.match(pattern, line)
164
+ if match:
165
+ module_name = match.group(1)
166
+ # Handle relative imports
167
+ if module_name.startswith('.'):
168
+ continue # Skip relative imports
169
+ # Get the top-level package
170
+ top_package = module_name.split('.')[0]
171
+ if top_package not in imports:
172
+ imports.append(top_package)
173
+
174
+ # Check each import
175
+ for module_name in imports:
176
+ try:
177
+ importlib.import_module(module_name)
178
+ print(f"SUCCESS: {{module_name}}")
179
+ except ImportError as e:
180
+ print(f"ERROR: {{module_name}} - {{str(e)}}")
181
+ """)
182
+
183
+ # Run the import checker
184
+ cmd = [sys.executable, temp_file]
185
+ process = subprocess.run(
186
+ cmd,
187
+ stdout=subprocess.PIPE,
188
+ stderr=subprocess.PIPE,
189
+ text=True
190
+ )
191
+
192
+ # Parse the output to find import errors
193
+ line_num = 1 # Default line number for import errors
194
+ for line in process.stdout.splitlines():
195
+ if line.startswith("ERROR:"):
196
+ # Extract module name and error message
197
+ _, module_info = line.split(":", 1)
198
+ module_name, error_msg = module_info.strip().split(" - ", 1)
199
+
200
+ # Try to find the line number for this import
201
+ import_line = self._find_import_line(file_path, module_name)
202
+ if import_line > 0:
203
+ line_num = import_line
204
+
205
+ # Create error details
206
+ error = CompilationError(
207
+ message=f"Import error for module '{module_name}': {error_msg}",
208
+ severity=CompilationErrorSeverity.ERROR,
209
+ position=CompilationErrorPosition(line=line_num),
210
+ file_path=file_path,
211
+ code="import-error"
212
+ )
213
+
214
+ result['errors'].append(error)
215
+ result['error_count'] += 1
216
+
217
+ # Check if there were any errors
218
+ if result['error_count'] > 0:
219
+ result['success'] = False
220
+
221
+ except Exception as e:
222
+ # Handle any other errors
223
+ result['success'] = False
224
+ result['error_message'] = f"Error checking imports: {str(e)}"
225
+
226
+ return result
227
+
228
+ def _find_import_line(self, file_path: str, module_name: str) -> int:
229
+ """
230
+ Find the line number where a module is imported.
231
+
232
+ Args:
233
+ file_path (str): Path to the file.
234
+ module_name (str): Name of the module to find.
235
+
236
+ Returns:
237
+ int: Line number (1-based) or 0 if not found.
238
+ """
239
+ try:
240
+ with open(file_path, 'r', encoding='utf-8') as f:
241
+ for i, line in enumerate(f, 1):
242
+ if re.search(r'^\s*import\s+' + re.escape(module_name), line) or \
243
+ re.search(r'^\s*from\s+' + re.escape(module_name) + r'\s+import', line):
244
+ return i
245
+ return 0
246
+ except Exception:
247
+ return 0
248
+
249
+ def compile_file(self, file_path: str) -> Dict[str, Any]:
250
+ """
251
+ Compile (check) a single Python file.
252
+
253
+ Args:
254
+ file_path (str): Path to the file to compile.
255
+
256
+ Returns:
257
+ Dict[str, Any]: Compilation results.
258
+ """
259
+ if not os.path.exists(file_path):
260
+ return FileCompilationResult(
261
+ file_path=file_path,
262
+ success=False,
263
+ language="python",
264
+ error_message=f"File not found: {file_path}"
265
+ ).model_dump()
266
+
267
+ if not self.is_supported_file(file_path):
268
+ return FileCompilationResult(
269
+ file_path=file_path,
270
+ success=False,
271
+ language="python",
272
+ error_message=f"Unsupported file type: {file_path}"
273
+ ).model_dump()
274
+
275
+ start_time = time.time()
276
+
277
+ # First check syntax
278
+ syntax_result = self._check_syntax(file_path)
279
+
280
+ # Then check imports if syntax is correct
281
+ import_result = {'errors': [], 'error_count': 0, 'warning_count': 0}
282
+ if syntax_result['success']:
283
+ import_result = self._check_imports(file_path)
284
+
285
+ # Combine results
286
+ all_errors = syntax_result['errors'] + import_result['errors']
287
+ error_count = syntax_result['error_count'] + import_result['error_count']
288
+ warning_count = syntax_result['warning_count'] + import_result['warning_count']
289
+
290
+ # Calculate execution time
291
+ execution_time_ms = int((time.time() - start_time) * 1000)
292
+
293
+ # Create the final result
294
+ result = FileCompilationResult(
295
+ file_path=file_path,
296
+ success=(error_count == 0),
297
+ language="python",
298
+ errors=all_errors,
299
+ error_count=error_count,
300
+ warning_count=warning_count,
301
+ info_count=0,
302
+ execution_time_ms=execution_time_ms
303
+ )
304
+
305
+ return result.model_dump()
306
+
307
+ def compile_project(self, project_path: str) -> Dict[str, Any]:
308
+ """
309
+ Compile (check) a Python project.
310
+
311
+ Args:
312
+ project_path (str): Path to the project directory.
313
+
314
+ Returns:
315
+ Dict[str, Any]: Compilation results.
316
+ """
317
+ if not os.path.exists(project_path):
318
+ return ProjectCompilationResult(
319
+ project_path=project_path,
320
+ success=False,
321
+ total_files=0,
322
+ error_message=f"Project directory not found: {project_path}"
323
+ ).model_dump()
324
+
325
+ # Find all Python files
326
+ python_files = []
327
+ for root, _, files in os.walk(project_path):
328
+ for file in files:
329
+ if file.endswith('.py'):
330
+ python_files.append(os.path.join(root, file))
331
+
332
+ if not python_files:
333
+ return ProjectCompilationResult(
334
+ project_path=project_path,
335
+ success=True,
336
+ total_files=0,
337
+ file_results={}
338
+ ).model_dump()
339
+
340
+ # Compile each file
341
+ file_results = {}
342
+ total_errors = 0
343
+ total_warnings = 0
344
+ files_with_errors = 0
345
+
346
+ for file_path in python_files:
347
+ file_result = self.compile_file(file_path)
348
+ file_results[file_path] = file_result
349
+
350
+ if file_result['error_count'] > 0:
351
+ files_with_errors += 1
352
+ total_errors += file_result['error_count']
353
+
354
+ total_warnings += file_result['warning_count']
355
+
356
+ # Create the project result
357
+ result = ProjectCompilationResult(
358
+ project_path=project_path,
359
+ success=(total_errors == 0),
360
+ total_files=len(python_files),
361
+ files_with_errors=files_with_errors,
362
+ total_errors=total_errors,
363
+ total_warnings=total_warnings,
364
+ total_infos=0,
365
+ file_results={p: FileCompilationResult(**r) for p, r in file_results.items()}
366
+ )
367
+
368
+ return result.model_dump()
369
+
370
+ def format_compile_result(self, compile_result: Dict[str, Any]) -> str:
371
+ """
372
+ Format compilation results into a human-readable string.
373
+
374
+ Args:
375
+ compile_result (Dict[str, Any]): The compilation result dictionary.
376
+
377
+ Returns:
378
+ str: A formatted string representation of the compilation results.
379
+ """
380
+ if 'project_path' in compile_result:
381
+ # This is a project result
382
+ return ProjectCompilationResult(**compile_result).to_str()
383
+ else:
384
+ # This is a file result
385
+ return FileCompilationResult(**compile_result).to_str()
386
+
387
+ def compile_python_file(file_path: str, verbose: bool = False) -> Dict[str, Any]:
388
+ """
389
+ Utility function to compile a single Python file.
390
+
391
+ Args:
392
+ file_path (str): Path to the file to compile.
393
+ verbose (bool): Whether to display verbose output.
394
+
395
+ Returns:
396
+ Dict[str, Any]: A dictionary containing compilation results.
397
+ """
398
+ compiler = PythonCompiler(verbose=verbose)
399
+ return compiler.compile_file(file_path)
400
+
401
+ def compile_python_project(project_path: str, verbose: bool = False) -> Dict[str, Any]:
402
+ """
403
+ Utility function to compile a Python project.
404
+
405
+ Args:
406
+ project_path (str): Path to the project directory.
407
+ verbose (bool): Whether to display verbose output.
408
+
409
+ Returns:
410
+ Dict[str, Any]: A dictionary containing compilation results.
411
+ """
412
+ compiler = PythonCompiler(verbose=verbose)
413
+ return compiler.compile_project(project_path)