pyfixit-cli 0.1.0__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.
pyfixit/__init__.py ADDED
File without changes
pyfixit/cleaner.py ADDED
@@ -0,0 +1,62 @@
1
+ from pathlib import Path
2
+
3
+ from pyfixit.requirements_parser import parse_requirement
4
+ from pyfixit.package_names import get_import_name
5
+
6
+
7
+ def clean_requirements(project_directory=".", apply=False):
8
+ project_directory = Path(project_directory)
9
+
10
+ requirements_file = project_directory / "requirements.txt"
11
+
12
+ if not requirements_file.exists():
13
+ return []
14
+
15
+ lines = requirements_file.read_text(
16
+ encoding="utf-8"
17
+ ).splitlines()
18
+
19
+ from pyfixit.imports import get_project_imports
20
+
21
+ imports = get_project_imports(project_directory)
22
+
23
+ kept = []
24
+ removed = []
25
+
26
+ for line in lines:
27
+
28
+ original_line = line
29
+ line = line.strip()
30
+
31
+ if not line:
32
+ kept.append(original_line)
33
+ continue
34
+
35
+ if line.startswith("#"):
36
+ kept.append(original_line)
37
+ continue
38
+
39
+ parsed = parse_requirement(line)
40
+
41
+ if parsed is None:
42
+ kept.append(original_line)
43
+ continue
44
+
45
+ package_name = parsed["package"]
46
+
47
+ import_name = get_import_name(package_name)
48
+
49
+ if import_name not in imports:
50
+ removed.append(original_line)
51
+ else:
52
+ kept.append(original_line)
53
+
54
+ # Only modify requirements.txt when --apply is used
55
+ if apply and removed:
56
+
57
+ requirements_file.write_text(
58
+ "\n".join(kept) + "\n",
59
+ encoding="utf-8"
60
+ )
61
+
62
+ return removed
pyfixit/cli.py ADDED
@@ -0,0 +1,265 @@
1
+ import argparse
2
+ import json
3
+ from pyfixit.diagnostics import diagnose_project, get_fix_suggestion
4
+ from pyfixit.cleaner import clean_requirements
5
+
6
+
7
+ def main():
8
+
9
+ parser = argparse.ArgumentParser(
10
+ description="Diagnose common Python import and dependency problems."
11
+ )
12
+
13
+ parser.add_argument(
14
+ "command",
15
+ nargs="?",
16
+ default="diagnose",
17
+ help="Command to run"
18
+ )
19
+
20
+ parser.add_argument(
21
+ "project_directory",
22
+ nargs="?",
23
+ default=".",
24
+ help="Python project directory"
25
+ )
26
+
27
+ parser.add_argument(
28
+ "--apply",
29
+ action="store_true",
30
+ help="Apply cleanup changes"
31
+ )
32
+
33
+ parser.add_argument(
34
+ "--json",
35
+ action="store_true",
36
+ help="Output diagnosis as JSON"
37
+ )
38
+
39
+ parser.add_argument(
40
+ "--version",
41
+ action="version",
42
+ version="PyFixIt 0.1.0"
43
+ )
44
+
45
+ args = parser.parse_args()
46
+
47
+ # ------------------------------------------------
48
+ # Support: pyfixit .
49
+ # ------------------------------------------------
50
+
51
+ if args.command not in ["diagnose", "clean", "check"]:
52
+ args.project_directory = args.command
53
+ args.command = "diagnose"
54
+
55
+ # ------------------------------------------------
56
+ # CLEAN
57
+ # ------------------------------------------------
58
+
59
+ if args.command == "clean":
60
+
61
+ print("PyFixIt v0.1.0")
62
+ print("Python Project Troubleshooter")
63
+ print()
64
+
65
+ removed = clean_requirements(
66
+ args.project_directory,
67
+ apply=args.apply
68
+ )
69
+
70
+ print("Cleaning requirements.txt")
71
+ print("------------------------------")
72
+
73
+ if len(removed) == 0:
74
+ print("Nothing to remove.")
75
+
76
+ elif args.apply:
77
+
78
+ for dependency in removed:
79
+ print(f"[REMOVED] {dependency}")
80
+
81
+ print()
82
+ print(f"{len(removed)} dependencies removed.")
83
+
84
+ else:
85
+
86
+ for dependency in removed:
87
+ print(f"[REMOVE] {dependency}")
88
+
89
+ print()
90
+ print(
91
+ f"{len(removed)} unused dependencies found."
92
+ )
93
+
94
+ print(
95
+ "Run 'pyfixit clean --apply' to remove them."
96
+ )
97
+
98
+ return
99
+
100
+
101
+
102
+ # ------------------------------------------------
103
+ # CHECK
104
+ # ------------------------------------------------
105
+
106
+ if args.command == "check":
107
+
108
+ print("PyFixIt v0.1.0")
109
+ print("Python Project Troubleshooter")
110
+ print()
111
+
112
+ result = diagnose_project(
113
+ args.project_directory
114
+ )
115
+
116
+ dependency_problems = 0
117
+
118
+ for item in result["dependencies"]:
119
+
120
+ if item["status"] != "ok":
121
+ dependency_problems += 1
122
+
123
+ unused_count = len(
124
+ result["unused_dependencies"]
125
+ )
126
+
127
+ print("Checking project...")
128
+ print()
129
+
130
+ if dependency_problems == 0:
131
+ print("[OK] No dependency problems found")
132
+ else:
133
+ print(
134
+ f"[FAIL] {dependency_problems} "
135
+ f"dependency problems found"
136
+ )
137
+
138
+ if unused_count == 0:
139
+ print("[OK] No unused dependencies found")
140
+ else:
141
+ print(
142
+ f"[FAIL] {unused_count} "
143
+ f"unused dependencies found"
144
+ )
145
+
146
+ print()
147
+
148
+ if dependency_problems > 0 or unused_count > 0:
149
+ raise SystemExit(1)
150
+
151
+ raise SystemExit(0)
152
+
153
+
154
+
155
+ # ------------------------------------------------
156
+ # DIAGNOSE
157
+ # ------------------------------------------------
158
+
159
+ result = diagnose_project(
160
+ args.project_directory
161
+ )
162
+
163
+ if args.json:
164
+ print(json.dumps(result, indent=2))
165
+
166
+ has_problems = any(
167
+ item["status"] != "ok"
168
+ for item in result["dependencies"]
169
+ )
170
+
171
+ if len(result["unused_dependencies"]) > 0:
172
+ has_problems = True
173
+
174
+ if has_problems:
175
+ raise SystemExit(1)
176
+
177
+ raise SystemExit(0)
178
+
179
+ print("PyFixIt v0.1.0")
180
+ print("Python Project Troubleshooter")
181
+ print()
182
+
183
+ print("Dependencies")
184
+ print("------------------------------")
185
+
186
+ has_problems = False
187
+
188
+ for item in result["dependencies"]:
189
+
190
+ status = item["status"]
191
+
192
+ if status == "ok":
193
+ print(f"[OK] {item['module']}")
194
+ continue
195
+
196
+ has_problems = True
197
+
198
+ if status == "version_conflict":
199
+
200
+ print(
201
+ f"[VERSION CONFLICT] {item['module']}"
202
+ )
203
+
204
+ print(
205
+ f" Required: "
206
+ f"{item['operator']}{item['required_version']}"
207
+ )
208
+
209
+ print(
210
+ f" Installed: "
211
+ f"{item['installed_version']}"
212
+ )
213
+
214
+ print(
215
+ f" Fix: "
216
+ f"{get_fix_suggestion(item)}"
217
+ )
218
+
219
+ print()
220
+
221
+ continue
222
+
223
+ print(f"[PROBLEM] {item['module']}")
224
+ print(f" Status: {status}")
225
+ print(
226
+ f" Fix: "
227
+ f"{get_fix_suggestion(item)}"
228
+ )
229
+
230
+ print()
231
+
232
+ print("Unused dependencies")
233
+ print("------------------------------")
234
+
235
+ unused = result["unused_dependencies"]
236
+
237
+ if len(unused) == 0:
238
+
239
+ print("None")
240
+
241
+ else:
242
+
243
+ has_problems = True
244
+
245
+ for dependency in unused:
246
+
247
+ print(f"[UNUSED] {dependency}")
248
+
249
+ print(
250
+ f" Fix: Remove "
251
+ f"'{dependency}' from requirements.txt"
252
+ )
253
+
254
+ # ------------------------------------------------
255
+ # EXIT CODE
256
+ # ------------------------------------------------
257
+
258
+ if has_problems:
259
+ raise SystemExit(1)
260
+
261
+ raise SystemExit(0)
262
+
263
+
264
+ if __name__ == "__main__":
265
+ main()
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+ from importlib.metadata import version, PackageNotFoundError
3
+
4
+
5
+ def find_requirements_file():
6
+ requirements_file = Path("requirements.txt")
7
+
8
+ if requirements_file.exists():
9
+ return requirements_file
10
+
11
+ return None
12
+
13
+
14
+ def read_requirements(requirements_file):
15
+ dependencies = []
16
+
17
+ with open(requirements_file, "r", encoding="utf-8") as file:
18
+ for line in file:
19
+ line = line.strip()
20
+
21
+ if line and not line.startswith("#"):
22
+ dependencies.append(line)
23
+
24
+ return dependencies
25
+
26
+
27
+ def is_package_installed(package_name):
28
+ try:
29
+ version(package_name)
30
+ return True
31
+ except PackageNotFoundError:
32
+ return False
pyfixit/diagnostics.py ADDED
@@ -0,0 +1,294 @@
1
+ import importlib.util
2
+ from importlib.metadata import version, PackageNotFoundError
3
+ from pathlib import Path
4
+
5
+ from pyfixit.package_names import get_import_name, get_package_name
6
+ from pyfixit.requirements_parser import parse_requirement, check_version
7
+
8
+
9
+ def check_dependency_declared(package_name, dependencies):
10
+ for dependency in dependencies:
11
+
12
+ parsed = parse_requirement(dependency)
13
+
14
+ if parsed is None:
15
+ continue
16
+
17
+ if parsed["package"].lower() == package_name.lower():
18
+ return True
19
+
20
+ return False
21
+
22
+
23
+ def check_dependency_installed(module_name):
24
+ try:
25
+ spec = importlib.util.find_spec(module_name)
26
+
27
+ if spec is None:
28
+ return False
29
+
30
+ return True
31
+
32
+ except ModuleNotFoundError:
33
+ return False
34
+
35
+
36
+ def get_installed_version(package_name):
37
+ try:
38
+ return version(package_name)
39
+
40
+ except PackageNotFoundError:
41
+ return None
42
+
43
+
44
+ def diagnose_dependency(module_name, dependencies):
45
+
46
+ import_name = module_name
47
+ package_name = get_package_name(module_name)
48
+
49
+ declared = False
50
+ required_version = None
51
+ operator = None
52
+
53
+ for dependency in dependencies:
54
+
55
+ parsed = parse_requirement(dependency)
56
+
57
+ if parsed is None:
58
+ continue
59
+
60
+ declared_package = parsed["package"]
61
+
62
+ if declared_package.lower() == package_name.lower():
63
+
64
+ declared = True
65
+ required_version = parsed["version"]
66
+ operator = parsed["operator"]
67
+
68
+ break
69
+
70
+ installed = check_dependency_installed(
71
+ import_name
72
+ )
73
+
74
+ installed_version = None
75
+
76
+ if installed:
77
+ installed_version = get_installed_version(
78
+ package_name
79
+ )
80
+
81
+ if not declared and not installed:
82
+
83
+ status = "missing_declaration_and_installation"
84
+
85
+ elif declared and not installed:
86
+
87
+ status = "missing_installation"
88
+
89
+ elif not declared and installed:
90
+
91
+ status = "missing_declaration"
92
+
93
+ elif required_version is not None:
94
+
95
+ if check_version(
96
+ installed_version,
97
+ operator,
98
+ required_version
99
+ ):
100
+
101
+ status = "ok"
102
+
103
+ else:
104
+
105
+ status = "version_conflict"
106
+
107
+ else:
108
+
109
+ status = "ok"
110
+
111
+ return {
112
+ "module": module_name,
113
+ "package_name": package_name,
114
+ "import_name": import_name,
115
+ "declared": declared,
116
+ "installed": installed,
117
+ "installed_version": installed_version,
118
+ "required_version": required_version,
119
+ "operator": operator,
120
+ "status": status
121
+ }
122
+
123
+
124
+ def diagnose_dependencies(modules, dependencies):
125
+
126
+ results = []
127
+
128
+ for module in modules:
129
+
130
+ result = diagnose_dependency(
131
+ module,
132
+ dependencies
133
+ )
134
+
135
+ results.append(result)
136
+
137
+ return results
138
+
139
+
140
+ def read_requirements(project_directory="."):
141
+
142
+ requirements_file = Path(
143
+ project_directory
144
+ ) / "requirements.txt"
145
+
146
+ if not requirements_file.exists():
147
+ return []
148
+
149
+ dependencies = []
150
+
151
+ with open(
152
+ requirements_file,
153
+ "r",
154
+ encoding="utf-8"
155
+ ) as file:
156
+
157
+ for line in file:
158
+
159
+ line = line.strip()
160
+
161
+ if not line:
162
+ continue
163
+
164
+ if line.startswith("#"):
165
+ continue
166
+
167
+ dependencies.append(line)
168
+
169
+ return dependencies
170
+
171
+
172
+ def find_unused_dependencies(imports, dependencies):
173
+
174
+ unused = []
175
+
176
+ for dependency in dependencies:
177
+
178
+ parsed = parse_requirement(dependency)
179
+
180
+ if parsed is None:
181
+ continue
182
+
183
+ package_name = parsed["package"]
184
+
185
+ import_name = get_import_name(
186
+ package_name
187
+ )
188
+
189
+ if import_name not in imports:
190
+
191
+ unused.append(dependency)
192
+
193
+ return unused
194
+
195
+
196
+ def diagnose_project(project_directory="."):
197
+
198
+ from pyfixit.imports import get_project_imports
199
+ from pyfixit.module_classifier import classify_modules
200
+
201
+ imports = get_project_imports(
202
+ project_directory
203
+ )
204
+
205
+ classified = classify_modules(
206
+ imports,
207
+ project_directory
208
+ )
209
+
210
+ declared_dependencies = read_requirements(
211
+ project_directory
212
+ )
213
+
214
+ third_party_modules = classified[
215
+ "third_party"
216
+ ]
217
+
218
+ results = diagnose_dependencies(
219
+ third_party_modules,
220
+ declared_dependencies
221
+ )
222
+
223
+ unused_dependencies = find_unused_dependencies(
224
+ imports,
225
+ declared_dependencies
226
+ )
227
+
228
+ return {
229
+ "dependencies": results,
230
+ "unused_dependencies": unused_dependencies
231
+ }
232
+
233
+
234
+ def get_fix_suggestion(result):
235
+
236
+ status = result["status"]
237
+
238
+ module = result["module"]
239
+
240
+ package_name = result.get(
241
+ "package_name",
242
+ module
243
+ )
244
+
245
+ if status == "missing_installation":
246
+
247
+ return f"pip install {package_name}"
248
+
249
+ if status == "missing_declaration_and_installation":
250
+
251
+ return (
252
+ f"Install it with: pip install {package_name}\n"
253
+ f"Then add '{package_name}' to requirements.txt"
254
+ )
255
+
256
+ if status == "missing_declaration":
257
+
258
+ return (
259
+ f"Add '{package_name}' to requirements.txt"
260
+ )
261
+
262
+ if status == "version_conflict":
263
+
264
+ required_version = result["required_version"]
265
+ operator = result["operator"]
266
+
267
+ return (
268
+ f"pip install "
269
+ f"{package_name}{operator}{required_version}"
270
+ )
271
+
272
+ if status == "ok":
273
+
274
+ return "No action needed"
275
+
276
+ return "No fix available"
277
+
278
+
279
+
280
+ def get_diagnosis_summary(diagnosis):
281
+ dependencies = diagnosis["dependencies"]
282
+ unused_dependencies = diagnosis["unused_dependencies"]
283
+
284
+ problems = 0
285
+
286
+ for dependency in dependencies:
287
+ if dependency["status"] != "ok":
288
+ problems += 1
289
+
290
+ return {
291
+ "total_dependencies": len(dependencies),
292
+ "problems": problems,
293
+ "unused_dependencies": len(unused_dependencies)
294
+ }
pyfixit/environment.py ADDED
@@ -0,0 +1,21 @@
1
+ import sys
2
+
3
+
4
+ def get_python_version():
5
+ return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
6
+
7
+
8
+ def get_python_executable():
9
+ return sys.executable
10
+
11
+
12
+ def is_virtual_environment():
13
+ return sys.prefix != sys.base_prefix
14
+
15
+
16
+ def get_environment_info():
17
+ return {
18
+ "python_version": get_python_version(),
19
+ "python_executable": get_python_executable(),
20
+ "virtual_environment": is_virtual_environment()
21
+ }