.pydev-tools / _ast / migration.py
.pydev-tools / _ast / migration.py
#!/usr/bin/env python3
"""Audit source for deprecated / outdated patterns and produce a migration plan.
Supports pattern sets:
- python2-compatible : basestring, xrange, iteritems/itervalues/iterkeys, has_key, unicode()
- deprecated-imports : imp, distutils, cStringIO, StringIO, time.clock, asyncio.get_event_loop()
- outdated-packages : best-effort; prints a hint to run `pip list` for version checks
Emits JSON with findings (file:line, matched text, suggested replacement).
Usage: migration.py <pattern> <target_path> [output_dir]
"""
import json, os, re, sys
PATTERNS = {
"python2-compatible": [
(r"\bbasestring\b", "str"),
(r"\bxrange\b", "range"),
(r"\biteritems\(\)", "items()"),
(r"\bitervalues\(\)", "values()"),
(r"\biterkeys\(\)", "keys()"),
(r"\.has_key\(", ".get("),
(r"\bunicode\(", "str("),
],
"deprecated-imports": [
(r"\bimport\s+imp\b", "from importlib import import_module"),
(r"\bfrom\s+distutils", "use setuptools or stdlib equivalents"),
(r"\bcStringIO\b", "io.StringIO"),
(r"\bStringIO\b", "io.StringIO"),
],
}
if len(sys.argv) < 2:
json.dump({"error": "missing pattern", "availablePatterns": list(PATTERNS.keys())}, sys.stdout)
sys.exit(0)
last_two = sys.argv[-2:]
patterns_arg = " ".join(sys.argv[: len(sys.argv) - 2]) if len(sys.argv) > 3 else (sys.argv[1] if len(sys.argv) > 1 else "")
target = last_two[0].strip() if len(last_two) > 0 else "."
out_dir = last_two[1].strip() if len(last_two) > 1 else "."
if "," in patterns_arg:
selected = [p.strip() for p in patterns_arg.replace(",", " ").split() if p.strip()]
else:
selected = [patterns_arg]
for name in selected:
if name not in PATTERNS:
json.dump({"error": f"unknown pattern '{name}'", "availablePatterns": list(PATTERNS.keys())}, sys.stdout)
sys.exit(0)
def walk_py(directory):
for root, _dirs, names in os.walk(directory):
for fn in sorted(names):
if fn.endswith(".py"):
yield os.path.join(root, fn)
findings = []
for f in walk_py(target):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for line_no, raw in enumerate(text.split("\n"), start=1):
for pattern, suggestion in PATTERNS[name]:
if re.search(pattern, raw):
findings.append({
"pattern": name,
"file": f,
"line": line_no,
"matched": raw.strip(),
"suggestion": suggestion,
})
# best-effort outdated-packages hint
if "outdated-packages" in selected:
findings.append({"pattern": "outdated-packages", "file": "-", "line": 0,
"matched": "pip list", "suggestion": "Compare installed versions against PyPI for latest releases."})
md = ["# Migration Audit Report", "", f"Patterns: {', '.join(selected)}", ""]
for it in findings:
md.append(f"- **[{it['pattern']}]** `{it['file']}:{it['line']}` — `{it['matched']}`")
md.append(f" → {it['suggestion']}")
md_path = os.path.join(out_dir, "migration_report.md")
try:
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(md))
except OSError:
pass
json.dump({"findings": findings, "summary": {"total": len(findings), "byPattern": {p: sum(1 for f in findings if f["pattern"] == p) for p in selected}}}, sys.stdout, indent=2)
#!/usr/bin/env python3
"""Audit source for deprecated / outdated patterns and produce a migration plan.
Supports pattern sets:
- python2-compatible : basestring, xrange, iteritems/itervalues/iterkeys, has_key, unicode()
- deprecated-imports : imp, distutils, cStringIO, StringIO, time.clock, asyncio.get_event_loop()
- outdated-packages : best-effort; prints a hint to run `pip list` for version checks
Emits JSON with findings (file:line, matched text, suggested replacement).
Usage: migration.py <pattern> <target_path> [output_dir]
"""
import json, os, re, sys
PATTERNS = {
"python2-compatible": [
(r"\bbasestring\b", "str"),
(r"\bxrange\b", "range"),
(r"\biteritems\(\)", "items()"),
(r"\bitervalues\(\)", "values()"),
(r"\biterkeys\(\)", "keys()"),
(r"\.has_key\(", ".get("),
(r"\bunicode\(", "str("),
],
"deprecated-imports": [
(r"\bimport\s+imp\b", "from importlib import import_module"),
(r"\bfrom\s+distutils", "use setuptools or stdlib equivalents"),
(r"\bcStringIO\b", "io.StringIO"),
(r"\bStringIO\b", "io.StringIO"),
],
}
if len(sys.argv) < 2:
json.dump({"error": "missing pattern", "availablePatterns": list(PATTERNS.keys())}, sys.stdout)
sys.exit(0)
last_two = sys.argv[-2:]
patterns_arg = " ".join(sys.argv[: len(sys.argv) - 2]) if len(sys.argv) > 3 else (sys.argv[1] if len(sys.argv) > 1 else "")
target = last_two[0].strip() if len(last_two) > 0 else "."
out_dir = last_two[1].strip() if len(last_two) > 1 else "."
if "," in patterns_arg:
selected = [p.strip() for p in patterns_arg.replace(",", " ").split() if p.strip()]
else:
selected = [patterns_arg]
for name in selected:
if name not in PATTERNS:
json.dump({"error": f"unknown pattern '{name}'", "availablePatterns": list(PATTERNS.keys())}, sys.stdout)
sys.exit(0)
def walk_py(directory):
for root, _dirs, names in os.walk(directory):
for fn in sorted(names):
if fn.endswith(".py"):
yield os.path.join(root, fn)
findings = []
for f in walk_py(target):
try:
text = open(f, encoding="utf-8").read()
except OSError:
continue
for line_no, raw in enumerate(text.split("\n"), start=1):
for pattern, suggestion in PATTERNS[name]:
if re.search(pattern, raw):
findings.append({
"pattern": name,
"file": f,
"line": line_no,
"matched": raw.strip(),
"suggestion": suggestion,
})
# best-effort outdated-packages hint
if "outdated-packages" in selected:
findings.append({"pattern": "outdated-packages", "file": "-", "line": 0,
"matched": "pip list", "suggestion": "Compare installed versions against PyPI for latest releases."})
md = ["# Migration Audit Report", "", f"Patterns: {', '.join(selected)}", ""]
for it in findings:
md.append(f"- **[{it['pattern']}]** `{it['file']}:{it['line']}` — `{it['matched']}`")
md.append(f" → {it['suggestion']}")
md_path = os.path.join(out_dir, "migration_report.md")
try:
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(md))
except OSError:
pass
json.dump({"findings": findings, "summary": {"total": len(findings), "byPattern": {p: sum(1 for f in findings if f["pattern"] == p) for p in selected}}}, sys.stdout, indent=2)