pdd-cli 0.0.39__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 +5 -4
- pdd/auto_deps_main.py +10 -6
- pdd/auto_include.py +143 -101
- pdd/auto_update.py +76 -68
- 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 +51 -35
- 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/get_extension.py +23 -9
- 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.39.dist-info → pdd_cli-0.0.41.dist-info}/METADATA +3 -3
- {pdd_cli-0.0.39.dist-info → pdd_cli-0.0.41.dist-info}/RECORD +28 -24
- {pdd_cli-0.0.39.dist-info → pdd_cli-0.0.41.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.39.dist-info → pdd_cli-0.0.41.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.39.dist-info → pdd_cli-0.0.41.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.39.dist-info → pdd_cli-0.0.41.dist-info}/top_level.txt +0 -0
pdd/__init__.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
"""PDD - Prompt Driven Development"""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.0.41"
|
|
2
4
|
|
|
3
5
|
# Strength parameter used for LLM extraction across the codebase
|
|
4
|
-
# Used in postprocessing, XML tagging, code generation, and other extraction
|
|
6
|
+
# Used in postprocessing, XML tagging, code generation, and other extraction
|
|
7
|
+
# operations. The module should have a large context window and be affordable.
|
|
5
8
|
EXTRACTION_STRENGTH = 0.9
|
|
6
9
|
|
|
7
10
|
DEFAULT_STRENGTH = 0.9
|
|
@@ -10,8 +13,6 @@ DEFAULT_TEMPERATURE = 0.0
|
|
|
10
13
|
|
|
11
14
|
DEFAULT_TIME = 0.25
|
|
12
15
|
|
|
13
|
-
"""PDD - Prompt Driven Development"""
|
|
14
|
-
|
|
15
16
|
# Define constants used across the package
|
|
16
17
|
DEFAULT_LLM_MODEL = "gpt-4.1-nano"
|
|
17
18
|
# When going to production, set the following constants:
|
pdd/auto_deps_main.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"""Main function for the auto-deps command."""
|
|
1
2
|
import sys
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Tuple, Optional
|
|
@@ -8,7 +9,7 @@ from . import DEFAULT_STRENGTH, DEFAULT_TIME
|
|
|
8
9
|
from .construct_paths import construct_paths
|
|
9
10
|
from .insert_includes import insert_includes
|
|
10
11
|
|
|
11
|
-
def auto_deps_main(
|
|
12
|
+
def auto_deps_main( # pylint: disable=too-many-arguments, too-many-locals
|
|
12
13
|
ctx: click.Context,
|
|
13
14
|
prompt_file: str,
|
|
14
15
|
directory_path: str,
|
|
@@ -57,7 +58,10 @@ def auto_deps_main(
|
|
|
57
58
|
# Handle force_scan option
|
|
58
59
|
if force_scan and Path(csv_path).exists():
|
|
59
60
|
if not ctx.obj.get('quiet', False):
|
|
60
|
-
rprint(
|
|
61
|
+
rprint(
|
|
62
|
+
"[yellow]Removing existing CSV file due to "
|
|
63
|
+
f"--force-scan option: {csv_path}[/yellow]"
|
|
64
|
+
)
|
|
61
65
|
Path(csv_path).unlink()
|
|
62
66
|
|
|
63
67
|
# Get strength and temperature from context
|
|
@@ -78,11 +82,11 @@ def auto_deps_main(
|
|
|
78
82
|
|
|
79
83
|
# Save the modified prompt to the output file
|
|
80
84
|
output_path = output_file_paths["output"]
|
|
81
|
-
Path(output_path).write_text(modified_prompt)
|
|
85
|
+
Path(output_path).write_text(modified_prompt, encoding="utf-8")
|
|
82
86
|
|
|
83
87
|
# Save the CSV output if it was generated
|
|
84
88
|
if csv_output:
|
|
85
|
-
Path(csv_path).write_text(csv_output)
|
|
89
|
+
Path(csv_path).write_text(csv_output, encoding="utf-8")
|
|
86
90
|
|
|
87
91
|
# Provide user feedback
|
|
88
92
|
if not ctx.obj.get('quiet', False):
|
|
@@ -94,7 +98,7 @@ def auto_deps_main(
|
|
|
94
98
|
|
|
95
99
|
return modified_prompt, total_cost, model_name
|
|
96
100
|
|
|
97
|
-
except Exception as
|
|
101
|
+
except Exception as exc:
|
|
98
102
|
if not ctx.obj.get('quiet', False):
|
|
99
|
-
rprint(f"[bold red]Error:[/bold red] {str(
|
|
103
|
+
rprint(f"[bold red]Error:[/bold red] {str(exc)}")
|
|
100
104
|
sys.exit(1)
|
pdd/auto_include.py
CHANGED
|
@@ -1,20 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides the `auto_include` function to automatically find and
|
|
3
|
+
insert dependencies into a prompt.
|
|
4
|
+
"""
|
|
5
|
+
from io import StringIO
|
|
1
6
|
from typing import Tuple, Optional
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
2
9
|
from pydantic import BaseModel, Field
|
|
3
|
-
from rich import print
|
|
4
10
|
from rich.console import Console
|
|
5
11
|
from rich.panel import Panel
|
|
6
|
-
|
|
12
|
+
|
|
13
|
+
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
7
14
|
from .llm_invoke import llm_invoke
|
|
15
|
+
from .load_prompt_template import load_prompt_template
|
|
8
16
|
from .summarize_directory import summarize_directory
|
|
9
|
-
import pandas as pd
|
|
10
|
-
from io import StringIO
|
|
11
|
-
from . import DEFAULT_TIME, DEFAULT_STRENGTH
|
|
12
17
|
|
|
13
18
|
console = Console()
|
|
14
19
|
|
|
15
20
|
class AutoIncludeOutput(BaseModel):
|
|
21
|
+
"""
|
|
22
|
+
Pydantic model for the output of the auto_include extraction.
|
|
23
|
+
"""
|
|
16
24
|
string_of_includes: str = Field(description="The string of includes to be added to the prompt")
|
|
17
25
|
|
|
26
|
+
|
|
27
|
+
def _validate_input(input_prompt: str, directory_path: str, strength: float, temperature: float):
|
|
28
|
+
"""Validate the inputs for the auto_include function."""
|
|
29
|
+
if not input_prompt:
|
|
30
|
+
raise ValueError("Input prompt cannot be empty")
|
|
31
|
+
if not directory_path:
|
|
32
|
+
raise ValueError("Invalid 'directory_path'.")
|
|
33
|
+
if not 0 <= strength <= 1:
|
|
34
|
+
raise ValueError("Strength must be between 0 and 1")
|
|
35
|
+
if not 0 <= temperature <= 1:
|
|
36
|
+
raise ValueError("Temperature must be between 0 and 1")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_available_includes_from_csv(csv_output: str) -> list[str]:
|
|
40
|
+
"""Parse the CSV output and return a list of available includes."""
|
|
41
|
+
if not csv_output:
|
|
42
|
+
return []
|
|
43
|
+
try:
|
|
44
|
+
# pylint: disable=invalid-name
|
|
45
|
+
dataframe = pd.read_csv(StringIO(csv_output))
|
|
46
|
+
return dataframe.apply(
|
|
47
|
+
lambda row: f"File: {row['full_path']}\nSummary: {row['file_summary']}",
|
|
48
|
+
axis=1
|
|
49
|
+
).tolist()
|
|
50
|
+
except Exception as ex:
|
|
51
|
+
console.print(f"[red]Error parsing CSV: {str(ex)}[/red]")
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_prompts() -> tuple[str, str]:
|
|
56
|
+
"""Load the prompt templates."""
|
|
57
|
+
auto_include_prompt = load_prompt_template("auto_include_LLM")
|
|
58
|
+
extract_prompt = load_prompt_template("extract_auto_include_LLM")
|
|
59
|
+
if not auto_include_prompt or not extract_prompt:
|
|
60
|
+
raise ValueError("Failed to load prompt templates")
|
|
61
|
+
return auto_include_prompt, extract_prompt
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _summarize(directory_path: str, csv_file: Optional[str], llm_kwargs: dict) -> tuple[str, float, str]:
|
|
65
|
+
"""Summarize the directory."""
|
|
66
|
+
return summarize_directory(
|
|
67
|
+
directory_path=directory_path,
|
|
68
|
+
csv_file=csv_file,
|
|
69
|
+
**llm_kwargs
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _run_llm_and_extract(
|
|
74
|
+
auto_include_prompt: str,
|
|
75
|
+
extract_prompt: str,
|
|
76
|
+
input_prompt: str,
|
|
77
|
+
available_includes: list[str],
|
|
78
|
+
llm_kwargs: dict,
|
|
79
|
+
) -> tuple[str, float, str]:
|
|
80
|
+
"""Run the LLM prompts and extract the dependencies."""
|
|
81
|
+
# pylint: disable=broad-except
|
|
82
|
+
# Run auto_include_LLM prompt
|
|
83
|
+
auto_include_response = llm_invoke(
|
|
84
|
+
prompt=auto_include_prompt,
|
|
85
|
+
input_json={
|
|
86
|
+
"input_prompt": input_prompt,
|
|
87
|
+
"available_includes": "\n".join(available_includes)
|
|
88
|
+
},
|
|
89
|
+
**llm_kwargs
|
|
90
|
+
)
|
|
91
|
+
total_cost = auto_include_response["cost"]
|
|
92
|
+
model_name = auto_include_response["model_name"]
|
|
93
|
+
|
|
94
|
+
# Run extract_auto_include_LLM prompt
|
|
95
|
+
try:
|
|
96
|
+
extract_response = llm_invoke(
|
|
97
|
+
prompt=extract_prompt,
|
|
98
|
+
input_json={"llm_output": auto_include_response["result"]},
|
|
99
|
+
output_pydantic=AutoIncludeOutput,
|
|
100
|
+
**llm_kwargs
|
|
101
|
+
)
|
|
102
|
+
total_cost += extract_response["cost"]
|
|
103
|
+
model_name = extract_response["model_name"]
|
|
104
|
+
dependencies = extract_response["result"].string_of_includes
|
|
105
|
+
except Exception as ex:
|
|
106
|
+
console.print(f"[red]Error extracting dependencies: {str(ex)}[/red]")
|
|
107
|
+
dependencies = ""
|
|
108
|
+
return dependencies, total_cost, model_name
|
|
109
|
+
|
|
110
|
+
|
|
18
111
|
def auto_include(
|
|
19
112
|
input_prompt: str,
|
|
20
113
|
directory_path: str,
|
|
@@ -39,128 +132,77 @@ def auto_include(
|
|
|
39
132
|
Returns:
|
|
40
133
|
Tuple[str, str, float, str]: (dependencies, csv_output, total_cost, model_name)
|
|
41
134
|
"""
|
|
135
|
+
# pylint: disable=broad-except
|
|
42
136
|
try:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
raise ValueError("Temperature must be between 0 and 1")
|
|
52
|
-
|
|
53
|
-
total_cost = 0.0
|
|
54
|
-
model_name = ""
|
|
137
|
+
_validate_input(input_prompt, directory_path, strength, temperature)
|
|
138
|
+
|
|
139
|
+
llm_kwargs = {
|
|
140
|
+
"strength": strength,
|
|
141
|
+
"temperature": temperature,
|
|
142
|
+
"time": time,
|
|
143
|
+
"verbose": verbose
|
|
144
|
+
}
|
|
55
145
|
|
|
56
146
|
if verbose:
|
|
57
147
|
console.print(Panel("Step 1: Loading prompt templates", style="blue"))
|
|
58
148
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
extract_prompt = load_prompt_template("extract_auto_include_LLM")
|
|
62
|
-
|
|
63
|
-
if not auto_include_prompt or not extract_prompt:
|
|
64
|
-
raise ValueError("Failed to load prompt templates")
|
|
65
|
-
|
|
149
|
+
auto_include_prompt, extract_prompt = _load_prompts()
|
|
150
|
+
|
|
66
151
|
if verbose:
|
|
67
152
|
console.print(Panel("Step 2: Running summarize_directory", style="blue"))
|
|
68
153
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
directory_path=directory_path,
|
|
72
|
-
strength=strength,
|
|
73
|
-
temperature=temperature,
|
|
74
|
-
time=time,
|
|
75
|
-
verbose=verbose,
|
|
76
|
-
csv_file=csv_file
|
|
154
|
+
csv_output, summary_cost, summary_model = _summarize(
|
|
155
|
+
directory_path, csv_file, llm_kwargs
|
|
77
156
|
)
|
|
78
|
-
total_cost += summary_cost
|
|
79
|
-
model_name = summary_model
|
|
80
|
-
|
|
81
|
-
# Parse CSV to get available includes
|
|
82
|
-
if not csv_output:
|
|
83
|
-
available_includes = []
|
|
84
|
-
else:
|
|
85
|
-
try:
|
|
86
|
-
df = pd.read_csv(StringIO(csv_output))
|
|
87
|
-
available_includes = df.apply(
|
|
88
|
-
lambda row: f"File: {row['full_path']}\nSummary: {row['file_summary']}",
|
|
89
|
-
axis=1
|
|
90
|
-
).tolist()
|
|
91
|
-
except Exception as e:
|
|
92
|
-
console.print(f"[red]Error parsing CSV: {str(e)}[/red]")
|
|
93
|
-
available_includes = []
|
|
94
157
|
|
|
158
|
+
available_includes = _get_available_includes_from_csv(csv_output)
|
|
159
|
+
|
|
95
160
|
if verbose:
|
|
96
161
|
console.print(Panel("Step 3: Running auto_include_LLM prompt", style="blue"))
|
|
97
162
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
strength=strength,
|
|
106
|
-
temperature=temperature,
|
|
107
|
-
time=time,
|
|
108
|
-
verbose=verbose
|
|
163
|
+
dependencies, llm_cost, llm_model_name = _run_llm_and_extract(
|
|
164
|
+
auto_include_prompt=auto_include_prompt,
|
|
165
|
+
extract_prompt=extract_prompt,
|
|
166
|
+
input_prompt=input_prompt,
|
|
167
|
+
available_includes=available_includes,
|
|
168
|
+
llm_kwargs=llm_kwargs,
|
|
109
169
|
)
|
|
110
|
-
|
|
111
|
-
|
|
170
|
+
|
|
171
|
+
total_cost = summary_cost + llm_cost
|
|
172
|
+
model_name = llm_model_name or summary_model
|
|
112
173
|
|
|
113
174
|
if verbose:
|
|
114
|
-
console.print(Panel(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
verbose=verbose,
|
|
125
|
-
output_pydantic=AutoIncludeOutput
|
|
126
|
-
)
|
|
127
|
-
total_cost += extract_response["cost"]
|
|
128
|
-
model_name = extract_response["model_name"]
|
|
129
|
-
|
|
130
|
-
if verbose:
|
|
131
|
-
console.print(Panel("Step 5: Extracting dependencies", style="blue"))
|
|
132
|
-
|
|
133
|
-
# Extract dependencies
|
|
134
|
-
dependencies = extract_response["result"].string_of_includes
|
|
135
|
-
except Exception as e:
|
|
136
|
-
console.print(f"[red]Error extracting dependencies: {str(e)}[/red]")
|
|
137
|
-
dependencies = ""
|
|
138
|
-
|
|
139
|
-
if verbose:
|
|
140
|
-
console.print(Panel(f"""
|
|
141
|
-
Results:
|
|
142
|
-
Dependencies: {dependencies}
|
|
143
|
-
CSV Output: {csv_output}
|
|
144
|
-
Total Cost: ${total_cost:.6f}
|
|
145
|
-
Model Used: {model_name}
|
|
146
|
-
""", style="green"))
|
|
175
|
+
console.print(Panel(
|
|
176
|
+
(
|
|
177
|
+
f"Results:\n"
|
|
178
|
+
f"Dependencies: {dependencies}\n"
|
|
179
|
+
f"CSV Output: {csv_output}\n"
|
|
180
|
+
f"Total Cost: ${total_cost:.6f}\n"
|
|
181
|
+
f"Model Used: {model_name}"
|
|
182
|
+
),
|
|
183
|
+
style="green"
|
|
184
|
+
))
|
|
147
185
|
|
|
148
186
|
return dependencies, csv_output, total_cost, model_name
|
|
149
187
|
|
|
150
|
-
except Exception as
|
|
151
|
-
console.print(f"[red]Error in auto_include: {str(
|
|
188
|
+
except Exception as ex:
|
|
189
|
+
console.print(f"[red]Error in auto_include: {str(ex)}[/red]")
|
|
152
190
|
raise
|
|
153
191
|
|
|
192
|
+
|
|
154
193
|
def main():
|
|
155
194
|
"""Example usage of auto_include function"""
|
|
156
195
|
try:
|
|
157
196
|
# Example inputs
|
|
158
197
|
input_prompt = "Write a function to process image data"
|
|
159
198
|
directory_path = "context/c*.py"
|
|
160
|
-
csv_file =
|
|
161
|
-
|
|
199
|
+
csv_file = (
|
|
200
|
+
"full_path,file_summary,date\n"
|
|
201
|
+
"context/image_utils.py,"
|
|
202
|
+
"\"Image processing utilities\",2023-01-01T10:00:00"
|
|
203
|
+
)
|
|
162
204
|
|
|
163
|
-
dependencies,
|
|
205
|
+
dependencies, _, total_cost, model_name = auto_include(
|
|
164
206
|
input_prompt=input_prompt,
|
|
165
207
|
directory_path=directory_path,
|
|
166
208
|
csv_file=csv_file,
|
|
@@ -175,8 +217,8 @@ context/image_utils.py,"Image processing utilities",2023-01-01T10:00:00"""
|
|
|
175
217
|
console.print(f"Total Cost: ${total_cost:.6f}")
|
|
176
218
|
console.print(f"Model Used: {model_name}")
|
|
177
219
|
|
|
178
|
-
except Exception as
|
|
179
|
-
console.print(f"[red]Error in main: {str(
|
|
220
|
+
except Exception as ex:
|
|
221
|
+
console.print(f"[red]Error in main: {str(ex)}[/red]")
|
|
180
222
|
|
|
181
223
|
if __name__ == "__main__":
|
|
182
224
|
main()
|
pdd/auto_update.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
"""This module provides a function to automatically update the package."""
|
|
1
2
|
import importlib.metadata
|
|
2
|
-
import
|
|
3
|
-
import semver
|
|
3
|
+
import shutil
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
|
-
import
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import requests
|
|
8
|
+
import semver
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
def detect_installation_method(sys_executable):
|
|
9
12
|
"""
|
|
@@ -38,12 +41,59 @@ def get_upgrade_command(package_name, installation_method):
|
|
|
38
41
|
uv_path = shutil.which("uv")
|
|
39
42
|
if uv_path:
|
|
40
43
|
return ([uv_path, "tool", "install", package_name, "--force"], False)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
# If uv isn't in PATH, use shell=True
|
|
45
|
+
return (["uv", "tool", "install", package_name, "--force"], True)
|
|
46
|
+
# Default pip method
|
|
47
|
+
return ([sys.executable, "-m", "pip", "install", "--upgrade", package_name], False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_latest_version(package_name: str) -> Optional[str]:
|
|
51
|
+
"""Fetch the latest version of a package from PyPI."""
|
|
52
|
+
# pylint: disable=broad-except
|
|
53
|
+
try:
|
|
54
|
+
pypi_url = f"https://pypi.org/pypi/{package_name}/json"
|
|
55
|
+
response = requests.get(pypi_url, timeout=10)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
return response.json()['info']['version']
|
|
58
|
+
except Exception as ex:
|
|
59
|
+
print(f"Failed to fetch latest version from PyPI: {str(ex)}")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _upgrade_package(package_name: str, installation_method: str):
|
|
64
|
+
"""Upgrade a package using the specified installation method."""
|
|
65
|
+
cmd, use_shell = get_upgrade_command(package_name, installation_method)
|
|
66
|
+
cmd_str = " ".join(cmd)
|
|
67
|
+
print(f"\nDetected installation method: {installation_method}")
|
|
68
|
+
print(f"Upgrading with command: {cmd_str}")
|
|
69
|
+
|
|
70
|
+
# pylint: disable=broad-except
|
|
71
|
+
try:
|
|
72
|
+
result = subprocess.run(
|
|
73
|
+
cmd,
|
|
74
|
+
shell=use_shell,
|
|
75
|
+
capture_output=True,
|
|
76
|
+
text=True,
|
|
77
|
+
check=False
|
|
78
|
+
)
|
|
79
|
+
if result.returncode == 0:
|
|
80
|
+
print(f"\nSuccessfully upgraded {package_name}")
|
|
81
|
+
return True
|
|
82
|
+
print(f"\nUpgrade command failed: {result.stderr}")
|
|
83
|
+
return False
|
|
84
|
+
except Exception as ex:
|
|
85
|
+
print(f"\nError during upgrade: {str(ex)}")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_new_version_available(current_version: str, latest_version: str) -> bool:
|
|
90
|
+
"""Check if a new version is available."""
|
|
91
|
+
try:
|
|
92
|
+
current_semver = semver.VersionInfo.parse(current_version)
|
|
93
|
+
latest_semver = semver.VersionInfo.parse(latest_version)
|
|
94
|
+
return latest_semver > current_semver
|
|
95
|
+
except ValueError:
|
|
96
|
+
return latest_version != current_version
|
|
47
97
|
|
|
48
98
|
|
|
49
99
|
def auto_update(package_name: str = "pdd-cli", latest_version: str = None) -> None:
|
|
@@ -55,85 +105,43 @@ def auto_update(package_name: str = "pdd-cli", latest_version: str = None) -> No
|
|
|
55
105
|
latest_version (str): Known latest version (default: None)
|
|
56
106
|
package_name (str): Name of the package to check (default: "pdd-cli")
|
|
57
107
|
"""
|
|
108
|
+
# pylint: disable=broad-except
|
|
58
109
|
try:
|
|
59
|
-
# Get current installed version
|
|
60
110
|
current_version = importlib.metadata.version(package_name)
|
|
61
111
|
|
|
62
|
-
# If latest_version is not provided, fetch from PyPI
|
|
63
112
|
if latest_version is None:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
response = requests.get(pypi_url)
|
|
67
|
-
response.raise_for_status()
|
|
68
|
-
latest_version = response.json()['info']['version']
|
|
69
|
-
except Exception as e:
|
|
70
|
-
print(f"Failed to fetch latest version from PyPI: {str(e)}")
|
|
113
|
+
latest_version = _get_latest_version(package_name)
|
|
114
|
+
if latest_version is None:
|
|
71
115
|
return
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
current_semver = semver.VersionInfo.parse(current_version)
|
|
76
|
-
latest_semver = semver.VersionInfo.parse(latest_version)
|
|
77
|
-
except ValueError:
|
|
78
|
-
# If versions don't follow semantic versioning, fall back to string comparison
|
|
79
|
-
if current_version == latest_version:
|
|
80
|
-
return
|
|
81
|
-
else:
|
|
82
|
-
# If versions follow semantic versioning, compare properly
|
|
83
|
-
if current_semver >= latest_semver:
|
|
84
|
-
return
|
|
117
|
+
if not _is_new_version_available(current_version, latest_version):
|
|
118
|
+
return
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
print(f"\nNew version of {package_name} available: "
|
|
121
|
+
f"{latest_version} (current: {current_version})")
|
|
88
122
|
|
|
89
|
-
# Ask for user confirmation
|
|
90
123
|
while True:
|
|
91
124
|
response = input("Would you like to upgrade? [y/N]: ").lower().strip()
|
|
92
125
|
if response in ['y', 'yes']:
|
|
93
|
-
# Detect installation method
|
|
94
126
|
installation_method = detect_installation_method(sys.executable)
|
|
95
|
-
|
|
127
|
+
if _upgrade_package(package_name, installation_method):
|
|
128
|
+
break
|
|
96
129
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
130
|
+
if installation_method == "uv":
|
|
131
|
+
print("\nAttempting fallback to pip...")
|
|
132
|
+
if _upgrade_package(package_name, "pip"):
|
|
133
|
+
break
|
|
100
134
|
|
|
101
|
-
try:
|
|
102
|
-
result = subprocess.run(cmd, shell=use_shell, capture_output=True, text=True)
|
|
103
|
-
|
|
104
|
-
if result.returncode == 0:
|
|
105
|
-
print(f"\nSuccessfully upgraded {package_name} to version {latest_version}")
|
|
106
|
-
else:
|
|
107
|
-
print(f"\nUpgrade command failed: {result.stderr}")
|
|
108
|
-
|
|
109
|
-
# If UV failed and we're not already in fallback mode, try pip as fallback
|
|
110
|
-
if installation_method == "uv":
|
|
111
|
-
print("\nAttempting fallback to pip...")
|
|
112
|
-
fallback_cmd, fallback_shell = get_upgrade_command(package_name, "pip")
|
|
113
|
-
fallback_str = " ".join(fallback_cmd)
|
|
114
|
-
print(f"Fallback command: {fallback_str}")
|
|
115
|
-
|
|
116
|
-
try:
|
|
117
|
-
fallback_result = subprocess.run(fallback_cmd, shell=fallback_shell, capture_output=True, text=True)
|
|
118
|
-
if fallback_result.returncode == 0:
|
|
119
|
-
print(f"\nSuccessfully upgraded {package_name} using fallback method")
|
|
120
|
-
else:
|
|
121
|
-
print(f"\nFallback upgrade failed: {fallback_result.stderr}")
|
|
122
|
-
except Exception as fallback_err:
|
|
123
|
-
print(f"\nError during fallback upgrade: {str(fallback_err)}")
|
|
124
|
-
except Exception as e:
|
|
125
|
-
print(f"\nError during upgrade: {str(e)}")
|
|
126
135
|
break
|
|
127
|
-
|
|
136
|
+
if response in ['n', 'no', '']:
|
|
128
137
|
print("\nUpgrade cancelled")
|
|
129
138
|
break
|
|
130
|
-
|
|
131
|
-
print("Please answer 'y' or 'n'")
|
|
139
|
+
print("Please answer 'y' or 'n'")
|
|
132
140
|
|
|
133
141
|
except importlib.metadata.PackageNotFoundError:
|
|
134
142
|
print(f"Package {package_name} is not installed")
|
|
135
|
-
except Exception as
|
|
136
|
-
print(f"Error checking for updates: {str(
|
|
143
|
+
except Exception as ex:
|
|
144
|
+
print(f"Error checking for updates: {str(ex)}")
|
|
137
145
|
|
|
138
146
|
|
|
139
147
|
if __name__ == "__main__":
|