auto-code-fixer 0.2.5__py3-none-any.whl → 0.2.6__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.
@@ -1,2 +1 @@
1
- __version__ = "0.2.5"
2
-
1
+ __version__ = "0.2.6"
auto_code_fixer/cli.py CHANGED
@@ -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
- current_code = f.read() # 🔑 SOURCE OF TRUTH
25
+ original_code = f.read()
28
26
 
29
27
  temp_dir = tempfile.mkdtemp(prefix="codefix_")
30
- temp_file = os.path.join(temp_dir, "temp.py")
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
- # ✅ WRITE FROM MEMORY, NOT TEMP FILE
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, project_root):
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
- current_code,
73
+ open(temp_file).read(),
85
74
  stderr,
86
75
  api_key,
87
76
  )
88
77
 
89
- if import_hijacked(current_code, fixed_code):
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(current_code)
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("--ask", action="store_true")
156
- ask_group.add_argument("--no-ask", action="store_true")
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("--verbose", action="store_true")
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
- # 🔑 NORMALIZE ROOT PATH ONCE
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)
auto_code_fixer/fixer.py CHANGED
@@ -1,60 +1,55 @@
1
- from openai import OpenAI
1
+ import os
2
+ import time
2
3
  from decouple import config
4
+ from openai import OpenAI
3
5
 
4
- client = OpenAI(api_key=config("chat_gpt_secret_key"))
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
+ )
6
13
 
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
- """
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
+ )
12
19
 
13
- system_prompt = """
14
- You are a senior Python engineer fixing code.
20
+ return OpenAI(api_key=key)
15
21
 
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
22
 
26
- user_prompt = f"""
27
- The following Python code has an error.
23
+ def fix_code_with_gpt(original_code, error_log, api_key=None):
24
+ client = get_openai_client(api_key)
28
25
 
29
- CODE:
26
+ prompt = f"""
27
+ You are a senior Python engineer.
28
+ Fix the following Python code so it runs without errors.
29
+
30
+ Code:
30
31
  {original_code}
31
32
 
32
- ERROR:
33
+ Error:
33
34
  {error_log}
34
35
 
