pdd-cli 0.0.40__py3-none-any.whl → 0.0.41__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 pdd-cli might be problematic. Click here for more details.
- pdd/__init__.py +1 -1
- pdd/auto_update.py +73 -78
- pdd/bug_main.py +2 -2
- pdd/bug_to_unit_test.py +46 -38
- pdd/change.py +20 -13
- pdd/change_main.py +222 -162
- pdd/cli.py +111 -92
- pdd/cmd_test_main.py +48 -33
- pdd/crash_main.py +9 -8
- pdd/data/llm_model.csv +1 -1
- pdd/fix_verification_errors.py +13 -0
- pdd/fix_verification_main.py +2 -2
- pdd/logo_animation.py +455 -0
- pdd/process_csv_change.py +1 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +2 -1
- pdd/prompts/sync_analysis_LLM.prompt +82 -0
- pdd/sync_animation.py +643 -0
- pdd/sync_determine_operation.py +574 -0
- pdd/xml_tagger.py +15 -6
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/METADATA +3 -3
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/RECORD +25 -21
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.40.dist-info → pdd_cli-0.0.41.dist-info}/top_level.txt +0 -0
pdd/change_main.py
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
Module for handling the 'change' command, which modifies prompt files based on
|
|
3
|
+
change instructions, using code context.
|
|
4
|
+
|
|
5
|
+
Supports both single file changes and batch processing via CSV mode.
|
|
6
|
+
"""
|
|
4
7
|
import csv
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
5
10
|
from pathlib import Path
|
|
6
11
|
from typing import Optional, Tuple, Dict, Any, List
|
|
7
12
|
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
# Import Rich for pretty printing
|
|
16
|
+
from rich import print as rprint
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
|
|
8
19
|
# Use relative imports for internal modules
|
|
9
20
|
from .construct_paths import construct_paths
|
|
10
21
|
from .change import change as change_func
|
|
11
22
|
from .process_csv_change import process_csv_change
|
|
12
23
|
from .get_extension import get_extension
|
|
13
|
-
from . import DEFAULT_STRENGTH, DEFAULT_TIME
|
|
14
|
-
|
|
15
|
-
# Import Rich for pretty printing
|
|
16
|
-
from rich import print as rprint
|
|
17
|
-
from rich.panel import Panel
|
|
24
|
+
from . import DEFAULT_STRENGTH, DEFAULT_TIME
|
|
18
25
|
|
|
19
26
|
# Set up logging
|
|
20
27
|
logger = logging.getLogger(__name__)
|
|
21
28
|
# Ensure logger propagates messages to the root logger configured in the main CLI entry point
|
|
22
29
|
# If not configured elsewhere, uncomment the following lines:
|
|
23
|
-
# logging.basicConfig(level=logging.INFO,
|
|
30
|
+
# logging.basicConfig(level=logging.INFO,
|
|
31
|
+
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
24
32
|
# logger.setLevel(logging.DEBUG)
|
|
25
33
|
|
|
26
34
|
|
|
@@ -55,58 +63,64 @@ def change_main(
|
|
|
55
63
|
- float: Total cost of the operation.
|
|
56
64
|
- str: Name of the model used.
|
|
57
65
|
"""
|
|
58
|
-
logger.debug(
|
|
59
|
-
logger.debug(
|
|
60
|
-
logger.debug(
|
|
61
|
-
logger.debug(
|
|
62
|
-
logger.debug(
|
|
66
|
+
logger.debug("Starting change_main with use_csv=%s", use_csv)
|
|
67
|
+
logger.debug(" change_prompt_file: %s", change_prompt_file)
|
|
68
|
+
logger.debug(" input_code: %s", input_code)
|
|
69
|
+
logger.debug(" input_prompt_file: %s", input_prompt_file)
|
|
70
|
+
logger.debug(" output: %s", output)
|
|
63
71
|
|
|
64
72
|
# Retrieve global options from context
|
|
65
73
|
force: bool = ctx.obj.get("force", False)
|
|
66
74
|
quiet: bool = ctx.obj.get("quiet", False)
|
|
67
75
|
strength: float = ctx.obj.get("strength", DEFAULT_STRENGTH)
|
|
68
76
|
temperature: float = ctx.obj.get("temperature", 0.0)
|
|
69
|
-
time_budget: float = ctx.obj.get("time", DEFAULT_TIME)
|
|
70
|
-
# --- Get language and extension from context ---
|
|
77
|
+
time_budget: float = ctx.obj.get("time", DEFAULT_TIME)
|
|
78
|
+
# --- Get language and extension from context ---
|
|
71
79
|
# These are crucial for knowing the target code file types, especially in CSV mode
|
|
72
|
-
target_language: str = ctx.obj.get("language", "")
|
|
80
|
+
target_language: str = ctx.obj.get("language", "")
|
|
73
81
|
target_extension: Optional[str] = ctx.obj.get("extension", None)
|
|
74
82
|
|
|
75
83
|
result_message: str = ""
|
|
76
84
|
total_cost: float = 0.0
|
|
77
85
|
model_name: str = ""
|
|
78
86
|
success: bool = False
|
|
79
|
-
modified_prompts_list: List[Dict[str, str]] = []
|
|
87
|
+
modified_prompts_list: List[Dict[str, str]] = [] # For CSV mode
|
|
80
88
|
|
|
81
89
|
try:
|
|
82
90
|
# --- 1. Argument Validation ---
|
|
83
91
|
if not change_prompt_file or not input_code:
|
|
84
92
|
msg = "[bold red]Error:[/bold red] Both --change-prompt-file and --input-code arguments are required."
|
|
85
|
-
if not quiet:
|
|
93
|
+
if not quiet:
|
|
94
|
+
rprint(msg)
|
|
86
95
|
logger.error(msg)
|
|
87
96
|
return msg, 0.0, ""
|
|
88
97
|
|
|
89
98
|
# Handle trailing slashes in output path *before* using it in validation/construct_paths
|
|
90
|
-
original_output = output # Keep original for potential later use if needed
|
|
91
99
|
if output and isinstance(output, str) and output.endswith(('/', '\\')):
|
|
92
|
-
logger.debug(
|
|
100
|
+
logger.debug("Normalizing output path: %s", output)
|
|
93
101
|
output = os.path.normpath(output)
|
|
94
|
-
logger.debug(
|
|
102
|
+
logger.debug("Normalized output path: %s", output)
|
|
95
103
|
|
|
96
104
|
if use_csv:
|
|
97
105
|
if input_prompt_file:
|
|
98
106
|
msg = "[bold red]Error:[/bold red] --input-prompt-file should not be provided when using --csv mode."
|
|
99
|
-
if not quiet:
|
|
107
|
+
if not quiet:
|
|
108
|
+
rprint(msg)
|
|
100
109
|
logger.error(msg)
|
|
101
110
|
return msg, 0.0, ""
|
|
102
111
|
# Check if input_code is a directory *before* trying to use it
|
|
103
112
|
if not os.path.isdir(input_code):
|
|
104
|
-
msg = f"[bold red]Error:[/bold red] In CSV mode, --input-code ('{input_code}')
|
|
105
|
-
|
|
113
|
+
msg = (f"[bold red]Error:[/bold red] In CSV mode, --input-code ('{input_code}') "
|
|
114
|
+
"must be a valid directory.")
|
|
115
|
+
if not quiet:
|
|
116
|
+
rprint(msg)
|
|
106
117
|
logger.error(msg)
|
|
107
118
|
return msg, 0.0, ""
|
|
108
119
|
if not change_prompt_file.lower().endswith(".csv"):
|
|
109
|
-
|
|
120
|
+
logger.warning(
|
|
121
|
+
"Input change file '%s' does not end with .csv. Assuming it's a CSV.",
|
|
122
|
+
change_prompt_file
|
|
123
|
+
)
|
|
110
124
|
|
|
111
125
|
# Validate CSV header *before* calling construct_paths
|
|
112
126
|
logger.debug("Validating CSV header...")
|
|
@@ -118,44 +132,51 @@ def change_main(
|
|
|
118
132
|
header = reader.fieldnames
|
|
119
133
|
if header is None:
|
|
120
134
|
raise csv.Error("CSV file appears to be empty or header is missing.")
|
|
121
|
-
logger.debug(
|
|
135
|
+
logger.debug("CSV header found: %s", header)
|
|
122
136
|
required_columns = {'prompt_name', 'change_instructions'}
|
|
123
137
|
if not required_columns.issubset(header):
|
|
124
138
|
missing_columns = required_columns - set(header)
|
|
125
139
|
msg = "CSV file must contain 'prompt_name' and 'change_instructions' columns."
|
|
126
140
|
if missing_columns:
|
|
127
141
|
msg += f" Missing: {missing_columns}"
|
|
128
|
-
if not quiet:
|
|
142
|
+
if not quiet:
|
|
143
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
129
144
|
logger.error(msg)
|
|
130
145
|
return msg, 0.0, ""
|
|
131
146
|
logger.debug("CSV header validated successfully.")
|
|
132
147
|
except FileNotFoundError:
|
|
133
148
|
msg = f"CSV file not found: {change_prompt_file}"
|
|
134
|
-
if not quiet:
|
|
149
|
+
if not quiet:
|
|
150
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
135
151
|
logger.error(msg)
|
|
136
152
|
return msg, 0.0, ""
|
|
137
|
-
except csv.Error as
|
|
138
|
-
msg = f"Failed to read or validate CSV header: {
|
|
139
|
-
if not quiet:
|
|
140
|
-
|
|
153
|
+
except csv.Error as csv_error: # Catch specific CSV errors
|
|
154
|
+
msg = f"Failed to read or validate CSV header: {csv_error}"
|
|
155
|
+
if not quiet:
|
|
156
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
157
|
+
logger.error("CSV header validation error: %s", csv_error, exc_info=True)
|
|
141
158
|
return msg, 0.0, ""
|
|
142
|
-
except Exception as
|
|
143
|
-
msg = f"Failed to open or read CSV file '{change_prompt_file}': {
|
|
144
|
-
if not quiet:
|
|
145
|
-
|
|
159
|
+
except Exception as general_error: # Need to keep this broad exception for file errors
|
|
160
|
+
msg = f"Failed to open or read CSV file '{change_prompt_file}': {general_error}"
|
|
161
|
+
if not quiet:
|
|
162
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
163
|
+
logger.error("Error reading CSV file: %s", general_error, exc_info=True)
|
|
146
164
|
return msg, 0.0, ""
|
|
147
165
|
|
|
148
|
-
else:
|
|
166
|
+
else: # Non-CSV mode
|
|
149
167
|
if not input_prompt_file:
|
|
150
168
|
msg = "[bold red]Error:[/bold red] --input-prompt-file is required when not using --csv mode."
|
|
151
|
-
if not quiet:
|
|
169
|
+
if not quiet:
|
|
170
|
+
rprint(msg)
|
|
152
171
|
logger.error(msg)
|
|
153
172
|
return msg, 0.0, ""
|
|
154
173
|
if os.path.isdir(input_code):
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
msg = (f"[bold red]Error:[/bold red] In non-CSV mode, --input-code ('{input_code}') "
|
|
175
|
+
"must be a file path, not a directory.")
|
|
176
|
+
if not quiet:
|
|
177
|
+
rprint(msg)
|
|
178
|
+
logger.error(msg)
|
|
179
|
+
return msg, 0.0, ""
|
|
159
180
|
|
|
160
181
|
# --- 2. Construct Paths and Read Inputs (where applicable) ---
|
|
161
182
|
input_file_paths: Dict[str, str] = {}
|
|
@@ -173,7 +194,8 @@ def change_main(
|
|
|
173
194
|
input_file_paths["input_code"] = input_code
|
|
174
195
|
input_file_paths["input_prompt_file"] = input_prompt_file
|
|
175
196
|
|
|
176
|
-
logger.debug(
|
|
197
|
+
logger.debug("Calling construct_paths with inputs: %s and options: %s",
|
|
198
|
+
input_file_paths, command_options)
|
|
177
199
|
try:
|
|
178
200
|
input_strings, output_file_paths, language = construct_paths(
|
|
179
201
|
input_file_paths=input_file_paths,
|
|
@@ -182,13 +204,14 @@ def change_main(
|
|
|
182
204
|
command="change",
|
|
183
205
|
command_options=command_options,
|
|
184
206
|
)
|
|
185
|
-
logger.debug(
|
|
186
|
-
logger.debug(
|
|
187
|
-
logger.debug(
|
|
188
|
-
logger.debug(
|
|
189
|
-
except Exception as
|
|
190
|
-
msg = f"Error constructing paths: {
|
|
191
|
-
if not quiet:
|
|
207
|
+
logger.debug("construct_paths returned:")
|
|
208
|
+
logger.debug(" input_strings keys: %s", list(input_strings.keys()))
|
|
209
|
+
logger.debug(" output_file_paths: %s", output_file_paths)
|
|
210
|
+
logger.debug(" language: %s", language) # Language might be inferred or needed for defaults
|
|
211
|
+
except Exception as construct_error: # Need to keep for proper error handling
|
|
212
|
+
msg = f"Error constructing paths: {construct_error}"
|
|
213
|
+
if not quiet:
|
|
214
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
192
215
|
logger.error(msg, exc_info=True)
|
|
193
216
|
return msg, 0.0, ""
|
|
194
217
|
|
|
@@ -196,19 +219,23 @@ def change_main(
|
|
|
196
219
|
if use_csv:
|
|
197
220
|
logger.info("Running in CSV mode.")
|
|
198
221
|
# Determine language and extension for process_csv_change
|
|
199
|
-
csv_target_language = target_language or language or "python"
|
|
222
|
+
csv_target_language = target_language or language or "python" # Prioritize context language
|
|
200
223
|
try:
|
|
201
224
|
if target_extension:
|
|
202
225
|
extension = target_extension
|
|
203
|
-
logger.debug(
|
|
226
|
+
logger.debug("Using extension '%s' from context for CSV processing.", extension)
|
|
204
227
|
else:
|
|
205
228
|
extension = get_extension(csv_target_language)
|
|
206
|
-
logger.debug(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
229
|
+
logger.debug(
|
|
230
|
+
"Derived language '%s' and extension '%s' for CSV processing.",
|
|
231
|
+
csv_target_language, extension
|
|
232
|
+
)
|
|
233
|
+
except ValueError as value_error:
|
|
234
|
+
msg = f"Could not determine file extension for language '{csv_target_language}': {value_error}"
|
|
235
|
+
if not quiet:
|
|
236
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
237
|
+
logger.error(msg)
|
|
238
|
+
return msg, 0.0, ""
|
|
212
239
|
|
|
213
240
|
try:
|
|
214
241
|
# Call process_csv_change - this is the function mocked in CSV tests
|
|
@@ -216,8 +243,8 @@ def change_main(
|
|
|
216
243
|
csv_file=change_prompt_file,
|
|
217
244
|
strength=strength,
|
|
218
245
|
temperature=temperature,
|
|
219
|
-
time=time_budget,
|
|
220
|
-
code_directory=input_code,
|
|
246
|
+
time=time_budget, # Pass time_budget
|
|
247
|
+
code_directory=input_code, # Pass the directory path
|
|
221
248
|
language=csv_target_language,
|
|
222
249
|
extension=extension,
|
|
223
250
|
budget=budget,
|
|
@@ -225,11 +252,15 @@ def change_main(
|
|
|
225
252
|
#verbose=ctx.obj.get("verbose", False) # Removed based on TypeError in verification
|
|
226
253
|
)
|
|
227
254
|
# Process_csv_change should return cost and model name even on partial success/failure.
|
|
228
|
-
logger.info(
|
|
229
|
-
|
|
255
|
+
logger.info(
|
|
256
|
+
"process_csv_change returned: success=%s, cost=%s, model=%s",
|
|
257
|
+
success, total_cost, model_name
|
|
258
|
+
)
|
|
259
|
+
except Exception as csv_process_error: # Need to keep for proper error handling
|
|
230
260
|
# This catches errors within process_csv_change itself
|
|
231
|
-
msg = f"Error during CSV processing: {
|
|
232
|
-
if not quiet:
|
|
261
|
+
msg = f"Error during CSV processing: {csv_process_error}"
|
|
262
|
+
if not quiet:
|
|
263
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
233
264
|
logger.error(msg, exc_info=True)
|
|
234
265
|
# Even if the process fails, the tests expect the overall success message
|
|
235
266
|
result_message = "Multiple prompts have been updated."
|
|
@@ -238,22 +269,23 @@ def change_main(
|
|
|
238
269
|
|
|
239
270
|
# Always set the result message for CSV mode, regardless of internal success/failure of rows
|
|
240
271
|
result_message = "Multiple prompts have been updated."
|
|
241
|
-
logger.info(
|
|
272
|
+
logger.info("CSV processing complete. Result message: %s", result_message)
|
|
242
273
|
|
|
243
|
-
else:
|
|
274
|
+
else: # Non-CSV mode
|
|
244
275
|
logger.info("Running in single-file mode.")
|
|
245
276
|
change_prompt_content = input_strings.get("change_prompt_file")
|
|
246
277
|
input_code_content = input_strings.get("input_code")
|
|
247
278
|
input_prompt_content = input_strings.get("input_prompt_file")
|
|
248
279
|
|
|
249
280
|
if not all([change_prompt_content, input_code_content, input_prompt_content]):
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
281
|
+
missing = [k for k, v in {"change_prompt_file": change_prompt_content,
|
|
282
|
+
"input_code": input_code_content,
|
|
283
|
+
"input_prompt_file": input_prompt_content}.items() if not v]
|
|
284
|
+
msg = f"Failed to read content for required input files: {', '.join(missing)}"
|
|
285
|
+
if not quiet:
|
|
286
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
287
|
+
logger.error(msg)
|
|
288
|
+
return msg, 0.0, ""
|
|
257
289
|
|
|
258
290
|
try:
|
|
259
291
|
# Call the imported change function
|
|
@@ -263,15 +295,16 @@ def change_main(
|
|
|
263
295
|
input_prompt_content,
|
|
264
296
|
strength,
|
|
265
297
|
temperature,
|
|
266
|
-
time_budget,
|
|
298
|
+
time=time_budget,
|
|
267
299
|
budget=budget,
|
|
268
|
-
|
|
300
|
+
verbose=not quiet
|
|
269
301
|
)
|
|
270
302
|
success = True # Assume success if no exception
|
|
271
303
|
logger.info("Single prompt change successful.")
|
|
272
|
-
except Exception as
|
|
273
|
-
msg = f"Error during prompt modification: {
|
|
274
|
-
if not quiet:
|
|
304
|
+
except Exception as change_error: # Need to keep for proper error handling
|
|
305
|
+
msg = f"Error during prompt modification: {change_error}"
|
|
306
|
+
if not quiet:
|
|
307
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
275
308
|
logger.error(msg, exc_info=True)
|
|
276
309
|
return msg, 0.0, ""
|
|
277
310
|
|
|
@@ -280,11 +313,11 @@ def change_main(
|
|
|
280
313
|
output_path_obj: Optional[Path] = None
|
|
281
314
|
if output:
|
|
282
315
|
output_path_obj = Path(output).resolve()
|
|
283
|
-
logger.debug(
|
|
316
|
+
logger.debug("Resolved user specified output path: %s", output_path_obj)
|
|
284
317
|
elif not use_csv and "output_prompt_file" in output_file_paths:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
318
|
+
# Use default path from construct_paths for single file mode if no --output
|
|
319
|
+
output_path_obj = Path(output_file_paths["output_prompt_file"]).resolve()
|
|
320
|
+
logger.debug("Using default output path from construct_paths: %s", output_path_obj)
|
|
288
321
|
|
|
289
322
|
# Proceed with saving if CSV mode OR if non-CSV mode was successful
|
|
290
323
|
if use_csv or success:
|
|
@@ -294,9 +327,9 @@ def change_main(
|
|
|
294
327
|
|
|
295
328
|
if output_is_csv:
|
|
296
329
|
# Save all results to a single CSV file
|
|
297
|
-
logger.info(
|
|
330
|
+
logger.info("Saving batch results to CSV: %s", output_path_obj)
|
|
298
331
|
try:
|
|
299
|
-
output_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
output_path_obj.parent.mkdir(parents=True, exist_ok=True) # Uses Path.mkdir, OK here
|
|
300
333
|
with open(output_path_obj, 'w', newline='', encoding='utf-8') as outfile:
|
|
301
334
|
# Use the fieldnames expected by the tests
|
|
302
335
|
fieldnames = ['file_name', 'modified_prompt']
|
|
@@ -305,23 +338,30 @@ def change_main(
|
|
|
305
338
|
# Only write successfully processed prompts from the list
|
|
306
339
|
for item in modified_prompts_list:
|
|
307
340
|
# Ensure item has the expected keys before writing
|
|
308
|
-
if 'file_name' in item and 'modified_prompt' in item and
|
|
309
|
-
|
|
341
|
+
if ('file_name' in item and 'modified_prompt' in item and
|
|
342
|
+
item['modified_prompt'] is not None):
|
|
343
|
+
writer.writerow({
|
|
310
344
|
'file_name': item.get('file_name', 'unknown_prompt'),
|
|
311
345
|
'modified_prompt': item.get('modified_prompt', '')
|
|
312
346
|
})
|
|
313
347
|
else:
|
|
314
|
-
logger.warning(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if not quiet:
|
|
348
|
+
logger.warning(
|
|
349
|
+
"Skipping row in output CSV due to missing data or error: %s",
|
|
350
|
+
item.get('file_name')
|
|
351
|
+
)
|
|
352
|
+
if not quiet:
|
|
353
|
+
rprint(f"[green]Results saved to:[/green] {output_path_obj}")
|
|
354
|
+
except IOError as io_error:
|
|
355
|
+
msg = f"Failed to write output CSV '{output_path_obj}': {io_error}"
|
|
356
|
+
if not quiet:
|
|
357
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
319
358
|
logger.error(msg, exc_info=True)
|
|
320
359
|
# Return the standard CSV message but potentially with cost/model from successful rows
|
|
321
360
|
return result_message, total_cost, model_name or ""
|
|
322
|
-
except Exception as
|
|
323
|
-
msg = f"Unexpected error writing output CSV '{output_path_obj}': {
|
|
324
|
-
if not quiet:
|
|
361
|
+
except Exception as save_error: # Need to keep for proper error handling
|
|
362
|
+
msg = f"Unexpected error writing output CSV '{output_path_obj}': {save_error}"
|
|
363
|
+
if not quiet:
|
|
364
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
325
365
|
logger.error(msg, exc_info=True)
|
|
326
366
|
return result_message, total_cost, model_name or ""
|
|
327
367
|
|
|
@@ -336,89 +376,107 @@ def change_main(
|
|
|
336
376
|
output_dir = output_path_obj
|
|
337
377
|
# Check if it doesn't exist AND doesn't have a suffix (likely intended dir)
|
|
338
378
|
elif not output_path_obj.exists() and not output_path_obj.suffix:
|
|
339
|
-
|
|
340
|
-
else:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
379
|
+
output_dir = output_path_obj
|
|
380
|
+
else: # Assume it's a file path, use parent
|
|
381
|
+
output_dir = output_path_obj.parent
|
|
382
|
+
logger.warning(
|
|
383
|
+
"Output path '%s' is not a directory or CSV. "
|
|
384
|
+
"Saving individual files to parent directory: %s",
|
|
385
|
+
output_path_obj, output_dir
|
|
386
|
+
)
|
|
387
|
+
else: # No output specified, save to CWD
|
|
344
388
|
output_dir = Path.cwd()
|
|
345
389
|
|
|
346
|
-
logger.info(
|
|
390
|
+
logger.info("Saving individual modified prompts to directory: %s", output_dir)
|
|
347
391
|
try:
|
|
348
392
|
# Use os.makedirs to align with test mocks
|
|
349
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
350
|
-
except OSError as
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
393
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
394
|
+
except OSError as os_error:
|
|
395
|
+
msg = f"Failed to create output directory '{output_dir}': {os_error}"
|
|
396
|
+
if not quiet:
|
|
397
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
398
|
+
logger.error(msg, exc_info=True)
|
|
399
|
+
return result_message, total_cost, model_name or ""
|
|
355
400
|
|
|
356
401
|
saved_files_count = 0
|
|
357
402
|
for item in modified_prompts_list:
|
|
358
|
-
original_prompt_filename = item.get('file_name')
|
|
403
|
+
original_prompt_filename = item.get('file_name') # This should be the original prompt filename
|
|
359
404
|
modified_content = item.get('modified_prompt')
|
|
360
405
|
|
|
361
406
|
# Skip if modification failed for this file or data is missing
|
|
362
407
|
if not original_prompt_filename or modified_content is None:
|
|
363
|
-
logger.warning(
|
|
408
|
+
logger.warning(
|
|
409
|
+
"Skipping save for item due to missing data or error: %s",
|
|
410
|
+
item
|
|
411
|
+
)
|
|
364
412
|
continue
|
|
365
413
|
|
|
366
414
|
# Use original filename for the output file
|
|
367
415
|
individual_output_path = output_dir / Path(original_prompt_filename).name
|
|
368
416
|
|
|
369
417
|
if not force and individual_output_path.exists():
|
|
370
|
-
logger.warning(
|
|
371
|
-
|
|
418
|
+
logger.warning(
|
|
419
|
+
"Output file exists, skipping: %s. Use --force to overwrite.",
|
|
420
|
+
individual_output_path
|
|
421
|
+
)
|
|
422
|
+
if not quiet:
|
|
423
|
+
rprint(f"[yellow]Skipping existing file:[/yellow] {individual_output_path}")
|
|
372
424
|
continue
|
|
373
425
|
|
|
374
426
|
try:
|
|
375
|
-
logger.debug(
|
|
376
|
-
with open(individual_output_path, 'w', encoding='utf-8') as
|
|
377
|
-
|
|
378
|
-
logger.debug(
|
|
427
|
+
logger.debug("Attempting to save file to: %s", individual_output_path)
|
|
428
|
+
with open(individual_output_path, 'w', encoding='utf-8') as output_file:
|
|
429
|
+
output_file.write(modified_content)
|
|
430
|
+
logger.debug("Saved modified prompt to: %s", individual_output_path)
|
|
379
431
|
saved_files_count += 1
|
|
380
|
-
except IOError as
|
|
381
|
-
msg = f"Failed to write output file '{individual_output_path}': {
|
|
382
|
-
if not quiet:
|
|
432
|
+
except IOError as io_error:
|
|
433
|
+
msg = f"Failed to write output file '{individual_output_path}': {io_error}"
|
|
434
|
+
if not quiet:
|
|
435
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
436
|
+
logger.error(msg, exc_info=True)
|
|
437
|
+
# Continue saving others
|
|
438
|
+
except Exception as save_error: # Need to keep for proper error handling
|
|
439
|
+
msg = f"Unexpected error writing output file '{individual_output_path}': {save_error}"
|
|
440
|
+
if not quiet:
|
|
441
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
383
442
|
logger.error(msg, exc_info=True)
|
|
384
443
|
# Continue saving others
|
|
385
|
-
except Exception as e:
|
|
386
|
-
msg = f"Unexpected error writing output file '{individual_output_path}': {e}"
|
|
387
|
-
if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
388
|
-
logger.error(msg, exc_info=True)
|
|
389
|
-
# Continue saving others
|
|
390
444
|
|
|
391
|
-
logger.info(
|
|
392
|
-
if not quiet:
|
|
445
|
+
logger.info("Results saved as individual files in directory successfully")
|
|
446
|
+
if not quiet:
|
|
447
|
+
rprint(f"[green]Saved {saved_files_count} modified prompts to:[/green] {output_dir}")
|
|
393
448
|
|
|
394
|
-
else:
|
|
449
|
+
else: # Non-CSV mode saving
|
|
395
450
|
if not output_path_obj:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
451
|
+
# This case should ideally be caught by construct_paths, but double-check
|
|
452
|
+
msg = "Could not determine output path for modified prompt."
|
|
453
|
+
if not quiet:
|
|
454
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
455
|
+
logger.error(msg)
|
|
456
|
+
return msg, 0.0, ""
|
|
401
457
|
|
|
402
|
-
logger.info(
|
|
458
|
+
logger.info("Saving single modified prompt to: %s", output_path_obj)
|
|
403
459
|
try:
|
|
404
|
-
output_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
460
|
+
output_path_obj.parent.mkdir(parents=True, exist_ok=True) # Uses Path.mkdir, OK here
|
|
405
461
|
# Use open() for writing as expected by tests
|
|
406
|
-
with open(output_path_obj, 'w', encoding='utf-8') as
|
|
407
|
-
|
|
462
|
+
with open(output_path_obj, 'w', encoding='utf-8') as output_file:
|
|
463
|
+
output_file.write(result_message) # result_message contains the modified content here
|
|
408
464
|
if not quiet:
|
|
409
465
|
rprint(f"[green]Modified prompt saved to:[/green] {output_path_obj}")
|
|
410
466
|
rprint(Panel(result_message, title="Modified Prompt Content", expand=False))
|
|
411
467
|
# Update result_message for return value to be a status, not the full content
|
|
412
468
|
result_message = f"Modified prompt saved to {output_path_obj}"
|
|
413
469
|
|
|
414
|
-
except IOError as
|
|
415
|
-
msg = f"Failed to write output file '{output_path_obj}': {
|
|
416
|
-
if not quiet:
|
|
470
|
+
except IOError as io_error:
|
|
471
|
+
msg = f"Failed to write output file '{output_path_obj}': {io_error}"
|
|
472
|
+
if not quiet:
|
|
473
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
417
474
|
logger.error(msg, exc_info=True)
|
|
418
|
-
return msg, total_cost, model_name or ""
|
|
419
|
-
except Exception as
|
|
420
|
-
msg = f"Unexpected error writing output file '{output_path_obj}': {
|
|
421
|
-
if not quiet:
|
|
475
|
+
return msg, total_cost, model_name or "" # Return error after processing
|
|
476
|
+
except Exception as save_error: # Need to keep for proper error handling
|
|
477
|
+
msg = f"Unexpected error writing output file '{output_path_obj}': {save_error}"
|
|
478
|
+
if not quiet:
|
|
479
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
422
480
|
logger.error(msg, exc_info=True)
|
|
423
481
|
return msg, total_cost, model_name or ""
|
|
424
482
|
|
|
@@ -430,30 +488,32 @@ def change_main(
|
|
|
430
488
|
rprint(f"[bold]Total cost:[/bold] ${total_cost:.6f}")
|
|
431
489
|
if use_csv:
|
|
432
490
|
if output_is_csv:
|
|
433
|
-
|
|
491
|
+
rprint(f"[bold]Results saved to CSV:[/bold] {output_path_obj.resolve()}")
|
|
434
492
|
else:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
493
|
+
# Re-calculate output_dir in case it wasn't set earlier (e.g., no output specified)
|
|
494
|
+
final_output_dir = Path(output).resolve() if output and Path(output).resolve().is_dir() else Path.cwd()
|
|
495
|
+
if output and not final_output_dir.is_dir(): # Handle case where output was file-like
|
|
496
|
+
# Use the previously calculated output_dir if available
|
|
497
|
+
final_output_dir = output_dir if 'output_dir' in locals() else Path(output).resolve().parent
|
|
498
|
+
rprint(f"[bold]Results saved as individual files in directory:[/bold] {final_output_dir}")
|
|
499
|
+
|
|
500
|
+
except FileNotFoundError as file_error:
|
|
501
|
+
msg = f"Input file not found: {file_error}"
|
|
502
|
+
if not quiet:
|
|
503
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
504
|
+
logger.error(msg, exc_info=True)
|
|
505
|
+
return msg, 0.0, ""
|
|
506
|
+
except NotADirectoryError as dir_error:
|
|
507
|
+
msg = f"Expected a directory but found a file, or vice versa: {dir_error}"
|
|
508
|
+
if not quiet:
|
|
509
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
446
510
|
logger.error(msg, exc_info=True)
|
|
447
511
|
return msg, 0.0, ""
|
|
448
|
-
except
|
|
449
|
-
msg = f"Expected a directory but found a file, or vice versa: {e}"
|
|
450
|
-
if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
451
|
-
logger.error(msg, exc_info=True)
|
|
452
|
-
return msg, 0.0, ""
|
|
453
|
-
except Exception as e:
|
|
512
|
+
except Exception as general_error: # Need to keep for proper error handling
|
|
454
513
|
# Catch-all for truly unexpected errors during the main flow
|
|
455
|
-
msg = f"An unexpected error occurred: {
|
|
456
|
-
if not quiet:
|
|
514
|
+
msg = f"An unexpected error occurred: {general_error}"
|
|
515
|
+
if not quiet:
|
|
516
|
+
rprint(f"[bold red]Error: {msg}[/bold red]")
|
|
457
517
|
logger.error("Unexpected error in change_main", exc_info=True)
|
|
458
518
|
return msg, 0.0, ""
|
|
459
519
|
|