auto-code-fixer 0.2.5__tar.gz → 0.2.6__tar.gz
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.
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/PKG-INFO +1 -1
- auto_code_fixer-0.2.6/auto_code_fixer/__init__.py +1 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer/cli.py +29 -59
- auto_code_fixer-0.2.6/auto_code_fixer/fixer.py +55 -0
- auto_code_fixer-0.2.6/auto_code_fixer/installer.py +16 -0
- auto_code_fixer-0.2.6/auto_code_fixer/runner.py +14 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/PKG-INFO +1 -1
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/SOURCES.txt +0 -1
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/pyproject.toml +1 -1
- auto_code_fixer-0.2.5/auto_code_fixer/__init__.py +0 -2
- auto_code_fixer-0.2.5/auto_code_fixer/fixer.py +0 -60
- auto_code_fixer-0.2.5/auto_code_fixer/guard.py +0 -19
- auto_code_fixer-0.2.5/auto_code_fixer/installer.py +0 -25
- auto_code_fixer-0.2.5/auto_code_fixer/runner.py +0 -30
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/LICENSE +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/README.md +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer/utils.py +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/dependency_links.txt +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/entry_points.txt +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/requires.txt +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/top_level.txt +0 -0
- {auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.6"
|
|
@@ -2,7 +2,6 @@ import argparse
|
|
|
2
2
|
import shutil
|
|
3
3
|
import tempfile
|
|
4
4
|
import time
|
|
5
|
-
import os
|
|
6
5
|
from decouple import config
|
|
7
6
|
|
|
8
7
|
from auto_code_fixer.runner import run_code
|
|
@@ -14,7 +13,6 @@ from auto_code_fixer.utils import (
|
|
|
14
13
|
log,
|
|
15
14
|
set_verbose,
|
|
16
15
|
)
|
|
17
|
-
from auto_code_fixer.guard import import_hijacked
|
|
18
16
|
from auto_code_fixer import __version__
|
|
19
17
|
|
|
20
18
|
MAX_RETRIES = 5
|
|
@@ -24,22 +22,16 @@ def fix_file(file_path, project_root, api_key, ask, verbose):
|
|
|
24
22
|
log(f"Processing file: {file_path}")
|
|
25
23
|
|
|
26
24
|
with open(file_path) as f:
|
|
27
|
-
|
|
25
|
+
original_code = f.read()
|
|
28
26
|
|
|
29
27
|
temp_dir = tempfile.mkdtemp(prefix="codefix_")
|
|
30
|
-
temp_file =
|
|
31
|
-
|
|
32
|
-
# write initial code to temp
|
|
33
|
-
with open(temp_file, "w") as f:
|
|
34
|
-
f.write(current_code)
|
|
28
|
+
temp_file = f"{temp_dir}/temp.py"
|
|
29
|
+
shutil.copy(file_path, temp_file)
|
|
35
30
|
|
|
36
31
|
for attempt in range(MAX_RETRIES):
|
|
37
32
|
log(f"Run attempt #{attempt + 1}")
|
|
38
33
|
|
|
39
|
-
retcode, stdout, stderr = run_code(
|
|
40
|
-
temp_file,
|
|
41
|
-
project_root=project_root,
|
|
42
|
-
)
|
|
34
|
+
retcode, stdout, stderr = run_code(temp_file)
|
|
43
35
|
|
|
44
36
|
if verbose:
|
|
45
37
|
if stdout:
|
|
@@ -55,15 +47,13 @@ def fix_file(file_path, project_root, api_key, ask, verbose):
|
|
|
55
47
|
confirm = input(
|
|
56
48
|
f"Overwrite original file '{file_path}' with fixed version? (y/n): "
|
|
57
49
|
).strip().lower()
|
|
50
|
+
|
|
58
51
|
if confirm != "y":
|
|
59
52
|
log("User declined overwrite", "WARN")
|
|
60
53
|
shutil.rmtree(temp_dir)
|
|
61
54
|
return
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
with open(file_path, "w") as f:
|
|
65
|
-
f.write(current_code)
|
|
66
|
-
|
|
56
|
+
shutil.copy(temp_file, file_path)
|
|
67
57
|
log(f"File updated: {file_path}")
|
|
68
58
|
|
|
69
59
|
shutil.rmtree(temp_dir)
|
|
@@ -73,55 +63,24 @@ def fix_file(file_path, project_root, api_key, ask, verbose):
|
|
|
73
63
|
log("Error detected ❌", "ERROR")
|
|
74
64
|
print(stderr)
|
|
75
65
|
|
|
76
|
-
if check_and_install_missing_lib(stderr
|
|
66
|
+
if check_and_install_missing_lib(stderr):
|
|
77
67
|
log("Missing dependency installed, retrying…")
|
|
78
68
|
time.sleep(1)
|
|
79
69
|
continue
|
|
80
70
|
|
|
81
71
|
log("Sending code & error to GPT 🧠", "DEBUG")
|
|
82
|
-
|
|
83
72
|
fixed_code = fix_code_with_gpt(
|
|
84
|
-
|
|
73
|
+
open(temp_file).read(),
|
|
85
74
|
stderr,
|
|
86
75
|
api_key,
|
|
87
76
|
)
|
|
88
77
|
|
|
89
|
-
if
|
|
90
|
-
log(
|
|
91
|
-
"GPT attempted to hijack local imports. Rejecting fix.",
|
|
92
|
-
"ERROR",
|
|
93
|
-
)
|
|
94
|
-
break
|
|
95
|
-
|
|
96
|
-
if fixed_code.strip() == current_code.strip():
|
|
78
|
+
if fixed_code.strip() == open(temp_file).read().strip():
|
|
97
79
|
log("GPT returned no changes. Stopping.", "WARN")
|
|
98
80
|
break
|
|
99
81
|
|
|
100
|
-
import ast
|
|
101
|
-
|
|
102
|
-
# Validate GPT output BEFORE accepting it
|
|
103
|
-
try:
|
|
104
|
-
ast.parse(fixed_code)
|
|
105
|
-
except SyntaxError:
|
|
106
|
-
log(
|
|
107
|
-
"GPT returned invalid or empty Python code. Rejecting fix.",
|
|
108
|
-
"ERROR",
|
|
109
|
-
)
|
|
110
|
-
break
|
|
111
|
-
|
|
112
|
-
if not fixed_code.strip():
|
|
113
|
-
log(
|
|
114
|
-
"GPT returned empty code. Rejecting fix.",
|
|
115
|
-
"ERROR",
|
|
116
|
-
)
|
|
117
|
-
break
|
|
118
|
-
|
|
119
|
-
# ✅ SAFE: update memory only after validation
|
|
120
|
-
current_code = fixed_code
|
|
121
|
-
|
|
122
|
-
# then update temp file for next execution
|
|
123
82
|
with open(temp_file, "w") as f:
|
|
124
|
-
f.write(
|
|
83
|
+
f.write(fixed_code)
|
|
125
84
|
|
|
126
85
|
log("Code updated by GPT ✏️")
|
|
127
86
|
time.sleep(1)
|
|
@@ -130,7 +89,6 @@ def fix_file(file_path, project_root, api_key, ask, verbose):
|
|
|
130
89
|
shutil.rmtree(temp_dir)
|
|
131
90
|
|
|
132
91
|
|
|
133
|
-
|
|
134
92
|
def main():
|
|
135
93
|
parser = argparse.ArgumentParser(
|
|
136
94
|
description="Auto-fix Python code using ChatGPT"
|
|
@@ -151,23 +109,35 @@ def main():
|
|
|
151
109
|
parser.add_argument("--project-root", default=".")
|
|
152
110
|
parser.add_argument("--api-key")
|
|
153
111
|
|
|
112
|
+
# ✅ Proper boolean flags
|
|
154
113
|
ask_group = parser.add_mutually_exclusive_group()
|
|
155
|
-
ask_group.add_argument(
|
|
156
|
-
|
|
114
|
+
ask_group.add_argument(
|
|
115
|
+
"--ask",
|
|
116
|
+
action="store_true",
|
|
117
|
+
help="Ask before overwriting files",
|
|
118
|
+
)
|
|
119
|
+
ask_group.add_argument(
|
|
120
|
+
"--no-ask",
|
|
121
|
+
action="store_true",
|
|
122
|
+
help="Do not ask before overwriting files",
|
|
123
|
+
)
|
|
157
124
|
|
|
158
|
-
parser.add_argument(
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--verbose",
|
|
127
|
+
action="store_true",
|
|
128
|
+
help="Enable verbose/debug output",
|
|
129
|
+
)
|
|
159
130
|
|
|
160
131
|
args = parser.parse_args()
|
|
161
132
|
|
|
162
133
|
if not args.entry_file:
|
|
163
134
|
parser.error("the following arguments are required: entry_file")
|
|
164
135
|
|
|
165
|
-
#
|
|
166
|
-
project_root = os.path.abspath(args.project_root)
|
|
167
|
-
|
|
136
|
+
# ENV defaults
|
|
168
137
|
env_ask = config("AUTO_CODE_FIXER_ASK", default=False, cast=bool)
|
|
169
138
|
env_verbose = config("AUTO_CODE_FIXER_VERBOSE", default=False, cast=bool)
|
|
170
139
|
|
|
140
|
+
# Final ask resolution (CLI overrides ENV)
|
|
171
141
|
if args.ask:
|
|
172
142
|
ask = True
|
|
173
143
|
elif args.no_ask:
|
|
@@ -180,4 +150,4 @@ def main():
|
|
|
180
150
|
|
|
181
151
|
files = discover_all_files(args.entry_file)
|
|
182
152
|
for file in files:
|
|
183
|
-
fix_file(file, project_root, args.api_key, ask, verbose)
|
|
153
|
+
fix_file(file, args.project_root, args.api_key, ask, verbose)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from decouple import config
|
|
4
|
+
from openai import OpenAI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_openai_client(api_key=None):
|
|
8
|
+
key = (
|
|
9
|
+
api_key
|
|
10
|
+
or os.getenv("OPENAI_API_KEY")
|
|
11
|
+
or config("OPENAI_API_KEY", default=None)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if not key:
|
|
15
|
+
raise RuntimeError(
|
|
16
|
+
"OpenAI API key not found. "
|
|
17
|
+
"Set OPENAI_API_KEY env var or .env file or pass --api-key"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return OpenAI(api_key=key)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def fix_code_with_gpt(original_code, error_log, api_key=None):
|
|
24
|
+
client = get_openai_client(api_key)
|
|
25
|
+
|
|
26
|
+
prompt = f"""
|
|
27
|
+
You are a senior Python engineer.
|
|
28
|
+
Fix the following Python code so it runs without errors.
|
|
29
|
+
|
|
30
|
+
Code:
|
|
31
|
+
{original_code}
|
|
32
|
+
|
|
33
|
+
Error:
|
|
34
|
+
{error_log}
|
|
35
|
+
|
|
36
|
+
Return ONLY the full corrected Python code.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
response = client.chat.completions.create(
|
|
40
|
+
model="gpt-5",
|
|
41
|
+
messages=[
|
|
42
|
+
{"role": "system", "content": "You fix broken Python code."},
|
|
43
|
+
{"role": "user", "content": prompt},
|
|
44
|
+
],
|
|
45
|
+
max_completion_tokens=1500,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
fixed_code = response.choices[0].message.content.strip()
|
|
49
|
+
|
|
50
|
+
if fixed_code.startswith("```"):
|
|
51
|
+
fixed_code = "\n".join(fixed_code.split("\n")[1:])
|
|
52
|
+
if fixed_code.endswith("```"):
|
|
53
|
+
fixed_code = "\n".join(fixed_code.split("\n")[:-1])
|
|
54
|
+
|
|
55
|
+
return fixed_code.strip()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check_and_install_missing_lib(stderr):
|
|
7
|
+
match = re.search(r"No module named '([^']+)'", stderr)
|
|
8
|
+
if not match:
|
|
9
|
+
return False
|
|
10
|
+
|
|
11
|
+
lib = match.group(1)
|
|
12
|
+
try:
|
|
13
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", lib])
|
|
14
|
+
return True
|
|
15
|
+
except subprocess.CalledProcessError:
|
|
16
|
+
return False
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def run_code(path_to_code):
|
|
5
|
+
try:
|
|
6
|
+
result = subprocess.run(
|
|
7
|
+
["python3", path_to_code],
|
|
8
|
+
capture_output=True,
|
|
9
|
+
text=True,
|
|
10
|
+
timeout=15,
|
|
11
|
+
)
|
|
12
|
+
return result.returncode, result.stdout, result.stderr
|
|
13
|
+
except subprocess.TimeoutExpired:
|
|
14
|
+
return -1, "", "TimeoutExpired: Code took too long to run."
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from openai import OpenAI
|
|
2
|
-
from decouple import config
|
|
3
|
-
|
|
4
|
-
client = OpenAI(api_key=config("chat_gpt_secret_key"))
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def fix_code_with_gpt(original_code: str, error_log: str, api_key: str | None):
|
|
8
|
-
"""
|
|
9
|
-
Send code + error to GPT and return fixed code.
|
|
10
|
-
Enforces strict rules to prevent semantic-breaking fixes.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
system_prompt = """
|
|
14
|
-
You are a senior Python engineer fixing code.
|
|
15
|
-
|
|
16
|
-
STRICT RULES (DO NOT VIOLATE):
|
|
17
|
-
- NEVER replace or wrap local imports with try/except.
|
|
18
|
-
- NEVER inline, duplicate, or redefine code from other local files.
|
|
19
|
-
- NEVER change import statements for local modules.
|
|
20
|
-
- Assume all local imports are valid and must remain unchanged.
|
|
21
|
-
- If an import fails, assume environment configuration is incorrect.
|
|
22
|
-
- Only fix syntax errors or runtime errors in the current file.
|
|
23
|
-
- Return ONLY valid Python code. No markdown. No explanation.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
user_prompt = f"""
|
|
27
|
-
The following Python code has an error.
|
|
28
|
-
|
|
29
|
-
CODE:
|
|
30
|
-
{original_code}
|
|
31
|
-
|
|
32
|
-
ERROR:
|
|
33
|
-
{error_log}
|
|
34
|
-
|
|
35
|
-
Fix the code according to the rules.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
response = client.chat.completions.create(
|
|
40
|
-
model="gpt-5",
|
|
41
|
-
messages=[
|
|
42
|
-
{"role": "system", "content": system_prompt},
|
|
43
|
-
{"role": "user", "content": user_prompt},
|
|
44
|
-
],
|
|
45
|
-
max_completion_tokens=1500,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
fixed_code = response.choices[0].message.content.strip()
|
|
49
|
-
|
|
50
|
-
# strip accidental markdown fences
|
|
51
|
-
if fixed_code.startswith("```"):
|
|
52
|
-
fixed_code = "\n".join(fixed_code.splitlines()[1:])
|
|
53
|
-
if fixed_code.endswith("```"):
|
|
54
|
-
fixed_code = "\n".join(fixed_code.splitlines()[:-1])
|
|
55
|
-
|
|
56
|
-
return fixed_code.strip()
|
|
57
|
-
|
|
58
|
-
except Exception as e:
|
|
59
|
-
print(f"Error calling OpenAI API: {e}")
|
|
60
|
-
return original_code
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
def import_hijacked(original_code: str, fixed_code: str) -> bool:
|
|
2
|
-
"""
|
|
3
|
-
Detects GPT cheating by replacing imports with try/except fallbacks
|
|
4
|
-
or inlining local module logic.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
if "from " not in original_code:
|
|
8
|
-
return False
|
|
9
|
-
|
|
10
|
-
# common cheating patterns
|
|
11
|
-
bad_patterns = [
|
|
12
|
-
"try:",
|
|
13
|
-
"except ImportError",
|
|
14
|
-
"except ModuleNotFoundError",
|
|
15
|
-
"def greet(",
|
|
16
|
-
"def add(",
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
return any(p in fixed_code for p in bad_patterns)
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
import sys
|
|
4
|
-
import subprocess
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def check_and_install_missing_lib(stderr, project_root):
|
|
8
|
-
match = re.search(r"No module named '([^']+)'", stderr)
|
|
9
|
-
if not match:
|
|
10
|
-
return False
|
|
11
|
-
|
|
12
|
-
missing = match.group(1)
|
|
13
|
-
|
|
14
|
-
# 🔒 CRITICAL: skip local project modules
|
|
15
|
-
local_file = os.path.join(project_root, f"{missing}.py")
|
|
16
|
-
if os.path.exists(local_file):
|
|
17
|
-
return False
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
subprocess.check_call(
|
|
21
|
-
[sys.executable, "-m", "pip", "install", missing]
|
|
22
|
-
)
|
|
23
|
-
return True
|
|
24
|
-
except subprocess.CalledProcessError:
|
|
25
|
-
return False
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def run_code(script_path, project_root):
|
|
7
|
-
env = os.environ.copy()
|
|
8
|
-
|
|
9
|
-
script_dir = os.path.dirname(script_path)
|
|
10
|
-
|
|
11
|
-
# 🔑 CRITICAL: include BOTH project root and script dir
|
|
12
|
-
paths = [project_root, script_dir]
|
|
13
|
-
|
|
14
|
-
existing = env.get("PYTHONPATH", "")
|
|
15
|
-
if existing:
|
|
16
|
-
paths.append(existing)
|
|
17
|
-
|
|
18
|
-
env["PYTHONPATH"] = os.pathsep.join(paths)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
result = subprocess.run(
|
|
22
|
-
[sys.executable, script_path],
|
|
23
|
-
capture_output=True,
|
|
24
|
-
text=True,
|
|
25
|
-
timeout=15,
|
|
26
|
-
env=env,
|
|
27
|
-
)
|
|
28
|
-
return result.returncode, result.stdout, result.stderr
|
|
29
|
-
except subprocess.TimeoutExpired:
|
|
30
|
-
return -1, "", "TimeoutExpired: Code took too long to run."
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{auto_code_fixer-0.2.5 → auto_code_fixer-0.2.6}/auto_code_fixer.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|