codeshift 0.2.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.
- codeshift/__init__.py +8 -0
- codeshift/analyzer/__init__.py +5 -0
- codeshift/analyzer/risk_assessor.py +388 -0
- codeshift/api/__init__.py +1 -0
- codeshift/api/auth.py +182 -0
- codeshift/api/config.py +73 -0
- codeshift/api/database.py +215 -0
- codeshift/api/main.py +103 -0
- codeshift/api/models/__init__.py +55 -0
- codeshift/api/models/auth.py +108 -0
- codeshift/api/models/billing.py +92 -0
- codeshift/api/models/migrate.py +42 -0
- codeshift/api/models/usage.py +116 -0
- codeshift/api/routers/__init__.py +5 -0
- codeshift/api/routers/auth.py +440 -0
- codeshift/api/routers/billing.py +395 -0
- codeshift/api/routers/migrate.py +304 -0
- codeshift/api/routers/usage.py +291 -0
- codeshift/api/routers/webhooks.py +289 -0
- codeshift/cli/__init__.py +5 -0
- codeshift/cli/commands/__init__.py +7 -0
- codeshift/cli/commands/apply.py +352 -0
- codeshift/cli/commands/auth.py +842 -0
- codeshift/cli/commands/diff.py +221 -0
- codeshift/cli/commands/scan.py +368 -0
- codeshift/cli/commands/upgrade.py +436 -0
- codeshift/cli/commands/upgrade_all.py +518 -0
- codeshift/cli/main.py +221 -0
- codeshift/cli/quota.py +210 -0
- codeshift/knowledge/__init__.py +50 -0
- codeshift/knowledge/cache.py +167 -0
- codeshift/knowledge/generator.py +231 -0
- codeshift/knowledge/models.py +151 -0
- codeshift/knowledge/parser.py +270 -0
- codeshift/knowledge/sources.py +388 -0
- codeshift/knowledge_base/__init__.py +17 -0
- codeshift/knowledge_base/loader.py +102 -0
- codeshift/knowledge_base/models.py +110 -0
- codeshift/migrator/__init__.py +23 -0
- codeshift/migrator/ast_transforms.py +256 -0
- codeshift/migrator/engine.py +395 -0
- codeshift/migrator/llm_migrator.py +320 -0
- codeshift/migrator/transforms/__init__.py +19 -0
- codeshift/migrator/transforms/fastapi_transformer.py +174 -0
- codeshift/migrator/transforms/pandas_transformer.py +236 -0
- codeshift/migrator/transforms/pydantic_v1_to_v2.py +637 -0
- codeshift/migrator/transforms/requests_transformer.py +218 -0
- codeshift/migrator/transforms/sqlalchemy_transformer.py +175 -0
- codeshift/scanner/__init__.py +6 -0
- codeshift/scanner/code_scanner.py +352 -0
- codeshift/scanner/dependency_parser.py +473 -0
- codeshift/utils/__init__.py +5 -0
- codeshift/utils/api_client.py +266 -0
- codeshift/utils/cache.py +318 -0
- codeshift/utils/config.py +71 -0
- codeshift/utils/llm_client.py +221 -0
- codeshift/validator/__init__.py +6 -0
- codeshift/validator/syntax_checker.py +183 -0
- codeshift/validator/test_runner.py +224 -0
- codeshift-0.2.0.dist-info/METADATA +326 -0
- codeshift-0.2.0.dist-info/RECORD +65 -0
- codeshift-0.2.0.dist-info/WHEEL +5 -0
- codeshift-0.2.0.dist-info/entry_points.txt +2 -0
- codeshift-0.2.0.dist-info/licenses/LICENSE +21 -0
- codeshift-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Base transformer infrastructure for AST-based code migrations."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import libcst as cst
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TransformStatus(Enum):
|
|
11
|
+
"""Status of a transformation."""
|
|
12
|
+
|
|
13
|
+
SUCCESS = "success"
|
|
14
|
+
PARTIAL = "partial" # Some transforms applied, some failed
|
|
15
|
+
FAILED = "failed"
|
|
16
|
+
NO_CHANGES = "no_changes"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TransformChange:
|
|
21
|
+
"""Represents a single code change made by a transform."""
|
|
22
|
+
|
|
23
|
+
description: str
|
|
24
|
+
line_number: int
|
|
25
|
+
original: str
|
|
26
|
+
replacement: str
|
|
27
|
+
transform_name: str
|
|
28
|
+
confidence: float = 1.0 # 0.0 to 1.0
|
|
29
|
+
notes: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class TransformResult:
|
|
34
|
+
"""Result of applying transforms to a file."""
|
|
35
|
+
|
|
36
|
+
file_path: Path
|
|
37
|
+
status: TransformStatus
|
|
38
|
+
original_code: str
|
|
39
|
+
transformed_code: str
|
|
40
|
+
changes: list[TransformChange] = field(default_factory=list)
|
|
41
|
+
errors: list[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def has_changes(self) -> bool:
|
|
45
|
+
"""Check if any changes were made."""
|
|
46
|
+
return self.original_code != self.transformed_code
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def change_count(self) -> int:
|
|
50
|
+
"""Get the number of changes made."""
|
|
51
|
+
return len(self.changes)
|
|
52
|
+
|
|
53
|
+
def get_diff_lines(self) -> list[str]:
|
|
54
|
+
"""Get a simple diff representation."""
|
|
55
|
+
import difflib
|
|
56
|
+
|
|
57
|
+
original_lines = self.original_code.splitlines(keepends=True)
|
|
58
|
+
transformed_lines = self.transformed_code.splitlines(keepends=True)
|
|
59
|
+
|
|
60
|
+
diff = difflib.unified_diff(
|
|
61
|
+
original_lines,
|
|
62
|
+
transformed_lines,
|
|
63
|
+
fromfile=f"{self.file_path} (original)",
|
|
64
|
+
tofile=f"{self.file_path} (transformed)",
|
|
65
|
+
)
|
|
66
|
+
return list(diff)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BaseTransformer(cst.CSTTransformer):
|
|
70
|
+
"""Base class for LibCST transformers with change tracking."""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
super().__init__()
|
|
74
|
+
self.changes: list[TransformChange] = []
|
|
75
|
+
self.errors: list[str] = []
|
|
76
|
+
self._source_lines: list[str] = []
|
|
77
|
+
|
|
78
|
+
def set_source(self, source: str) -> None:
|
|
79
|
+
"""Set the source code for reference during transforms."""
|
|
80
|
+
self._source_lines = source.splitlines()
|
|
81
|
+
|
|
82
|
+
def record_change(
|
|
83
|
+
self,
|
|
84
|
+
description: str,
|
|
85
|
+
line_number: int,
|
|
86
|
+
original: str,
|
|
87
|
+
replacement: str,
|
|
88
|
+
transform_name: str,
|
|
89
|
+
confidence: float = 1.0,
|
|
90
|
+
notes: str | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Record a change made by the transformer."""
|
|
93
|
+
self.changes.append(
|
|
94
|
+
TransformChange(
|
|
95
|
+
description=description,
|
|
96
|
+
line_number=line_number,
|
|
97
|
+
original=original,
|
|
98
|
+
replacement=replacement,
|
|
99
|
+
transform_name=transform_name,
|
|
100
|
+
confidence=confidence,
|
|
101
|
+
notes=notes,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def record_error(self, error: str) -> None:
|
|
106
|
+
"""Record an error that occurred during transformation."""
|
|
107
|
+
self.errors.append(error)
|
|
108
|
+
|
|
109
|
+
def get_line(self, line_number: int) -> str:
|
|
110
|
+
"""Get a specific line from the source."""
|
|
111
|
+
if 0 < line_number <= len(self._source_lines):
|
|
112
|
+
return self._source_lines[line_number - 1]
|
|
113
|
+
return ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def transform_file(
|
|
117
|
+
file_path: Path,
|
|
118
|
+
transformer: BaseTransformer,
|
|
119
|
+
) -> TransformResult:
|
|
120
|
+
"""Transform a file using the given transformer.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
file_path: Path to the file to transform
|
|
124
|
+
transformer: The transformer to use
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
TransformResult with the original and transformed code
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
original_code = file_path.read_text()
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return TransformResult(
|
|
133
|
+
file_path=file_path,
|
|
134
|
+
status=TransformStatus.FAILED,
|
|
135
|
+
original_code="",
|
|
136
|
+
transformed_code="",
|
|
137
|
+
errors=[f"Failed to read file: {e}"],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return transform_code(original_code, file_path, transformer)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def transform_code(
|
|
144
|
+
source_code: str,
|
|
145
|
+
file_path: Path,
|
|
146
|
+
transformer: BaseTransformer,
|
|
147
|
+
) -> TransformResult:
|
|
148
|
+
"""Transform source code using the given transformer.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
source_code: The source code to transform
|
|
152
|
+
file_path: Path for reference in results
|
|
153
|
+
transformer: The transformer to use
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
TransformResult with the original and transformed code
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
tree = cst.parse_module(source_code)
|
|
160
|
+
except cst.ParserSyntaxError as e:
|
|
161
|
+
return TransformResult(
|
|
162
|
+
file_path=file_path,
|
|
163
|
+
status=TransformStatus.FAILED,
|
|
164
|
+
original_code=source_code,
|
|
165
|
+
transformed_code=source_code,
|
|
166
|
+
errors=[f"Failed to parse file: {e}"],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
transformer.set_source(source_code)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
transformed_tree = tree.visit(transformer)
|
|
173
|
+
transformed_code = transformed_tree.code
|
|
174
|
+
except Exception as e:
|
|
175
|
+
return TransformResult(
|
|
176
|
+
file_path=file_path,
|
|
177
|
+
status=TransformStatus.FAILED,
|
|
178
|
+
original_code=source_code,
|
|
179
|
+
transformed_code=source_code,
|
|
180
|
+
errors=[f"Transform failed: {e}"],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Determine status
|
|
184
|
+
if transformer.errors:
|
|
185
|
+
if transformer.changes:
|
|
186
|
+
status = TransformStatus.PARTIAL
|
|
187
|
+
else:
|
|
188
|
+
status = TransformStatus.FAILED
|
|
189
|
+
elif transformer.changes:
|
|
190
|
+
status = TransformStatus.SUCCESS
|
|
191
|
+
else:
|
|
192
|
+
status = TransformStatus.NO_CHANGES
|
|
193
|
+
|
|
194
|
+
return TransformResult(
|
|
195
|
+
file_path=file_path,
|
|
196
|
+
status=status,
|
|
197
|
+
original_code=source_code,
|
|
198
|
+
transformed_code=transformed_code,
|
|
199
|
+
changes=transformer.changes,
|
|
200
|
+
errors=transformer.errors,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def apply_transforms(
|
|
205
|
+
file_path: Path,
|
|
206
|
+
transformers: list[BaseTransformer],
|
|
207
|
+
) -> TransformResult:
|
|
208
|
+
"""Apply multiple transformers to a file in sequence.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
file_path: Path to the file to transform
|
|
212
|
+
transformers: List of transformers to apply in order
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Combined TransformResult
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
current_code = file_path.read_text()
|
|
219
|
+
except Exception as e:
|
|
220
|
+
return TransformResult(
|
|
221
|
+
file_path=file_path,
|
|
222
|
+
status=TransformStatus.FAILED,
|
|
223
|
+
original_code="",
|
|
224
|
+
transformed_code="",
|
|
225
|
+
errors=[f"Failed to read file: {e}"],
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
original_code = current_code
|
|
229
|
+
all_changes = []
|
|
230
|
+
all_errors = []
|
|
231
|
+
|
|
232
|
+
for transformer in transformers:
|
|
233
|
+
result = transform_code(current_code, file_path, transformer)
|
|
234
|
+
current_code = result.transformed_code
|
|
235
|
+
all_changes.extend(result.changes)
|
|
236
|
+
all_errors.extend(result.errors)
|
|
237
|
+
|
|
238
|
+
# Determine final status
|
|
239
|
+
if all_errors:
|
|
240
|
+
if all_changes:
|
|
241
|
+
status = TransformStatus.PARTIAL
|
|
242
|
+
else:
|
|
243
|
+
status = TransformStatus.FAILED
|
|
244
|
+
elif all_changes:
|
|
245
|
+
status = TransformStatus.SUCCESS
|
|
246
|
+
else:
|
|
247
|
+
status = TransformStatus.NO_CHANGES
|
|
248
|
+
|
|
249
|
+
return TransformResult(
|
|
250
|
+
file_path=file_path,
|
|
251
|
+
status=status,
|
|
252
|
+
original_code=original_code,
|
|
253
|
+
transformed_code=current_code,
|
|
254
|
+
changes=all_changes,
|
|
255
|
+
errors=all_errors,
|
|
256
|
+
)
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Migration engine with tiered approach."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from codeshift.knowledge import (
|
|
7
|
+
Confidence,
|
|
8
|
+
GeneratedKnowledgeBase,
|
|
9
|
+
is_tier_1_library,
|
|
10
|
+
)
|
|
11
|
+
from codeshift.migrator.ast_transforms import (
|
|
12
|
+
TransformChange,
|
|
13
|
+
TransformResult,
|
|
14
|
+
TransformStatus,
|
|
15
|
+
)
|
|
16
|
+
from codeshift.migrator.llm_migrator import LLMMigrator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MigrationEngine:
|
|
20
|
+
"""Orchestrates migrations using a tiered approach.
|
|
21
|
+
|
|
22
|
+
Tier 1: Deterministic AST transforms for well-known libraries
|
|
23
|
+
Tier 2: Knowledge base guided migration with LLM assistance
|
|
24
|
+
Tier 3: Pure LLM migration for unknown patterns
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
llm_migrator: LLMMigrator | None = None,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize the migration engine.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
llm_migrator: Optional LLM migrator instance.
|
|
35
|
+
"""
|
|
36
|
+
self.llm_migrator = llm_migrator or LLMMigrator()
|
|
37
|
+
|
|
38
|
+
def run_migration(
|
|
39
|
+
self,
|
|
40
|
+
code: str,
|
|
41
|
+
file_path: Path,
|
|
42
|
+
library: str,
|
|
43
|
+
old_version: str,
|
|
44
|
+
new_version: str,
|
|
45
|
+
knowledge_base: GeneratedKnowledgeBase | None = None,
|
|
46
|
+
progress_callback: Callable[[str], None] | None = None,
|
|
47
|
+
) -> TransformResult:
|
|
48
|
+
"""Run migration using the appropriate tier.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
code: Source code to migrate.
|
|
52
|
+
file_path: Path to the file being migrated.
|
|
53
|
+
library: Library being upgraded.
|
|
54
|
+
old_version: Current version.
|
|
55
|
+
new_version: Target version.
|
|
56
|
+
knowledge_base: Optional generated knowledge base.
|
|
57
|
+
progress_callback: Optional progress callback.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
TransformResult with migrated code.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def report(msg: str) -> None:
|
|
64
|
+
if progress_callback:
|
|
65
|
+
progress_callback(msg)
|
|
66
|
+
|
|
67
|
+
# Tier 1: Try deterministic AST transforms for known libraries
|
|
68
|
+
if is_tier_1_library(library):
|
|
69
|
+
report(f"Using Tier 1 (deterministic AST transforms) for {library}")
|
|
70
|
+
result = self._apply_tier1_transform(code, file_path, library)
|
|
71
|
+
|
|
72
|
+
if result.status == TransformStatus.SUCCESS:
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
# If partial success, try Tier 2/3 for remaining
|
|
76
|
+
if result.status == TransformStatus.PARTIAL:
|
|
77
|
+
report("Tier 1 partial - attempting Tier 2/3 for remaining changes")
|
|
78
|
+
# Use transformed code as base for next tier
|
|
79
|
+
code = result.transformed_code
|
|
80
|
+
|
|
81
|
+
# Tier 2: Knowledge base guided migration
|
|
82
|
+
if knowledge_base and knowledge_base.overall_confidence >= Confidence.MEDIUM:
|
|
83
|
+
report("Using Tier 2 (KB-guided migration)")
|
|
84
|
+
result = self._apply_tier2_transform(
|
|
85
|
+
code,
|
|
86
|
+
file_path,
|
|
87
|
+
library,
|
|
88
|
+
old_version,
|
|
89
|
+
new_version,
|
|
90
|
+
knowledge_base,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if result.status == TransformStatus.SUCCESS:
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
# Tier 3: Pure LLM migration
|
|
97
|
+
if self.llm_migrator.is_available:
|
|
98
|
+
report("Using Tier 3 (LLM-assisted migration)")
|
|
99
|
+
return self._apply_tier3_transform(
|
|
100
|
+
code,
|
|
101
|
+
file_path,
|
|
102
|
+
library,
|
|
103
|
+
old_version,
|
|
104
|
+
new_version,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# No migration possible
|
|
108
|
+
return TransformResult(
|
|
109
|
+
file_path=file_path,
|
|
110
|
+
status=TransformStatus.NO_CHANGES,
|
|
111
|
+
original_code=code,
|
|
112
|
+
transformed_code=code,
|
|
113
|
+
errors=["No migration method available"],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _apply_tier1_transform(
|
|
117
|
+
self,
|
|
118
|
+
code: str,
|
|
119
|
+
file_path: Path,
|
|
120
|
+
library: str,
|
|
121
|
+
) -> TransformResult:
|
|
122
|
+
"""Apply Tier 1 deterministic AST transforms.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
code: Source code to transform.
|
|
126
|
+
file_path: Path to the file.
|
|
127
|
+
library: Library being upgraded.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
TransformResult.
|
|
131
|
+
"""
|
|
132
|
+
# Import transformers dynamically based on library
|
|
133
|
+
transform_func = self._get_transform_func(library)
|
|
134
|
+
|
|
135
|
+
if transform_func is None:
|
|
136
|
+
return TransformResult(
|
|
137
|
+
file_path=file_path,
|
|
138
|
+
status=TransformStatus.NO_CHANGES,
|
|
139
|
+
original_code=code,
|
|
140
|
+
transformed_code=code,
|
|
141
|
+
errors=[f"No Tier 1 transformer for {library}"],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
transformed_code, changes = transform_func(code)
|
|
146
|
+
|
|
147
|
+
return TransformResult(
|
|
148
|
+
file_path=file_path,
|
|
149
|
+
status=TransformStatus.SUCCESS if changes else TransformStatus.NO_CHANGES,
|
|
150
|
+
original_code=code,
|
|
151
|
+
transformed_code=transformed_code,
|
|
152
|
+
changes=[
|
|
153
|
+
TransformChange(
|
|
154
|
+
description=c.description,
|
|
155
|
+
line_number=c.line_number,
|
|
156
|
+
original=c.original,
|
|
157
|
+
replacement=c.replacement,
|
|
158
|
+
transform_name=c.transform_name,
|
|
159
|
+
confidence=getattr(c, "confidence", 1.0),
|
|
160
|
+
)
|
|
161
|
+
for c in changes
|
|
162
|
+
],
|
|
163
|
+
)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
return TransformResult(
|
|
166
|
+
file_path=file_path,
|
|
167
|
+
status=TransformStatus.FAILED,
|
|
168
|
+
original_code=code,
|
|
169
|
+
transformed_code=code,
|
|
170
|
+
errors=[f"Tier 1 transform failed: {e}"],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _apply_tier2_transform(
|
|
174
|
+
self,
|
|
175
|
+
code: str,
|
|
176
|
+
file_path: Path,
|
|
177
|
+
library: str,
|
|
178
|
+
old_version: str,
|
|
179
|
+
new_version: str,
|
|
180
|
+
knowledge_base: GeneratedKnowledgeBase,
|
|
181
|
+
) -> TransformResult:
|
|
182
|
+
"""Apply Tier 2 knowledge base guided migration.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
code: Source code to transform.
|
|
186
|
+
file_path: Path to the file.
|
|
187
|
+
library: Library being upgraded.
|
|
188
|
+
old_version: Current version.
|
|
189
|
+
new_version: Target version.
|
|
190
|
+
knowledge_base: Generated knowledge base.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
TransformResult.
|
|
194
|
+
"""
|
|
195
|
+
if not self.llm_migrator.is_available:
|
|
196
|
+
return TransformResult(
|
|
197
|
+
file_path=file_path,
|
|
198
|
+
status=TransformStatus.NO_CHANGES,
|
|
199
|
+
original_code=code,
|
|
200
|
+
transformed_code=code,
|
|
201
|
+
errors=["LLM not available for Tier 2"],
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Build context from knowledge base
|
|
205
|
+
context_parts = [
|
|
206
|
+
f"Breaking changes for {library} {old_version} -> {new_version}:",
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
for change in knowledge_base.breaking_changes:
|
|
210
|
+
if change.new_api:
|
|
211
|
+
context_parts.append(
|
|
212
|
+
f"- {change.old_api} -> {change.new_api}: {change.description}"
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
context_parts.append(f"- {change.old_api} (removed): {change.description}")
|
|
216
|
+
|
|
217
|
+
context = "\n".join(context_parts)
|
|
218
|
+
|
|
219
|
+
# Use LLM with context
|
|
220
|
+
result = self.llm_migrator.migrate(
|
|
221
|
+
code=code,
|
|
222
|
+
library=library,
|
|
223
|
+
from_version=old_version,
|
|
224
|
+
to_version=new_version,
|
|
225
|
+
context=context,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if result.success:
|
|
229
|
+
return TransformResult(
|
|
230
|
+
file_path=file_path,
|
|
231
|
+
status=TransformStatus.SUCCESS,
|
|
232
|
+
original_code=code,
|
|
233
|
+
transformed_code=result.migrated_code,
|
|
234
|
+
changes=[
|
|
235
|
+
TransformChange(
|
|
236
|
+
description="KB-guided LLM migration",
|
|
237
|
+
line_number=1,
|
|
238
|
+
original="(various)",
|
|
239
|
+
replacement="(migrated)",
|
|
240
|
+
transform_name="tier2_kb_guided",
|
|
241
|
+
confidence=0.9,
|
|
242
|
+
)
|
|
243
|
+
],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return TransformResult(
|
|
247
|
+
file_path=file_path,
|
|
248
|
+
status=TransformStatus.FAILED,
|
|
249
|
+
original_code=code,
|
|
250
|
+
transformed_code=code,
|
|
251
|
+
errors=[result.error or "Tier 2 migration failed"],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _apply_tier3_transform(
|
|
255
|
+
self,
|
|
256
|
+
code: str,
|
|
257
|
+
file_path: Path,
|
|
258
|
+
library: str,
|
|
259
|
+
old_version: str,
|
|
260
|
+
new_version: str,
|
|
261
|
+
) -> TransformResult:
|
|
262
|
+
"""Apply Tier 3 pure LLM migration.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
code: Source code to transform.
|
|
266
|
+
file_path: Path to the file.
|
|
267
|
+
library: Library being upgraded.
|
|
268
|
+
old_version: Current version.
|
|
269
|
+
new_version: Target version.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
TransformResult.
|
|
273
|
+
"""
|
|
274
|
+
result = self.llm_migrator.migrate(
|
|
275
|
+
code=code,
|
|
276
|
+
library=library,
|
|
277
|
+
from_version=old_version,
|
|
278
|
+
to_version=new_version,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if result.success:
|
|
282
|
+
return TransformResult(
|
|
283
|
+
file_path=file_path,
|
|
284
|
+
status=TransformStatus.SUCCESS,
|
|
285
|
+
original_code=code,
|
|
286
|
+
transformed_code=result.migrated_code,
|
|
287
|
+
changes=[
|
|
288
|
+
TransformChange(
|
|
289
|
+
description="LLM-assisted migration",
|
|
290
|
+
line_number=1,
|
|
291
|
+
original="(various)",
|
|
292
|
+
replacement="(migrated)",
|
|
293
|
+
transform_name="tier3_llm",
|
|
294
|
+
confidence=0.7,
|
|
295
|
+
notes="Review carefully - LLM-generated changes",
|
|
296
|
+
)
|
|
297
|
+
],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return TransformResult(
|
|
301
|
+
file_path=file_path,
|
|
302
|
+
status=TransformStatus.NO_CHANGES,
|
|
303
|
+
original_code=code,
|
|
304
|
+
transformed_code=code,
|
|
305
|
+
errors=[result.error or "Tier 3 migration failed"],
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _get_transform_func(self, library: str) -> Callable | None:
|
|
309
|
+
"""Get the transform function for a library.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
library: Library name.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Transform function or None.
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
if library == "pydantic":
|
|
319
|
+
from codeshift.migrator.transforms.pydantic_v1_to_v2 import (
|
|
320
|
+
transform_pydantic_v1_to_v2,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return transform_pydantic_v1_to_v2
|
|
324
|
+
elif library == "fastapi":
|
|
325
|
+
from codeshift.migrator.transforms.fastapi_transformer import (
|
|
326
|
+
transform_fastapi,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return transform_fastapi
|
|
330
|
+
elif library == "sqlalchemy":
|
|
331
|
+
from codeshift.migrator.transforms.sqlalchemy_transformer import (
|
|
332
|
+
transform_sqlalchemy,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return transform_sqlalchemy
|
|
336
|
+
elif library == "pandas":
|
|
337
|
+
from codeshift.migrator.transforms.pandas_transformer import (
|
|
338
|
+
transform_pandas,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return transform_pandas
|
|
342
|
+
elif library == "requests":
|
|
343
|
+
from codeshift.migrator.transforms.requests_transformer import (
|
|
344
|
+
transform_requests,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
return transform_requests
|
|
348
|
+
except ImportError:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# Singleton instance
|
|
355
|
+
_default_engine: MigrationEngine | None = None
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def get_migration_engine() -> MigrationEngine:
|
|
359
|
+
"""Get the default migration engine instance."""
|
|
360
|
+
global _default_engine
|
|
361
|
+
if _default_engine is None:
|
|
362
|
+
_default_engine = MigrationEngine()
|
|
363
|
+
return _default_engine
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def run_migration(
|
|
367
|
+
code: str,
|
|
368
|
+
file_path: Path,
|
|
369
|
+
library: str,
|
|
370
|
+
old_version: str,
|
|
371
|
+
new_version: str,
|
|
372
|
+
knowledge_base: GeneratedKnowledgeBase | None = None,
|
|
373
|
+
) -> TransformResult:
|
|
374
|
+
"""Convenience function to run a migration.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
code: Source code to migrate.
|
|
378
|
+
file_path: Path to the file.
|
|
379
|
+
library: Library being upgraded.
|
|
380
|
+
old_version: Current version.
|
|
381
|
+
new_version: Target version.
|
|
382
|
+
knowledge_base: Optional generated knowledge base.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
TransformResult.
|
|
386
|
+
"""
|
|
387
|
+
engine = get_migration_engine()
|
|
388
|
+
return engine.run_migration(
|
|
389
|
+
code=code,
|
|
390
|
+
file_path=file_path,
|
|
391
|
+
library=library,
|
|
392
|
+
old_version=old_version,
|
|
393
|
+
new_version=new_version,
|
|
394
|
+
knowledge_base=knowledge_base,
|
|
395
|
+
)
|