pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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.
Files changed (195) hide show
  1. pdd/__init__.py +40 -8
  2. pdd/agentic_bug.py +323 -0
  3. pdd/agentic_bug_orchestrator.py +497 -0
  4. pdd/agentic_change.py +231 -0
  5. pdd/agentic_change_orchestrator.py +526 -0
  6. pdd/agentic_common.py +598 -0
  7. pdd/agentic_crash.py +534 -0
  8. pdd/agentic_e2e_fix.py +319 -0
  9. pdd/agentic_e2e_fix_orchestrator.py +426 -0
  10. pdd/agentic_fix.py +1294 -0
  11. pdd/agentic_langtest.py +162 -0
  12. pdd/agentic_update.py +387 -0
  13. pdd/agentic_verify.py +183 -0
  14. pdd/architecture_sync.py +565 -0
  15. pdd/auth_service.py +210 -0
  16. pdd/auto_deps_main.py +71 -51
  17. pdd/auto_include.py +245 -5
  18. pdd/auto_update.py +125 -47
  19. pdd/bug_main.py +196 -23
  20. pdd/bug_to_unit_test.py +2 -0
  21. pdd/change_main.py +11 -4
  22. pdd/cli.py +22 -1181
  23. pdd/cmd_test_main.py +350 -150
  24. pdd/code_generator.py +60 -18
  25. pdd/code_generator_main.py +790 -57
  26. pdd/commands/__init__.py +48 -0
  27. pdd/commands/analysis.py +306 -0
  28. pdd/commands/auth.py +309 -0
  29. pdd/commands/connect.py +290 -0
  30. pdd/commands/fix.py +163 -0
  31. pdd/commands/generate.py +257 -0
  32. pdd/commands/maintenance.py +175 -0
  33. pdd/commands/misc.py +87 -0
  34. pdd/commands/modify.py +256 -0
  35. pdd/commands/report.py +144 -0
  36. pdd/commands/sessions.py +284 -0
  37. pdd/commands/templates.py +215 -0
  38. pdd/commands/utility.py +110 -0
  39. pdd/config_resolution.py +58 -0
  40. pdd/conflicts_main.py +8 -3
  41. pdd/construct_paths.py +589 -111
  42. pdd/context_generator.py +10 -2
  43. pdd/context_generator_main.py +175 -76
  44. pdd/continue_generation.py +53 -10
  45. pdd/core/__init__.py +33 -0
  46. pdd/core/cli.py +527 -0
  47. pdd/core/cloud.py +237 -0
  48. pdd/core/dump.py +554 -0
  49. pdd/core/errors.py +67 -0
  50. pdd/core/remote_session.py +61 -0
  51. pdd/core/utils.py +90 -0
  52. pdd/crash_main.py +262 -33
  53. pdd/data/language_format.csv +71 -63
  54. pdd/data/llm_model.csv +20 -18
  55. pdd/detect_change_main.py +5 -4
  56. pdd/docs/prompting_guide.md +864 -0
  57. pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
  58. pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
  59. pdd/fix_code_loop.py +523 -95
  60. pdd/fix_code_module_errors.py +6 -2
  61. pdd/fix_error_loop.py +491 -92
  62. pdd/fix_errors_from_unit_tests.py +4 -3
  63. pdd/fix_main.py +278 -21
  64. pdd/fix_verification_errors.py +12 -100
  65. pdd/fix_verification_errors_loop.py +529 -286
  66. pdd/fix_verification_main.py +294 -89
  67. pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
  68. pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
  69. pdd/frontend/dist/index.html +376 -0
  70. pdd/frontend/dist/logo.svg +33 -0
  71. pdd/generate_output_paths.py +139 -15
  72. pdd/generate_test.py +218 -146
  73. pdd/get_comment.py +19 -44
  74. pdd/get_extension.py +8 -9
  75. pdd/get_jwt_token.py +318 -22
  76. pdd/get_language.py +8 -7
  77. pdd/get_run_command.py +75 -0
  78. pdd/get_test_command.py +68 -0
  79. pdd/git_update.py +70 -19
  80. pdd/incremental_code_generator.py +2 -2
  81. pdd/insert_includes.py +13 -4
  82. pdd/llm_invoke.py +1711 -181
  83. pdd/load_prompt_template.py +19 -12
  84. pdd/path_resolution.py +140 -0
  85. pdd/pdd_completion.fish +25 -2
  86. pdd/pdd_completion.sh +30 -4
  87. pdd/pdd_completion.zsh +79 -4
  88. pdd/postprocess.py +14 -4
  89. pdd/preprocess.py +293 -24
  90. pdd/preprocess_main.py +41 -6
  91. pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
  92. pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
  93. pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
  94. pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
  95. pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
  96. pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
  97. pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
  98. pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
  99. pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
  100. pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
  101. pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
  102. pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
  103. pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
  104. pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
  105. pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
  106. pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
  107. pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
  108. pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
  109. pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
  110. pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
  111. pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
  112. pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
  113. pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
  114. pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
  115. pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
  116. pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
  117. pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
  118. pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
  119. pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
  120. pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
  121. pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
  122. pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
  123. pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
  124. pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
  125. pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
  126. pdd/prompts/agentic_update_LLM.prompt +925 -0
  127. pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
  128. pdd/prompts/auto_include_LLM.prompt +122 -905
  129. pdd/prompts/change_LLM.prompt +3093 -1
  130. pdd/prompts/detect_change_LLM.prompt +686 -27
  131. pdd/prompts/example_generator_LLM.prompt +22 -1
  132. pdd/prompts/extract_code_LLM.prompt +5 -1
  133. pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
  134. pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
  135. pdd/prompts/extract_promptline_LLM.prompt +17 -11
  136. pdd/prompts/find_verification_errors_LLM.prompt +6 -0
  137. pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
  138. pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
  139. pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
  140. pdd/prompts/generate_test_LLM.prompt +41 -7
  141. pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
  142. pdd/prompts/increase_tests_LLM.prompt +1 -5
  143. pdd/prompts/insert_includes_LLM.prompt +316 -186
  144. pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
  145. pdd/prompts/prompt_diff_LLM.prompt +82 -0
  146. pdd/prompts/trace_LLM.prompt +25 -22
  147. pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
  148. pdd/prompts/update_prompt_LLM.prompt +22 -1
  149. pdd/pytest_output.py +127 -12
  150. pdd/remote_session.py +876 -0
  151. pdd/render_mermaid.py +236 -0
  152. pdd/server/__init__.py +52 -0
  153. pdd/server/app.py +335 -0
  154. pdd/server/click_executor.py +587 -0
  155. pdd/server/executor.py +338 -0
  156. pdd/server/jobs.py +661 -0
  157. pdd/server/models.py +241 -0
  158. pdd/server/routes/__init__.py +31 -0
  159. pdd/server/routes/architecture.py +451 -0
  160. pdd/server/routes/auth.py +364 -0
  161. pdd/server/routes/commands.py +929 -0
  162. pdd/server/routes/config.py +42 -0
  163. pdd/server/routes/files.py +603 -0
  164. pdd/server/routes/prompts.py +1322 -0
  165. pdd/server/routes/websocket.py +473 -0
  166. pdd/server/security.py +243 -0
  167. pdd/server/terminal_spawner.py +209 -0
  168. pdd/server/token_counter.py +222 -0
  169. pdd/setup_tool.py +648 -0
  170. pdd/simple_math.py +2 -0
  171. pdd/split_main.py +3 -2
  172. pdd/summarize_directory.py +237 -195
  173. pdd/sync_animation.py +8 -4
  174. pdd/sync_determine_operation.py +839 -112
  175. pdd/sync_main.py +351 -57
  176. pdd/sync_orchestration.py +1400 -756
  177. pdd/sync_tui.py +848 -0
  178. pdd/template_expander.py +161 -0
  179. pdd/template_registry.py +264 -0
  180. pdd/templates/architecture/architecture_json.prompt +237 -0
  181. pdd/templates/generic/generate_prompt.prompt +174 -0
  182. pdd/trace.py +168 -12
  183. pdd/trace_main.py +4 -3
  184. pdd/track_cost.py +140 -63
  185. pdd/unfinished_prompt.py +51 -4
  186. pdd/update_main.py +567 -67
  187. pdd/update_model_costs.py +2 -2
  188. pdd/update_prompt.py +19 -4
  189. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
  190. pdd_cli-0.0.118.dist-info/RECORD +227 -0
  191. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
  192. pdd_cli-0.0.45.dist-info/RECORD +0 -116
  193. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
  194. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
  195. {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/generate_test.py CHANGED
@@ -1,184 +1,256 @@
1
- from typing import Tuple, Optional
2
- from rich import print
3
- from rich.markdown import Markdown
1
+ """
2
+ Module for generating unit tests from code or example files using LLMs.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ from typing import Optional, Tuple
8
+
4
9
  from rich.console import Console
5
- from . import EXTRACTION_STRENGTH, DEFAULT_STRENGTH, DEFAULT_TIME
6
- from .load_prompt_template import load_prompt_template
7
- from .preprocess import preprocess
8
- from .llm_invoke import llm_invoke
9
- from .unfinished_prompt import unfinished_prompt
10
- from .continue_generation import continue_generation
11
- from .postprocess import postprocess
10
+ from rich.markdown import Markdown
11
+
12
+ from pdd import DEFAULT_STRENGTH, DEFAULT_TIME, EXTRACTION_STRENGTH
13
+ from pdd.continue_generation import continue_generation
14
+ from pdd.llm_invoke import llm_invoke
15
+ from pdd.load_prompt_template import load_prompt_template
16
+ from pdd.postprocess import postprocess
17
+ from pdd.preprocess import preprocess
18
+ from pdd.unfinished_prompt import unfinished_prompt
12
19
 
13
20
  console = Console()
14
21
 
22
+
23
+ def _validate_inputs(
24
+ prompt: str,
25
+ code: Optional[str],
26
+ strength: float,
27
+ temperature: float,
28
+ language: str
29
+ ) -> None:
30
+ """
31
+ Validates the inputs for generate_test function.
32
+
33
+ Raises:
34
+ ValueError: If any input is invalid.
35
+ """
36
+ if not isinstance(prompt, str) or not prompt.strip():
37
+ raise ValueError("Prompt must be a non-empty string")
38
+
39
+ if code is None or not isinstance(code, str) or not code.strip():
40
+ raise ValueError("Code must be a non-empty string")
41
+
42
+ if not isinstance(strength, (int, float)) or not 0 <= strength <= 1:
43
+ raise ValueError("Strength must be a float between 0 and 1")
44
+
45
+ if not isinstance(temperature, (int, float)):
46
+ raise ValueError("Temperature must be a float")
47
+
48
+ if not isinstance(language, str) or not language.strip():
49
+ raise ValueError("Language must be a non-empty string")
50
+
51
+
15
52
  def generate_test(
16
53
  prompt: str,
17
- code: str,
18
- strength: float=DEFAULT_STRENGTH,
19
- temperature: float=0.0,
54
+ code: Optional[str] = None,
55
+ example: Optional[str] = None,
56
+ strength: float = DEFAULT_STRENGTH,
57
+ temperature: float = 0.0,
20
58
  time: float = DEFAULT_TIME,
21
- language: str = "python",
22
- verbose: bool = False
59
+ language: str = 'python',
60
+ verbose: bool = False,
61
+ source_file_path: Optional[str] = None,
62
+ test_file_path: Optional[str] = None,
63
+ module_name: Optional[str] = None,
64
+ existing_tests: Optional[str] = None
23
65
  ) -> Tuple[str, float, str]:
24
66
  """
25
- Generate a unit test from a code file using LLM.
67
+ Generates a unit test for a given code file or example usage using an LLM.
26
68
 
27
69
  Args:
28
- prompt (str): The prompt that generated the code file.
29
- code (str): The code to generate a unit test from.
30
- strength (float): The strength of the LLM model (0-1).
31
- temperature (float): The temperature of the LLM model.
32
- language (str): The programming language for the unit test.
33
- time (float, optional): Time budget for LLM calls. Defaults to DEFAULT_TIME.
34
- verbose (bool): Whether to print detailed information.
70
+ prompt: The prompt that generated the code (context).
71
+ code: The source code to test. Mutually exclusive with 'example'.
72
+ example: An example usage of the module. Mutually exclusive with 'code'.
73
+ strength: LLM strength (0.0 to 1.0).
74
+ temperature: LLM temperature.
75
+ time: Thinking effort for the LLM (0.0 to 1.0).
76
+ language: Target language for the test.
77
+ verbose: Whether to print detailed logs.
78
+ source_file_path: Path to the code under test.
79
+ test_file_path: Destination path for the test.
80
+ module_name: Name of the module for imports.
81
+ existing_tests: Content of existing tests to merge.
35
82
 
36
83
  Returns:
37
- Tuple[str, float, str]: (unit_test, total_cost, model_name)
84
+ Tuple containing:
85
+ - unit_test (str): The generated test code.
86
+ - total_cost (float): Total cost of generation.
87
+ - model_name (str): Name of the model used.
38
88
  """
39
89
  total_cost = 0.0
40
- model_name = ""
90
+ model_name = "unknown"
41
91
 
42
- try:
43
- # Step 1: Load prompt template
44
- template = load_prompt_template("generate_test_LLM")
45
- if not template:
46
- raise ValueError("Failed to load generate_test_LLM prompt template")
47
-
48
- # Step 2: Preprocess template
49
- processed_template = preprocess(template, recursive=False, double_curly_brackets=False)
50
- processed_prompt = preprocess(prompt, recursive=False, double_curly_brackets=False)
51
-
52
- # Step 3: Run through LLM
53
- input_json = {
54
- "prompt_that_generated_code": processed_prompt,
55
- "code": code,
56
- "language": language
57
- }
92
+ # --- Step 1: Determine prompt template and validate inputs ---
93
+ if (code is None and example is None) or (code is not None and example is not None):
94
+ raise ValueError("Exactly one of 'code' or 'example' must be provided.")
58
95
 
59
- if verbose:
60
- console.print("[bold blue]Generating unit test...[/bold blue]")
96
+ template_name = "generate_test_from_example_LLM" if example else "generate_test_LLM"
97
+
98
+ raw_template = load_prompt_template(template_name)
99
+ if not raw_template:
100
+ raise ValueError(f"Failed to load {template_name} prompt template")
101
+
102
+ # --- Step 2: Preprocess template and prompt ---
103
+ # Preprocess the template
104
+ prompt_template = preprocess(
105
+ raw_template,
106
+ recursive=False,
107
+ double_curly_brackets=False
108
+ )
109
+
110
+ # Preprocess the original prompt input
111
+ processed_prompt_input = preprocess(
112
+ prompt,
113
+ recursive=False,
114
+ double_curly_brackets=False
115
+ )
61
116
 
62
- response = llm_invoke(
63
- prompt=processed_template,
64
- input_json=input_json,
117
+ # --- Step 3: Run inputs through LLM ---
118
+ input_data = {
119
+ "prompt_that_generated_code": processed_prompt_input,
120
+ "language": language,
121
+ "source_file_path": source_file_path if source_file_path else "",
122
+ "test_file_path": test_file_path if test_file_path else "",
123
+ "module_name": module_name if module_name else "",
124
+ "existing_tests": existing_tests if existing_tests else ""
125
+ }
126
+
127
+ if example:
128
+ input_data["example"] = example
129
+ else:
130
+ input_data["code"] = code
131
+
132
+ if verbose:
133
+ console.print(
134
+ f"[bold blue]Generating unit test using template: {template_name}[/bold blue]"
135
+ )
136
+ console.print(f"[dim]Strength: {strength}, Time: {time}, Temp: {temperature}[/dim]")
137
+
138
+ try:
139
+ llm_result = llm_invoke(
140
+ prompt=prompt_template,
141
+ input_json=input_data,
65
142
  strength=strength,
66
143
  temperature=temperature,
67
144
  time=time,
68
145
  verbose=verbose
69
146
  )
147
+ except Exception as e:
148
+ console.print(f"[bold red]Error invoking LLM:[/bold red] {e}")
149
+ raise
70
150
 
71
- total_cost += response['cost']
72
- model_name = response['model_name']
73
- result = response['result']
151
+ current_text = llm_result['result']
152
+ total_cost += llm_result.get('cost', 0.0)
153
+ model_name = llm_result.get('model_name', 'unknown')
74
154
 
75
- # Validate that we got a non-empty result
76
- if not result or not result.strip():
77
- raise ValueError(f"LLM test generation returned empty result. Model: {model_name}, Cost: ${response['cost']:.6f}")
155
+ # --- Step 4: Verbose Output of Initial Result ---
156
+ if verbose:
157
+ console.print("[bold green]Initial LLM Output:[/bold green]")
158
+ console.print(Markdown(current_text))
159
+ console.print(f"[dim]Initial Cost: ${llm_result.get('cost', 0.0):.6f}[/dim]")
78
160
 
79
- if verbose:
80
- console.print(Markdown(result))
81
- console.print(f"[bold green]Initial generation cost: ${total_cost:.6f}[/bold green]")
82
-
83
- # Step 4: Check if generation is complete
84
- last_600_chars = result[-600:] if len(result) > 600 else result
85
-
86
- # Validate that the last_600_chars is not empty after stripping
87
- if not last_600_chars.strip():
88
- # If the tail is empty, assume generation is complete
89
- if verbose:
90
- console.print("[bold yellow]Last 600 chars are empty, assuming generation is complete[/bold yellow]")
91
- reasoning = "Generation appears complete (tail is empty)"
92
- is_finished = True
93
- check_cost = 0.0
94
- check_model = model_name
95
- else:
96
- reasoning, is_finished, check_cost, check_model = unfinished_prompt(
97
- prompt_text=last_600_chars,
98
- strength=strength,
99
- temperature=temperature,
100
- time=time,
101
- verbose=verbose
102
- )
103
- total_cost += check_cost
104
-
105
- if not is_finished:
106
- if verbose:
107
- console.print("[bold yellow]Generation incomplete. Continuing...[/bold yellow]")
108
-
109
- continued_result, continue_cost, continue_model = continue_generation(
110
- formatted_input_prompt=processed_template,
111
- llm_output=result,
112
- strength=strength,
113
- temperature=temperature,
114
- time=time,
115
- verbose=verbose
116
- )
117
- total_cost += continue_cost
118
- result = continued_result
119
- model_name = continue_model
161
+ # --- Step 5: Detect incomplete generation ---
162
+ # Check the last 600 characters
163
+ last_chunk = current_text[-600:] if len(current_text) > 600 else current_text
120
164
 
121
- # Process the final result
165
+ # Only check if there is actual content
166
+ if last_chunk.strip():
122
167
  try:
123
- processed_result, post_cost, post_model = postprocess(
124
- result,
125
- language=language,
126
- strength=EXTRACTION_STRENGTH,
168
+ reasoning, is_finished, check_cost, _ = unfinished_prompt(
169
+ prompt_text=last_chunk,
170
+ strength=strength,
127
171
  temperature=temperature,
128
172
  time=time,
173
+ language=language,
129
174
  verbose=verbose
130
175
  )
131
- total_cost += post_cost
176
+ total_cost += check_cost
177
+
178
+ if not is_finished:
179
+ if verbose:
180
+ console.print(
181
+ "[yellow]Output detected as incomplete. Continuing generation...[/yellow]"
182
+ )
183
+ console.print(f"[dim]Reasoning: {reasoning}[/dim]")
184
+
185
+ # We need the formatted prompt for continue_generation.
186
+ # Since llm_invoke handles formatting internally, we attempt to format here
187
+ # to pass context to the continuation logic.
188
+ try:
189
+ formatted_input_prompt = prompt_template.format(**input_data)
190
+ except Exception:
191
+ # Fallback if simple formatting fails (e.g. complex jinja or missing keys)
192
+ # We use the raw template as best effort context
193
+ formatted_input_prompt = prompt_template
194
+
195
+ final_llm_output, cont_cost, cont_model = continue_generation(
196
+ formatted_input_prompt=formatted_input_prompt,
197
+ llm_output=current_text,
198
+ strength=strength,
199
+ temperature=temperature,
200
+ verbose=verbose
201
+ )
202
+
203
+ current_text = final_llm_output
204
+ total_cost += cont_cost
205
+ model_name = cont_model # Update to the model used for continuation
132
206
  except Exception as e:
133
- console.print(f"[bold red]Postprocess failed: {str(e)}[/bold red]")
134
- console.print(f"[bold yellow]Falling back to raw result[/bold yellow]")
135
-
136
- # Try to extract code blocks directly from the raw result
137
- import re
138
- code_blocks = re.findall(r'```(?:python)?\s*(.*?)```', result, re.DOTALL | re.IGNORECASE)
139
-
140
- if code_blocks:
141
- # Use the first substantial code block
142
- for block in code_blocks:
143
- if len(block.strip()) > 100 and ('def test_' in block or 'import' in block):
144
- processed_result = block.strip()
145
- break
146
- else:
147
- processed_result = code_blocks[0].strip() if code_blocks else result
148
- else:
149
- # No code blocks found, use raw result
150
- processed_result = result
151
-
152
- post_cost = 0.0
153
-
154
- # Step 5: Print total cost if verbose
207
+ console.print(f"[bold red]Error during completion check/continuation:[/bold red] {e}")
208
+ # Proceed with what we have if check fails
209
+
210
+ # --- Step 6: Postprocess ---
211
+ try:
212
+ extracted_code, pp_cost, _ = postprocess(
213
+ llm_output=current_text,
214
+ language=language,
215
+ strength=EXTRACTION_STRENGTH,
216
+ verbose=verbose
217
+ )
218
+ total_cost += pp_cost
219
+ unit_test = extracted_code
220
+ except Exception as e:
155
221
  if verbose:
156
- console.print(f"[bold green]Total cost: ${total_cost:.6f}[/bold green]")
157
- console.print(f"[bold blue]Final model used: {model_name}[/bold blue]")
222
+ console.print(f"[bold red]Postprocessing failed:[/bold red] {e}")
223
+ unit_test = ""
158
224
 
159
- # Step 6: Return results
160
- return processed_result, total_cost, model_name
225
+ # Fallback extraction if postprocess returned empty or failed
226
+ if not unit_test.strip():
227
+ if verbose:
228
+ console.print(
229
+ "[yellow]Postprocess returned empty. Attempting fallback regex extraction.[/yellow]"
230
+ )
161
231
 
162
- except Exception as e:
163
- console.print(f"[bold red]Error: {str(e)}[/bold red]")
164
- raise
232
+ # Regex to find code blocks, preferring those with specific keywords
233
+ code_block_pattern = re.compile(r"```(?:\w+)?\n(.*?)```", re.DOTALL)
234
+ matches = code_block_pattern.findall(current_text)
165
235
 
236
+ best_match = ""
237
+ for match in matches:
238
+ # Heuristic: prefer blocks that look like tests
239
+ if "def test_" in match or "import unittest" in match or "import pytest" in match:
240
+ best_match = match
241
+ break
166
242
 
167
- def _validate_inputs(
168
- prompt: str,
169
- code: str,
170
- strength: float,
171
- temperature: float,
172
- language: str
173
- ) -> None:
174
- """Validate input parameters."""
175
- if not prompt or not isinstance(prompt, str):
176
- raise ValueError("Prompt must be a non-empty string")
177
- if not code or not isinstance(code, str):
178
- raise ValueError("Code must be a non-empty string")
179
- if not isinstance(strength, float) or not 0 <= strength <= 1:
180
- raise ValueError("Strength must be a float between 0 and 1")
181
- if not isinstance(temperature, float):
182
- raise ValueError("Temperature must be a float")
183
- if not language or not isinstance(language, str):
184
- raise ValueError("Language must be a non-empty string")
243
+ if not best_match and matches:
244
+ # If no specific test keywords found, take the longest block
245
+ best_match = max(matches, key=len)
246
+
247
+ unit_test = best_match if best_match else current_text
248
+
249
+ # --- Step 7: Final Cost Reporting ---
250
+ if verbose:
251
+ console.print(f"[bold blue]Generation Complete.[/bold blue]")
252
+ console.print(f"[bold]Total Cost:[/bold] ${total_cost:.6f}")
253
+ console.print(f"[dim]Final Model: {model_name}[/dim]")
254
+
255
+ # --- Step 8: Return ---
256
+ return unit_test, total_cost, model_name
pdd/get_comment.py CHANGED
@@ -1,55 +1,30 @@
1
- # To achieve the task of writing a Python function `get_comment` that returns the comment character(s) associated with a given programming language, we need to follow the steps outlined in your description. Here's how you can implement this:
2
-
3
- # ```python
4
- import os
5
1
  import csv
6
2
 
7
- def get_comment(language):
8
- # Step 1: Load environment variables to get the path to the CSV file
9
- pdd_path = os.getenv('PDD_PATH')
10
- if not pdd_path:
11
- return 'del' # Return 'del' if the environment variable is not set
3
+ from pdd.path_resolution import get_default_resolver
4
+
5
+
6
+ def get_comment(language: str) -> str:
7
+ try:
8
+ resolver = get_default_resolver()
9
+ csv_file_path = resolver.resolve_data_file("data/language_format.csv")
10
+ except ValueError:
11
+ return "del"
12
12
 
13
- csv_file_path = os.path.join(pdd_path, 'data', 'language_format.csv')
13
+ if not isinstance(language, str):
14
+ return "del"
14
15
 
15
- # Step 2: Lower case the language string
16
16
  language = language.lower()
17
17
 
18
18
  try:
19
- # Step 3: Open the CSV file and look up the comment character(s)
20
- with open(csv_file_path, mode='r', newline='') as csvfile:
19
+ with open(str(csv_file_path), mode="r", newline="") as csvfile:
21
20
  reader = csv.DictReader(csvfile)
22
21
  for row in reader:
23
- if row['language'].lower() == language:
24
- comment = row['comment']
25
- # Step 4: Check if the comment character(s) is valid
26
- if comment:
27
- return comment
28
- else:
29
- return 'del'
22
+ if row["language"].lower() == language:
23
+ comment = row.get("comment", "")
24
+ return comment if comment else "del"
30
25
  except FileNotFoundError:
31
- return 'del' # Return 'del' if the file is not found
32
- except Exception as e:
33
- return 'del' # Return 'del' for any other exceptions
34
-
35
- return 'del' # Return 'del' if the language is not found
36
-
37
- # Example usage:
38
- # Assuming the environment variable PDD_PATH is set correctly
39
- # print(get_comment('Python')) # Output: #
40
- # print(get_comment('Java')) # Output: //
41
- # ```
42
-
43
- # ### Explanation:
44
-
45
- # 1. **Environment Variable**: The function first retrieves the `PDD_PATH` environment variable to locate the CSV file. If the environment variable is not set, it returns `'del'`.
46
-
47
- # 2. **Case Insensitivity**: The input language string is converted to lowercase to ensure case-insensitive comparison.
48
-
49
- # 3. **CSV Reading**: The function reads the CSV file using Python's `csv.DictReader`, which allows accessing each row as a dictionary. It checks if the lowercase version of the language matches any entry in the CSV.
50
-
51
- # 4. **Validation**: If a match is found, it checks if the comment character(s) is valid (i.e., not an empty string). If valid, it returns the comment character(s); otherwise, it returns `'del'`.
52
-
53
- # 5. **Error Handling**: The function handles potential errors such as file not found or other exceptions by returning `'del'`.
26
+ return "del"
27
+ except Exception:
28
+ return "del"
54
29
 
55
- # This implementation assumes that the CSV file is correctly formatted and that the environment variable `PDD_PATH` is set to the correct path.
30
+ return "del"
pdd/get_extension.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Module to retrieve file extensions for programming languages."""
2
2
 
3
- import os
4
3
  import pandas as pd
4
+ from pdd.path_resolution import get_default_resolver
5
5
 
6
6
  def get_extension(language: str) -> str:
7
7
  """
@@ -18,13 +18,12 @@ def get_extension(language: str) -> str:
18
18
  ValueError: If the PDD_PATH environment variable is not set.
19
19
  FileNotFoundError: If the language_format.csv file is not found.
20
20
  """
21
- # Step 1: Load the environment variable PDD_PATH
22
- pdd_path = os.getenv('PDD_PATH')
23
- if not pdd_path:
24
- raise ValueError("Environment variable PDD_PATH is not set.")
25
-
26
- # Construct the full path to the CSV file
27
- csv_file_path = os.path.join(pdd_path, 'data', 'language_format.csv')
21
+ # Step 1: Resolve CSV path from PDD_PATH
22
+ resolver = get_default_resolver()
23
+ try:
24
+ csv_file_path = resolver.resolve_data_file("data/language_format.csv")
25
+ except ValueError as exc:
26
+ raise ValueError("Environment variable PDD_PATH is not set.") from exc
28
27
 
29
28
  # Step 2: Lower case the language string
30
29
  language_lower = language.lower()
@@ -63,4 +62,4 @@ def get_extension(language: str) -> str:
63
62
  # ```bash
64
63
  # pip install pandas
65
64
  # ```
66
- # - Ensure that the CSV file is structured correctly and located at the specified path.
65
+ # - Ensure that the CSV file is structured correctly and located at the specified path.