35
- Fix the code according to the rules.
36
+ Return ONLY the full corrected Python code.
36
37
  """
37
38
 
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()
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
+ )
49
47
 
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])
48
+ fixed_code = response.choices[0].message.content.strip()
55
49
 
56
- return fixed_code.strip()
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])
57
54
 
58
- except Exception as e:
59
- print(f"Error calling OpenAI API: {e}")
60
- return original_code
55
+ return fixed_code.strip()
@@ -1,25 +1,16 @@
1
- import os
2
1
  import re
3
- import sys
4
2
  import subprocess
3
+ import sys
5
4
 
6
5
 
7
- def check_and_install_missing_lib(stderr, project_root):
6
+ def check_and_install_missing_lib(stderr):
8
7
  match = re.search(r"No module named '([^']+)'", stderr)
9
8
  if not match:
10
9
  return False
11
10
 
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
-
11
+ lib = match.group(1)
19
12
  try:
20
- subprocess.check_call(
21
- [sys.executable, "-m", "pip", "install", missing]
22
- )
13
+ subprocess.check_call([sys.executable, "-m", "pip", "install", lib])
23
14
  return True
24
15
  except subprocess.CalledProcessError:
25
16
  return False
auto_code_fixer/runner.py CHANGED
@@ -1,29 +1,13 @@
1
1
  import subprocess
2
- import os
3
- import sys
4
2
 
5
3
 
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
-
4
+ def run_code(path_to_code):
20
5
  try:
21
6
  result = subprocess.run(
22
- [sys.executable, script_path],
7
+ ["python3", path_to_code],
23
8
  capture_output=True,
24
9
  text=True,
25
10
  timeout=15,
26
- env=env,
27
11
  )
28
12
  return result.returncode, result.stdout, result.stderr
29
13
  except subprocess.TimeoutExpired:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auto-code-fixer
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Automatically fix Python code using ChatGPT
5
5
  Author-email: Arif Shah <ashah7775@gmail.com>
6
6
  License: MIT
@@ -0,0 +1,12 @@
1
+ auto_code_fixer/__init__.py,sha256=Oz5HbwHMyE87nmwV80AZzpkJPf-wBg7eDuJr_BXZkhU,22
2
+ auto_code_fixer/cli.py,sha256=xUVwCBTHxqxicAFqU_rbVigxP8fPL0L9m73NAUIWYy0,4147
3
+ auto_code_fixer/fixer.py,sha256=aoPAzUmaMSSEO4kpJ6PMWOOpWhnd7W1ikfFr9fQsHrg,1308
4
+ auto_code_fixer/installer.py,sha256=25rlboOI6SgJyVWDQ1NFGHukZkpIREsHa2abMYAqq7A,378
5
+ auto_code_fixer/runner.py,sha256=n5GR03-ZUnti0L9do0_BTsislrFzARjuP9lm_iaWsKc,388
6
+ auto_code_fixer/utils.py,sha256=ovWyNf3B7xMVq8qjylrPzNhrmLFaHKFF1G0vIp_GGSI,1425
7
+ auto_code_fixer-0.2.6.dist-info/licenses/LICENSE,sha256=hgchJNa26tjXuLztwSUDbYQxNLnAPnLk6kDXNIkC8xc,1066
8
+ auto_code_fixer-0.2.6.dist-info/METADATA,sha256=kGzKHtewk_p-y9gChVamngU9PScAIELAH4Edl5RNrHs,1522
9
+ auto_code_fixer-0.2.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ auto_code_fixer-0.2.6.dist-info/entry_points.txt,sha256=a-j2rkfwkrhXZ5Qbz_6_gwk6Bj7nijYR1DALjWp5Myk,61
11
+ auto_code_fixer-0.2.6.dist-info/top_level.txt,sha256=qUk1qznb6Qxqmxy2A3z_5dpOZlmNKHwUiLuJwH-CrAk,16
12
+ auto_code_fixer-0.2.6.dist-info/RECORD,,
auto_code_fixer/guard.py DELETED
@@ -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,13 +0,0 @@
1
- auto_code_fixer/__init__.py,sha256=S5Nlc-a2E4VkueiTguk-_xKzhUnSyTj5fGeoME1VKts,23
2
- auto_code_fixer/cli.py,sha256=cg86GiEuihOHnzqlP0ocEdmpgFtDigQ20pY7oazH-_c,5049
3
- auto_code_fixer/fixer.py,sha256=Lqb9IQiSvXIo168wv9cE3uyM3F_FVmu681PnnAmCFLI,1767
4
- auto_code_fixer/guard.py,sha256=Qfyqj2H1VeqBHFQxBkMy-L7Rz6ULXMV3uPAz7IMyTRs,489
5
- auto_code_fixer/installer.py,sha256=FYPD2r3zJ24MOPDlB_HWOTr2lTXKFLc73vMiQIChsCk,599
6
- auto_code_fixer/runner.py,sha256=EOQERj9lspNHpDd5QOvoB0XqbCRCed1vu1EAb6nJZsE,763
7
- auto_code_fixer/utils.py,sha256=ovWyNf3B7xMVq8qjylrPzNhrmLFaHKFF1G0vIp_GGSI,1425
8
- auto_code_fixer-0.2.5.dist-info/licenses/LICENSE,sha256=hgchJNa26tjXuLztwSUDbYQxNLnAPnLk6kDXNIkC8xc,1066
9
- auto_code_fixer-0.2.5.dist-info/METADATA,sha256=fJDgSRlKDTuBPzlNwpK3BIMwk1l7_uSwnTujsv3ex9c,1522
10
- auto_code_fixer-0.2.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- auto_code_fixer-0.2.5.dist-info/entry_points.txt,sha256=a-j2rkfwkrhXZ5Qbz_6_gwk6Bj7nijYR1DALjWp5Myk,61
12
- auto_code_fixer-0.2.5.dist-info/top_level.txt,sha256=qUk1qznb6Qxqmxy2A3z_5dpOZlmNKHwUiLuJwH-CrAk,16
13
- auto_code_fixer-0.2.5.dist-info/RECORD,,