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,365 @@
1
+ """
2
+ Module providing a high-level API for managing compiler.yml configurations.
3
+ This module offers functions to create, retrieve, update, and delete compiler configurations
4
+ through a simple API interface that can be used in web services or other applications.
5
+ """
6
+
7
+ import os
8
+ import yaml
9
+ from typing import Dict, List, Any, Optional, Tuple, Union
10
+ from pathlib import Path
11
+
12
+ from autocoder.compilers.compiler_config_manager import CompilerConfigManager, get_compiler_config_manager
13
+
14
+
15
+ class CompilerConfigAPI:
16
+ """
17
+ A high-level API for managing compiler configurations through a service interface.
18
+ This class simplifies interacting with compiler.yml files and provides standardized
19
+ response formats suitable for API endpoints.
20
+ """
21
+
22
+ def __init__(self, config_path: Optional[str] = None):
23
+ """
24
+ Initialize the CompilerConfigAPI.
25
+
26
+ Args:
27
+ config_path (Optional[str]): Path to the compiler.yml file.
28
+ If None, will use .auto-coder/projects/compiler.yml.
29
+ """
30
+ self.config_manager = get_compiler_config_manager(config_path)
31
+
32
+ def create_compiler(self,
33
+ name: str,
34
+ compiler_type: str,
35
+ triggers: List[str],
36
+ working_dir: str,
37
+ command: str,
38
+ args: List[str],
39
+ extract_regex: Optional[str] = None) -> Dict[str, Any]:
40
+ """
41
+ Create a new compiler configuration.
42
+
43
+ Args:
44
+ name (str): Name of the compiler.
45
+ compiler_type (str): Type of the compiler (e.g., 'frontend', 'backend').
46
+ working_dir (str): Working directory for the compiler.
47
+ command (str): The command to run.
48
+ args (List[str]): List of arguments for the command.
49
+ extract_regex (Optional[str]): Regular expression to extract error information.
50
+
51
+ Returns:
52
+ Dict[str, Any]: API response with status and result information.
53
+ """
54
+ try:
55
+ # Validate inputs
56
+ if not name or not name.strip():
57
+ return self._error_response("Compiler name cannot be empty")
58
+
59
+ if not command or not command.strip():
60
+ return self._error_response("Command cannot be empty")
61
+
62
+ # Create compiler config
63
+ compiler_config = self.config_manager.create_compiler_config(
64
+ name=name,
65
+ type=compiler_type,
66
+ working_dir=working_dir,
67
+ command=command,
68
+ args=args,
69
+ triggers=triggers,
70
+ extract_regex=extract_regex
71
+ )
72
+
73
+ # Add it to the configuration
74
+ success = self.config_manager.add_compiler(compiler_config)
75
+
76
+ if success:
77
+ return {
78
+ "status": "success",
79
+ "message": f"Compiler '{name}' created successfully",
80
+ "data": compiler_config
81
+ }
82
+ else:
83
+ return self._error_response(f"Failed to create compiler '{name}'")
84
+
85
+ except ValueError as e:
86
+ return self._error_response(str(e))
87
+ except Exception as e:
88
+ return self._error_response(f"Unexpected error: {str(e)}")
89
+
90
+ def get_compiler(self, name: str) -> Dict[str, Any]:
91
+ """
92
+ Get a specific compiler configuration.
93
+
94
+ Args:
95
+ name (str): Name of the compiler to retrieve.
96
+
97
+ Returns:
98
+ Dict[str, Any]: API response with status and compiler configuration.
99
+ """
100
+ try:
101
+ compiler_config = self.config_manager.get_compiler_by_name(name)
102
+
103
+ if compiler_config:
104
+ return {
105
+ "status": "success",
106
+ "data": compiler_config
107
+ }
108
+ else:
109
+ return self._error_response(f"Compiler '{name}' not found", status_code=404)
110
+
111
+ except FileNotFoundError:
112
+ return self._error_response("Configuration file not found", status_code=404)
113
+ except Exception as e:
114
+ return self._error_response(f"Unexpected error: {str(e)}")
115
+
116
+ def list_compilers(self) -> Dict[str, Any]:
117
+ """
118
+ List all compiler configurations.
119
+
120
+ Returns:
121
+ Dict[str, Any]: API response with status and list of compilers.
122
+ """
123
+ try:
124
+ compilers = self.config_manager.get_all_compilers()
125
+
126
+ return {
127
+ "status": "success",
128
+ "data": compilers,
129
+ "count": len(compilers)
130
+ }
131
+
132
+ except FileNotFoundError:
133
+ # Return empty list if file doesn't exist
134
+ return {
135
+ "status": "success",
136
+ "data": [],
137
+ "count": 0
138
+ }
139
+ except Exception as e:
140
+ return self._error_response(f"Unexpected error: {str(e)}")
141
+
142
+ def update_compiler(self,
143
+ name: str,
144
+ compiler_type: Optional[str] = None,
145
+ working_dir: Optional[str] = None,
146
+ command: Optional[str] = None,
147
+ args: Optional[List[str]] = None,
148
+ triggers: Optional[List[str]] = None,
149
+ extract_regex: Optional[str] = None) -> Dict[str, Any]:
150
+ """
151
+ Update an existing compiler configuration.
152
+
153
+ Args:
154
+ name (str): Name of the compiler to update.
155
+ compiler_type (Optional[str]): Type of the compiler.
156
+ working_dir (Optional[str]): Working directory for the compiler.
157
+ command (Optional[str]): The command to run.
158
+ args (Optional[List[str]]): List of arguments for the command.
159
+ extract_regex (Optional[str]): Regular expression to extract error information.
160
+
161
+ Returns:
162
+ Dict[str, Any]: API response with status and result information.
163
+ """
164
+ try:
165
+ # Get existing compiler
166
+ existing_config = self.config_manager.get_compiler_by_name(name)
167
+ if not existing_config:
168
+ return self._error_response(f"Compiler '{name}' not found", status_code=404)
169
+
170
+ # Create updated config by merging with existing values
171
+ updated_config = existing_config.copy()
172
+
173
+ if compiler_type is not None:
174
+ updated_config["type"] = compiler_type
175
+
176
+ if working_dir is not None:
177
+ updated_config["working_dir"] = working_dir
178
+
179
+ if command is not None:
180
+ updated_config["command"] = command
181
+
182
+ if args is not None:
183
+ updated_config["args"] = args
184
+
185
+ if extract_regex is not None:
186
+ updated_config["extract_regex"] = extract_regex
187
+
188
+ if triggers is not None:
189
+ updated_config["triggers"] = triggers
190
+
191
+ # Update the configuration
192
+ success = self.config_manager.update_compiler(name, updated_config)
193
+
194
+ if success:
195
+ return {
196
+ "status": "success",
197
+ "message": f"Compiler '{name}' updated successfully",
198
+ "data": updated_config
199
+ }
200
+ else:
201
+ return self._error_response(f"Failed to update compiler '{name}'")
202
+
203
+ except ValueError as e:
204
+ return self._error_response(str(e))
205
+ except FileNotFoundError:
206
+ return self._error_response("Configuration file not found", status_code=404)
207
+ except Exception as e:
208
+ return self._error_response(f"Unexpected error: {str(e)}")
209
+
210
+ def delete_compiler(self, name: str) -> Dict[str, Any]:
211
+ """
212
+ Delete a compiler configuration.
213
+
214
+ Args:
215
+ name (str): Name of the compiler to delete.
216
+
217
+ Returns:
218
+ Dict[str, Any]: API response with status and result information.
219
+ """
220
+ try:
221
+ success = self.config_manager.delete_compiler(name)
222
+
223
+ if success:
224
+ return {
225
+ "status": "success",
226
+ "message": f"Compiler '{name}' deleted successfully"
227
+ }
228
+ else:
229
+ return self._error_response(f"Failed to delete compiler '{name}'")
230
+
231
+ except ValueError as e:
232
+ return self._error_response(str(e))
233
+ except FileNotFoundError:
234
+ return self._error_response("Configuration file not found", status_code=404)
235
+ except Exception as e:
236
+ return self._error_response(f"Unexpected error: {str(e)}")
237
+
238
+ def initialize_config(self) -> Dict[str, Any]:
239
+ """
240
+ Initialize a default compiler configuration file if it doesn't exist.
241
+
242
+ Returns:
243
+ Dict[str, Any]: API response with status and result information.
244
+ """
245
+ try:
246
+ if self.config_manager.exists():
247
+ return {
248
+ "status": "success",
249
+ "message": "Configuration file already exists",
250
+ "data": self.config_manager.get_all_compilers()
251
+ }
252
+
253
+ success = self.config_manager.init_default_config()
254
+
255
+ if success:
256
+ return {
257
+ "status": "success",
258
+ "message": "Configuration file initialized with default settings",
259
+ "data": self.config_manager.get_all_compilers()
260
+ }
261
+ else:
262
+ return self._error_response("Failed to initialize configuration file")
263
+
264
+ except Exception as e:
265
+ return self._error_response(f"Unexpected error: {str(e)}")
266
+
267
+ def validate_config(self) -> Dict[str, Any]:
268
+ """
269
+ Validate the structure of the compiler.yml file.
270
+
271
+ Returns:
272
+ Dict[str, Any]: API response with validation results.
273
+ """
274
+ try:
275
+ if not self.config_manager.exists():
276
+ return self._error_response("Configuration file does not exist", status_code=404)
277
+
278
+ config = self.config_manager.read()
279
+
280
+ # Check if compilers section exists
281
+ if "compilers" not in config:
282
+ return self._error_response("Invalid configuration: missing 'compilers' section")
283
+
284
+ if not isinstance(config["compilers"], list):
285
+ return self._error_response("Invalid configuration: 'compilers' must be a list")
286
+
287
+ # Validate each compiler
288
+ validation_errors = []
289
+ for i, compiler in enumerate(config["compilers"]):
290
+ errors = self._validate_compiler_config(compiler, i)
291
+ validation_errors.extend(errors)
292
+
293
+ if validation_errors:
294
+ return {
295
+ "status": "error",
296
+ "message": "Configuration validation failed",
297
+ "errors": validation_errors
298
+ }
299
+
300
+ return {
301
+ "status": "success",
302
+ "message": "Configuration validation passed",
303
+ "data": config
304
+ }
305
+
306
+ except yaml.YAMLError:
307
+ return self._error_response("Invalid YAML format in configuration file")
308
+ except Exception as e:
309
+ return self._error_response(f"Unexpected error: {str(e)}")
310
+
311
+ def _validate_compiler_config(self, compiler: Dict[str, Any], index: int) -> List[str]:
312
+ """
313
+ Validate an individual compiler configuration.
314
+
315
+ Args:
316
+ compiler (Dict[str, Any]): The compiler configuration to validate.
317
+ index (int): Index of the compiler in the list (for error reporting).
318
+
319
+ Returns:
320
+ List[str]: List of validation error messages.
321
+ """
322
+ errors = []
323
+
324
+ # Check required fields
325
+ if "name" not in compiler or not compiler["name"]:
326
+ errors.append(f"Compiler at index {index}: missing or empty 'name' field")
327
+
328
+ if "command" not in compiler or not compiler["command"]:
329
+ errors.append(f"Compiler '{compiler.get('name', f'at index {index}')}': missing or empty 'command' field")
330
+
331
+ # Check args is a list
332
+ if "args" in compiler and not isinstance(compiler["args"], list):
333
+ errors.append(f"Compiler '{compiler.get('name', f'at index {index}')}': 'args' must be a list")
334
+
335
+ return errors
336
+
337
+ def _error_response(self, message: str, status_code: int = 400) -> Dict[str, Any]:
338
+ """
339
+ Create a standardized error response.
340
+
341
+ Args:
342
+ message (str): Error message.
343
+ status_code (int): HTTP-like status code.
344
+
345
+ Returns:
346
+ Dict[str, Any]: Standardized error response.
347
+ """
348
+ return {
349
+ "status": "error",
350
+ "message": message,
351
+ "code": status_code
352
+ }
353
+
354
+
355
+ def get_compiler_config_api(config_path: Optional[str] = None) -> CompilerConfigAPI:
356
+ """
357
+ Factory function to get a CompilerConfigAPI instance.
358
+
359
+ Args:
360
+ config_path (Optional[str]): Path to the compiler.yml file.
361
+
362
+ Returns:
363
+ CompilerConfigAPI: An instance of the CompilerConfigAPI.
364
+ """
365
+ return CompilerConfigAPI(config_path=config_path)
@@ -0,0 +1,305 @@
1
+ """
2
+ Module providing utilities for managing compiler.yml configuration files.
3
+ This module offers functions to read, write, add, update, delete, and query compiler configurations.
4
+ """
5
+
6
+ import os
7
+ import yaml
8
+ from typing import Dict, List, Any, Optional, Union
9
+ from pathlib import Path
10
+
11
+
12
+ class CompilerConfigManager:
13
+ """
14
+ A utility class for managing compiler configuration files (compiler.yml).
15
+ Provides methods to read, write, add, modify, delete, and query compiler configurations.
16
+ """
17
+
18
+ def __init__(self, config_path: Optional[str] = None):
19
+ """
20
+ Initialize the CompilerConfigManager.
21
+
22
+ Args:
23
+ config_path (Optional[str]): Path to the compiler.yml file.
24
+ If None, will use .auto-coder/projects/compiler.yml.
25
+ """
26
+ self.config_path = config_path or ".auto-coder/projects/compiler.yml"
27
+
28
+ def exists(self) -> bool:
29
+ """
30
+ Check if the configuration file exists.
31
+
32
+ Returns:
33
+ bool: True if the configuration file exists, False otherwise.
34
+ """
35
+ return os.path.exists(self.config_path)
36
+
37
+ def read(self) -> Dict[str, Any]:
38
+ """
39
+ Read the compiler configuration from YAML file.
40
+
41
+ Returns:
42
+ Dict[str, Any]: The compiler configuration as a dictionary.
43
+
44
+ Raises:
45
+ FileNotFoundError: If the configuration file doesn't exist.
46
+ yaml.YAMLError: If the YAML file is invalid.
47
+ """
48
+ if not self.exists():
49
+ raise FileNotFoundError(f"Configuration file not found: {self.config_path}")
50
+
51
+ with open(self.config_path, 'r', encoding='utf-8') as f:
52
+ config = yaml.safe_load(f)
53
+
54
+ # Ensure the config has a compilers section
55
+ if config is None:
56
+ config = {"compilers": []}
57
+ elif "compilers" not in config:
58
+ config["compilers"] = []
59
+
60
+ return config
61
+
62
+ def write(self, config: Dict[str, Any]) -> bool:
63
+ """
64
+ Write a compiler configuration to the YAML file.
65
+
66
+ Args:
67
+ config (Dict[str, Any]): The compiler configuration to write.
68
+
69
+ Returns:
70
+ bool: True if the write operation was successful, False otherwise.
71
+ """
72
+ try:
73
+ # Ensure directory exists
74
+ os.makedirs(os.path.dirname(os.path.abspath(self.config_path)), exist_ok=True)
75
+
76
+ with open(self.config_path, 'w', encoding='utf-8') as f:
77
+ yaml.dump(config, f, allow_unicode=True, default_flow_style=False)
78
+ return True
79
+ except Exception:
80
+ return False
81
+
82
+ def get_all_compilers(self) -> List[Dict[str, Any]]:
83
+ """
84
+ Get all compiler configurations from the file.
85
+
86
+ Returns:
87
+ List[Dict[str, Any]]: List of all compiler configurations.
88
+
89
+ Raises:
90
+ FileNotFoundError: If the configuration file doesn't exist.
91
+ """
92
+ config = self.read()
93
+ return config.get("compilers", [])
94
+
95
+ def get_compiler_by_name(self, name: str) -> Optional[Dict[str, Any]]:
96
+ """
97
+ Get a specific compiler configuration by name.
98
+
99
+ Args:
100
+ name (str): The name of the compiler to find.
101
+
102
+ Returns:
103
+ Optional[Dict[str, Any]]: The compiler configuration if found, None otherwise.
104
+
105
+ Raises:
106
+ FileNotFoundError: If the configuration file doesn't exist.
107
+ """
108
+ compilers = self.get_all_compilers()
109
+ for compiler in compilers:
110
+ if compiler.get("name") == name:
111
+ return compiler
112
+ return None
113
+
114
+ def add_compiler(self, compiler_config: Dict[str, Any]) -> bool:
115
+ """
116
+ Add a new compiler configuration to the file.
117
+
118
+ Args:
119
+ compiler_config (Dict[str, Any]): The compiler configuration to add.
120
+
121
+ Returns:
122
+ bool: True if the operation was successful, False otherwise.
123
+
124
+ Raises:
125
+ ValueError: If a compiler with the same name already exists.
126
+ FileNotFoundError: If the configuration file doesn't exist and couldn't be created.
127
+ """
128
+ name = compiler_config.get("name")
129
+ if not name:
130
+ raise ValueError("Compiler configuration must have a 'name' field")
131
+
132
+ try:
133
+ # Try to read existing config or create a new one
134
+ try:
135
+ config = self.read()
136
+ except FileNotFoundError:
137
+ config = {"compilers": []}
138
+
139
+ # Check if a compiler with this name already exists
140
+ for existing in config.get("compilers", []):
141
+ if existing.get("name") == name:
142
+ raise ValueError(f"A compiler with name '{name}' already exists")
143
+
144
+ # Add the new compiler config
145
+ config["compilers"].append(compiler_config)
146
+
147
+ # Write back to file
148
+ return self.write(config)
149
+ except Exception:
150
+ return False
151
+
152
+ def update_compiler(self, name: str, updated_config: Dict[str, Any]) -> bool:
153
+ """
154
+ Update an existing compiler configuration.
155
+
156
+ Args:
157
+ name (str): The name of the compiler to update.
158
+ updated_config (Dict[str, Any]): The updated compiler configuration.
159
+
160
+ Returns:
161
+ bool: True if the operation was successful, False otherwise.
162
+
163
+ Raises:
164
+ ValueError: If no compiler with the given name exists.
165
+ FileNotFoundError: If the configuration file doesn't exist.
166
+ """
167
+ try:
168
+ config = self.read()
169
+ found = False
170
+
171
+ # Update the compiler with matching name
172
+ for i, compiler in enumerate(config.get("compilers", [])):
173
+ if compiler.get("name") == name:
174
+ # Preserve the original name
175
+ updated_config["name"] = name
176
+ config["compilers"][i] = updated_config
177
+ found = True
178
+ break
179
+
180
+ if not found:
181
+ raise ValueError(f"No compiler with name '{name}' found")
182
+
183
+ # Write back to file
184
+ return self.write(config)
185
+ except Exception:
186
+ return False
187
+
188
+ def delete_compiler(self, name: str) -> bool:
189
+ """
190
+ Delete a compiler configuration from the file.
191
+
192
+ Args:
193
+ name (str): The name of the compiler to delete.
194
+
195
+ Returns:
196
+ bool: True if the operation was successful, False otherwise.
197
+
198
+ Raises:
199
+ ValueError: If no compiler with the given name exists.
200
+ FileNotFoundError: If the configuration file doesn't exist.
201
+ """
202
+ try:
203
+ config = self.read()
204
+ original_length = len(config.get("compilers", []))
205
+
206
+ # Filter out the compiler with the matching name
207
+ config["compilers"] = [
208
+ compiler for compiler in config.get("compilers", [])
209
+ if compiler.get("name") != name
210
+ ]
211
+
212
+ if len(config["compilers"]) == original_length:
213
+ raise ValueError(f"No compiler with name '{name}' found")
214
+
215
+ # Write back to file
216
+ return self.write(config)
217
+ except Exception:
218
+ return False
219
+
220
+ def create_compiler_config(self,
221
+ name: str,
222
+ type: str,
223
+ working_dir: str,
224
+ command: str,
225
+ args: List[str],
226
+ triggers: List[str],
227
+ extract_regex: Optional[str] = None) -> Dict[str, Any]:
228
+ """
229
+ Create a new compiler configuration dictionary.
230
+
231
+ Args:
232
+ name (str): Name of the compiler.
233
+ type (str): Type of the compiler (e.g., 'frontend', 'backend').
234
+ working_dir (str): Working directory for the compiler.
235
+ command (str): The command to run.
236
+ args (List[str]): List of arguments for the command.
237
+ extract_regex (Optional[str]): Regular expression to extract error information.
238
+
239
+ Returns:
240
+ Dict[str, Any]: A new compiler configuration dictionary.
241
+ """
242
+ compiler_config = {
243
+ "name": name,
244
+ "type": type,
245
+ "working_dir": working_dir,
246
+ "command": command,
247
+ "args": args,
248
+ "triggers": triggers
249
+ }
250
+
251
+ if extract_regex:
252
+ compiler_config["extract_regex"] = extract_regex
253
+
254
+ return compiler_config
255
+
256
+ def list_compiler_names(self) -> List[str]:
257
+ """
258
+ Get a list of all compiler names from the configuration.
259
+
260
+ Returns:
261
+ List[str]: List of compiler names.
262
+
263
+ Raises:
264
+ FileNotFoundError: If the configuration file doesn't exist.
265
+ """
266
+ compilers = self.get_all_compilers()
267
+ return [compiler.get("name") for compiler in compilers if compiler.get("name")]
268
+
269
+ def init_default_config(self) -> bool:
270
+ """
271
+ Initialize a default compiler configuration file if it doesn't exist.
272
+
273
+ Returns:
274
+ bool: True if initialization was successful, False otherwise.
275
+ """
276
+ if self.exists():
277
+ return True
278
+
279
+ default_config = {
280
+ "compilers": [
281
+ {
282
+ "name": "Frontend Build",
283
+ "type": "frontend",
284
+ "working_dir": "src/frontend",
285
+ "command": "npm",
286
+ "args": ["run", "build"],
287
+ "extract_regex": "(?P<severity>error|warning)\\s+in\\s+(?P<file>[^:]+):(?P<line>\\d+):(?P<column>\\d+)\\s*-\\s*(?P<message>.+)"
288
+ }
289
+ ]
290
+ }
291
+
292
+ return self.write(default_config)
293
+
294
+
295
+ def get_compiler_config_manager(config_path: Optional[str] = None) -> CompilerConfigManager:
296
+ """
297
+ Factory function to get a CompilerConfigManager instance.
298
+
299
+ Args:
300
+ config_path (Optional[str]): Path to the compiler.yml file.
301
+
302
+ Returns:
303
+ CompilerConfigManager: An instance of the CompilerConfigManager.
304
+ """
305
+ return CompilerConfigManager(config_path=config_path)