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/change_main.py CHANGED
@@ -1,26 +1,34 @@
1
- import click
2
- import logging
3
- import os # <--- Added import
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 # Added 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, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
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(f"Starting change_main with use_csv={use_csv}")
59
- logger.debug(f" change_prompt_file: {change_prompt_file}")
60
- logger.debug(f" input_code: {input_code}")
61
- logger.debug(f" input_prompt_file: {input_prompt_file}")
62
- logger.debug(f" output: {output}")
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) # Added time_budget
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", "") # Get from context
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]] = [] # For CSV mode
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: rprint(msg)
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(f"Normalizing output path: {output}")
100
+ logger.debug("Normalizing output path: %s", output)
93
101
  output = os.path.normpath(output)
94
- logger.debug(f"Normalized output path: {output}")
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: rprint(msg)
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}') must be a valid directory."
105
- if not quiet: rprint(msg)
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
- logger.warning(f"Input change file '{change_prompt_file}' does not end with .csv. Assuming it's a CSV.")
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(f"CSV header found: {header}")
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: rprint(f"[bold red]Error: {msg}[/bold red]")
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: rprint(f"[bold red]Error: {msg}[/bold red]")
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 e: # Catch specific CSV errors
138
- msg = f"Failed to read or validate CSV header: {e}"
139
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
140
- logger.error(f"CSV header validation error: {e}", exc_info=True)
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 e: # Catch other potential file errors
143
- msg = f"Failed to open or read CSV file '{change_prompt_file}': {e}"
144
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
145
- logger.error(f"Error reading CSV file: {e}", exc_info=True)
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: # Non-CSV mode
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: rprint(msg)
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
- msg = f"[bold red]Error:[/bold red] In non-CSV mode, --input-code ('{input_code}') must be a file path, not a directory."
156
- if not quiet: rprint(msg)
157
- logger.error(msg)
158
- return msg, 0.0, ""
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(f"Calling construct_paths with inputs: {input_file_paths} and options: {command_options}")
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(f"construct_paths returned:")
186
- logger.debug(f" input_strings keys: {list(input_strings.keys())}")
187
- logger.debug(f" output_file_paths: {output_file_paths}")
188
- logger.debug(f" language: {language}") # Language might be inferred or needed for defaults
189
- except Exception as e:
190
- msg = f"Error constructing paths: {e}"
191
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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" # Prioritize context language
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(f"Using extension '{extension}' from context for CSV processing.")
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(f"Derived language '{csv_target_language}' and extension '{extension}' for CSV processing.")
207
- except ValueError as e:
208
- msg = f"Could not determine file extension for language '{csv_target_language}': {e}"
209
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
210
- logger.error(msg)
211
- return msg, 0.0, ""
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, # Pass time_budget
220
- code_directory=input_code, # Pass the directory path
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(f"process_csv_change returned: success={success}, cost={total_cost}, model={model_name}")
229
- except Exception as e:
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: {e}"
232
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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(f"CSV processing complete. Result message: {result_message}")
272
+ logger.info("CSV processing complete. Result message: %s", result_message)
242
273
 
243
- else: # Non-CSV mode
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
- missing = [k for k, v in {"change_prompt_file": change_prompt_content,
251
- "input_code": input_code_content,
252
- "input_prompt_file": input_prompt_content}.items() if not v]
253
- msg = f"Failed to read content for required input files: {', '.join(missing)}"
254
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
255
- logger.error(msg)
256
- return msg, 0.0, ""
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, # Pass time_budget
298
+ time=time_budget,
267
299
  budget=budget,
268
- quiet=quiet
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 e:
273
- msg = f"Error during prompt modification: {e}"
274
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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(f"Resolved user specified output path: {output_path_obj}")
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
- # Use default path from construct_paths for single file mode if no --output
286
- output_path_obj = Path(output_file_paths["output_prompt_file"]).resolve()
287
- logger.debug(f"Using default output path from construct_paths: {output_path_obj}")
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(f"Saving batch results to CSV: {output_path_obj}")
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) # Uses Path.mkdir, OK here
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 item['modified_prompt'] is not None:
309
- writer.writerow({
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(f"Skipping row in output CSV due to missing data or error: {item.get('file_name')}")
315
- if not quiet: rprint(f"[green]Results saved to:[/green] {output_path_obj}")
316
- except IOError as e:
317
- msg = f"Failed to write output CSV '{output_path_obj}': {e}"
318
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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 e:
323
- msg = f"Unexpected error writing output CSV '{output_path_obj}': {e}"
324
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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
- output_dir = output_path_obj
340
- else: # Assume it's a file path, use parent
341
- output_dir = output_path_obj.parent
342
- logger.warning(f"Output path '{output_path_obj}' is not a directory or CSV. Saving individual files to parent directory: {output_dir}")
343
- else: # No output specified, save to CWD
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(f"Saving individual modified prompts to directory: {output_dir}")
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) # <--- Changed to os.makedirs
350
- except OSError as e:
351
- msg = f"Failed to create output directory '{output_dir}': {e}"
352
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
353
- logger.error(msg, exc_info=True)
354
- return result_message, total_cost, model_name or ""
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') # This should be the original prompt filename
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(f"Skipping save for item due to missing data or error: {item}")
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(f"Output file exists, skipping: {individual_output_path}. Use --force to overwrite.")
371
- if not quiet: rprint(f"[yellow]Skipping existing file:[/yellow] {individual_output_path}")
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(f"Attempting to save file to: {individual_output_path}")
376
- with open(individual_output_path, 'w', encoding='utf-8') as f:
377
- f.write(modified_content)
378
- logger.debug(f"Saved modified prompt to: {individual_output_path}")
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 e:
381
- msg = f"Failed to write output file '{individual_output_path}': {e}"
382
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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(f"Results saved as individual files in directory successfully")
392
- if not quiet: rprint(f"[green]Saved {saved_files_count} modified prompts to:[/green] {output_dir}")
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: # Non-CSV mode saving
449
+ else: # Non-CSV mode saving
395
450
  if not output_path_obj:
396
- # This case should ideally be caught by construct_paths, but double-check
397
- msg = "Could not determine output path for modified prompt."
398
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
399
- logger.error(msg)
400
- return msg, 0.0, ""
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(f"Saving single modified prompt to: {output_path_obj}")
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) # Uses Path.mkdir, OK here
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 f:
407
- f.write(result_message) # result_message contains the modified content here
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 e:
415
- msg = f"Failed to write output file '{output_path_obj}': {e}"
416
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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 "" # Return error after processing
419
- except Exception as e:
420
- msg = f"Unexpected error writing output file '{output_path_obj}': {e}"
421
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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
- rprint(f"[bold]Results saved to CSV:[/bold] {output_path_obj.resolve()}")
491
+ rprint(f"[bold]Results saved to CSV:[/bold] {output_path_obj.resolve()}")
434
492
  else:
435
- # Re-calculate output_dir in case it wasn't set earlier (e.g., no output specified)
436
- final_output_dir = Path(output).resolve() if output and Path(output).resolve().is_dir() else Path.cwd()
437
- if output and not final_output_dir.is_dir(): # Handle case where output was file-like
438
- # Use the previously calculated output_dir if available
439
- final_output_dir = output_dir if 'output_dir' in locals() else Path(output).resolve().parent
440
- rprint(f"[bold]Results saved as individual files in directory:[/bold] {final_output_dir}")
441
-
442
-
443
- except FileNotFoundError as e:
444
- msg = f"Input file not found: {e}"
445
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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 NotADirectoryError as e:
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: {e}"
456
- if not quiet: rprint(f"[bold red]Error: {msg}[/bold red]")
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