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,236 @@
|
|
|
1
|
+
"""Pandas 1.x to 2.0 transformation using LibCST."""
|
|
2
|
+
|
|
3
|
+
import libcst as cst
|
|
4
|
+
|
|
5
|
+
from codeshift.migrator.ast_transforms import BaseTransformer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PandasTransformer(BaseTransformer):
|
|
9
|
+
"""Transform Pandas 1.x code to 2.0."""
|
|
10
|
+
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__()
|
|
13
|
+
|
|
14
|
+
def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call:
|
|
15
|
+
"""Transform Pandas function calls."""
|
|
16
|
+
# Handle to_csv line_terminator -> lineterminator
|
|
17
|
+
if isinstance(updated_node.func, cst.Attribute):
|
|
18
|
+
attr_name = updated_node.func.attr.value
|
|
19
|
+
|
|
20
|
+
if attr_name == "to_csv":
|
|
21
|
+
new_args = []
|
|
22
|
+
changed = False
|
|
23
|
+
for arg in updated_node.args:
|
|
24
|
+
if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "line_terminator":
|
|
25
|
+
new_args.append(arg.with_changes(keyword=cst.Name("lineterminator")))
|
|
26
|
+
changed = True
|
|
27
|
+
self.record_change(
|
|
28
|
+
description="Rename to_csv line_terminator to lineterminator",
|
|
29
|
+
line_number=1,
|
|
30
|
+
original="to_csv(line_terminator=...)",
|
|
31
|
+
replacement="to_csv(lineterminator=...)",
|
|
32
|
+
transform_name="line_terminator_rename",
|
|
33
|
+
)
|
|
34
|
+
else:
|
|
35
|
+
new_args.append(arg)
|
|
36
|
+
|
|
37
|
+
if changed:
|
|
38
|
+
return updated_node.with_changes(args=new_args)
|
|
39
|
+
|
|
40
|
+
# Handle swaplevel axis removal
|
|
41
|
+
if attr_name == "swaplevel":
|
|
42
|
+
new_args = []
|
|
43
|
+
changed = False
|
|
44
|
+
for arg in updated_node.args:
|
|
45
|
+
if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "axis":
|
|
46
|
+
changed = True
|
|
47
|
+
self.record_change(
|
|
48
|
+
description="Remove axis parameter from swaplevel",
|
|
49
|
+
line_number=1,
|
|
50
|
+
original="swaplevel(..., axis=...)",
|
|
51
|
+
replacement="swaplevel(...)",
|
|
52
|
+
transform_name="swaplevel_remove_axis",
|
|
53
|
+
)
|
|
54
|
+
continue
|
|
55
|
+
new_args.append(arg)
|
|
56
|
+
|
|
57
|
+
if changed:
|
|
58
|
+
return updated_node.with_changes(args=new_args)
|
|
59
|
+
|
|
60
|
+
# Handle reorder_levels axis removal
|
|
61
|
+
if attr_name == "reorder_levels":
|
|
62
|
+
new_args = []
|
|
63
|
+
changed = False
|
|
64
|
+
for arg in updated_node.args:
|
|
65
|
+
if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "axis":
|
|
66
|
+
changed = True
|
|
67
|
+
self.record_change(
|
|
68
|
+
description="Remove axis parameter from reorder_levels",
|
|
69
|
+
line_number=1,
|
|
70
|
+
original="reorder_levels(..., axis=...)",
|
|
71
|
+
replacement="reorder_levels(...)",
|
|
72
|
+
transform_name="reorder_levels_remove_axis",
|
|
73
|
+
)
|
|
74
|
+
continue
|
|
75
|
+
new_args.append(arg)
|
|
76
|
+
|
|
77
|
+
if changed:
|
|
78
|
+
return updated_node.with_changes(args=new_args)
|
|
79
|
+
|
|
80
|
+
# Handle groupby numeric_only default changes
|
|
81
|
+
if attr_name in ("mean", "sum", "prod", "std", "var", "min", "max"):
|
|
82
|
+
# Check if called on groupby result and no numeric_only specified
|
|
83
|
+
has_numeric_only = any(
|
|
84
|
+
isinstance(arg.keyword, cst.Name) and arg.keyword.value == "numeric_only"
|
|
85
|
+
for arg in updated_node.args
|
|
86
|
+
)
|
|
87
|
+
if not has_numeric_only:
|
|
88
|
+
# Record as potential issue but don't auto-change
|
|
89
|
+
self.record_change(
|
|
90
|
+
description=f"GroupBy.{attr_name}() numeric_only now defaults to False",
|
|
91
|
+
line_number=1,
|
|
92
|
+
original=f".{attr_name}()",
|
|
93
|
+
replacement=f".{attr_name}(numeric_only=True)",
|
|
94
|
+
transform_name=f"groupby_{attr_name}_numeric_only",
|
|
95
|
+
confidence=0.6,
|
|
96
|
+
notes="Add numeric_only=True if you want to exclude non-numeric columns",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return updated_node
|
|
100
|
+
|
|
101
|
+
def leave_Attribute(
|
|
102
|
+
self, original_node: cst.Attribute, updated_node: cst.Attribute
|
|
103
|
+
) -> cst.Attribute:
|
|
104
|
+
"""Transform Pandas attribute accesses."""
|
|
105
|
+
attr_name = updated_node.attr.value
|
|
106
|
+
|
|
107
|
+
# Handle iteritems -> items
|
|
108
|
+
if attr_name == "iteritems":
|
|
109
|
+
self.record_change(
|
|
110
|
+
description="Rename iteritems() to items()",
|
|
111
|
+
line_number=1,
|
|
112
|
+
original=".iteritems()",
|
|
113
|
+
replacement=".items()",
|
|
114
|
+
transform_name="iteritems_to_items",
|
|
115
|
+
)
|
|
116
|
+
return updated_node.with_changes(attr=cst.Name("items"))
|
|
117
|
+
|
|
118
|
+
# Handle is_monotonic -> is_monotonic_increasing
|
|
119
|
+
if attr_name == "is_monotonic":
|
|
120
|
+
self.record_change(
|
|
121
|
+
description="Rename is_monotonic to is_monotonic_increasing",
|
|
122
|
+
line_number=1,
|
|
123
|
+
original=".is_monotonic",
|
|
124
|
+
replacement=".is_monotonic_increasing",
|
|
125
|
+
transform_name="is_monotonic_to_increasing",
|
|
126
|
+
)
|
|
127
|
+
return updated_node.with_changes(attr=cst.Name("is_monotonic_increasing"))
|
|
128
|
+
|
|
129
|
+
return updated_node
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class PandasAppendTransformer(BaseTransformer):
|
|
133
|
+
"""Transform DataFrame.append() to pd.concat()."""
|
|
134
|
+
|
|
135
|
+
def __init__(self) -> None:
|
|
136
|
+
super().__init__()
|
|
137
|
+
|
|
138
|
+
def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.BaseExpression:
|
|
139
|
+
"""Transform append calls to concat."""
|
|
140
|
+
if not isinstance(updated_node.func, cst.Attribute):
|
|
141
|
+
return updated_node
|
|
142
|
+
|
|
143
|
+
if updated_node.func.attr.value != "append":
|
|
144
|
+
return updated_node
|
|
145
|
+
|
|
146
|
+
# Get the object being called on
|
|
147
|
+
obj = updated_node.func.value
|
|
148
|
+
|
|
149
|
+
# Get the first positional argument (what's being appended)
|
|
150
|
+
if not updated_node.args:
|
|
151
|
+
return updated_node
|
|
152
|
+
|
|
153
|
+
append_arg = updated_node.args[0].value
|
|
154
|
+
|
|
155
|
+
# Check for ignore_index parameter
|
|
156
|
+
ignore_index = False
|
|
157
|
+
other_args = []
|
|
158
|
+
for arg in updated_node.args[1:]:
|
|
159
|
+
if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "ignore_index":
|
|
160
|
+
if isinstance(arg.value, cst.Name) and arg.value.value == "True":
|
|
161
|
+
ignore_index = True
|
|
162
|
+
else:
|
|
163
|
+
other_args.append(arg)
|
|
164
|
+
|
|
165
|
+
# Build pd.concat call
|
|
166
|
+
concat_args = [
|
|
167
|
+
cst.Arg(
|
|
168
|
+
value=cst.List(
|
|
169
|
+
elements=[
|
|
170
|
+
cst.Element(value=obj),
|
|
171
|
+
cst.Element(value=append_arg),
|
|
172
|
+
]
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
if ignore_index:
|
|
178
|
+
concat_args.append(
|
|
179
|
+
cst.Arg(
|
|
180
|
+
keyword=cst.Name("ignore_index"),
|
|
181
|
+
value=cst.Name("True"),
|
|
182
|
+
equal=cst.AssignEqual(
|
|
183
|
+
whitespace_before=cst.SimpleWhitespace(""),
|
|
184
|
+
whitespace_after=cst.SimpleWhitespace(""),
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self.record_change(
|
|
190
|
+
description="Replace DataFrame.append() with pd.concat()",
|
|
191
|
+
line_number=1,
|
|
192
|
+
original=".append(...)",
|
|
193
|
+
replacement="pd.concat([df1, df2], ...)",
|
|
194
|
+
transform_name="append_to_concat",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return cst.Call(
|
|
198
|
+
func=cst.Attribute(
|
|
199
|
+
value=cst.Name("pd"),
|
|
200
|
+
attr=cst.Name("concat"),
|
|
201
|
+
),
|
|
202
|
+
args=concat_args,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def transform_pandas(source_code: str) -> tuple[str, list]:
|
|
207
|
+
"""Transform Pandas code from 1.x to 2.0.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
source_code: The source code to transform
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Tuple of (transformed_code, list of changes)
|
|
214
|
+
"""
|
|
215
|
+
try:
|
|
216
|
+
tree = cst.parse_module(source_code)
|
|
217
|
+
except cst.ParserSyntaxError:
|
|
218
|
+
return source_code, []
|
|
219
|
+
|
|
220
|
+
# Apply main transformer
|
|
221
|
+
transformer = PandasTransformer()
|
|
222
|
+
transformer.set_source(source_code)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
transformed_tree = tree.visit(transformer)
|
|
226
|
+
all_changes = list(transformer.changes)
|
|
227
|
+
|
|
228
|
+
# Apply append transformer
|
|
229
|
+
append_transformer = PandasAppendTransformer()
|
|
230
|
+
append_transformer.set_source(transformed_tree.code)
|
|
231
|
+
transformed_tree = transformed_tree.visit(append_transformer)
|
|
232
|
+
all_changes.extend(append_transformer.changes)
|
|
233
|
+
|
|
234
|
+
return transformed_tree.code, all_changes
|
|
235
|
+
except Exception:
|
|
236
|
+
return source_code, []
|