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,352 @@
|
|
|
1
|
+
"""Apply command for applying proposed changes."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.prompt import Confirm
|
|
11
|
+
|
|
12
|
+
from codeshift.cli.commands.upgrade import load_state, save_state
|
|
13
|
+
from codeshift.cli.quota import (
|
|
14
|
+
QuotaError,
|
|
15
|
+
check_quota,
|
|
16
|
+
record_usage,
|
|
17
|
+
show_quota_exceeded_message,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command()
|
|
24
|
+
@click.option(
|
|
25
|
+
"--path",
|
|
26
|
+
"-p",
|
|
27
|
+
type=click.Path(exists=True),
|
|
28
|
+
default=".",
|
|
29
|
+
help="Path to the project",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--file",
|
|
33
|
+
"-f",
|
|
34
|
+
type=str,
|
|
35
|
+
help="Apply changes to a specific file only",
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--backup",
|
|
39
|
+
is_flag=True,
|
|
40
|
+
help="Create backup files (.bak) before applying changes",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--yes",
|
|
44
|
+
"-y",
|
|
45
|
+
is_flag=True,
|
|
46
|
+
help="Skip confirmation prompt",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--validate/--no-validate",
|
|
50
|
+
default=True,
|
|
51
|
+
help="Validate syntax after applying changes (default: yes)",
|
|
52
|
+
)
|
|
53
|
+
def apply(
|
|
54
|
+
path: str,
|
|
55
|
+
file: str | None,
|
|
56
|
+
backup: bool,
|
|
57
|
+
yes: bool,
|
|
58
|
+
validate: bool,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Apply proposed changes to your files.
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
Examples:
|
|
64
|
+
codeshift apply
|
|
65
|
+
codeshift apply --backup
|
|
66
|
+
codeshift apply --file models.py
|
|
67
|
+
codeshift apply -y # Skip confirmation
|
|
68
|
+
"""
|
|
69
|
+
project_path = Path(path).resolve()
|
|
70
|
+
state = load_state(project_path)
|
|
71
|
+
|
|
72
|
+
if state is None:
|
|
73
|
+
console.print(
|
|
74
|
+
Panel(
|
|
75
|
+
"[yellow]No pending migration found.[/]\n\n"
|
|
76
|
+
"Run [cyan]codeshift upgrade <library> --target <version>[/] first.",
|
|
77
|
+
title="No Changes",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
library = state.get("library", "unknown")
|
|
83
|
+
target_version = state.get("target_version", "unknown")
|
|
84
|
+
results = state.get("results", [])
|
|
85
|
+
|
|
86
|
+
if not results:
|
|
87
|
+
console.print("[yellow]No changes pending.[/]")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Check quota for file migrations
|
|
91
|
+
try:
|
|
92
|
+
check_quota("file_migrated", quantity=len(results), allow_offline=True)
|
|
93
|
+
except QuotaError as e:
|
|
94
|
+
show_quota_exceeded_message(e)
|
|
95
|
+
raise SystemExit(1) from e
|
|
96
|
+
|
|
97
|
+
# Filter results if file specified
|
|
98
|
+
if file:
|
|
99
|
+
results = [
|
|
100
|
+
r
|
|
101
|
+
for r in results
|
|
102
|
+
if Path(r["file_path"]).name == file
|
|
103
|
+
or str(Path(r["file_path"]).relative_to(project_path)) == file
|
|
104
|
+
]
|
|
105
|
+
if not results:
|
|
106
|
+
console.print(f"[yellow]No pending changes for file: {file}[/]")
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Show summary
|
|
110
|
+
total_changes = sum(r.get("change_count", 0) for r in results)
|
|
111
|
+
console.print(
|
|
112
|
+
Panel(
|
|
113
|
+
f"[bold]Migration: {library}[/] → v{target_version}\n\n"
|
|
114
|
+
f"Files to modify: [cyan]{len(results)}[/]\n"
|
|
115
|
+
f"Total changes: [cyan]{total_changes}[/]",
|
|
116
|
+
title="Apply Changes",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# List files to be modified
|
|
121
|
+
console.print("\n[bold]Files to be modified:[/]")
|
|
122
|
+
for result in results:
|
|
123
|
+
file_path = Path(result["file_path"])
|
|
124
|
+
relative_path = (
|
|
125
|
+
file_path.relative_to(project_path)
|
|
126
|
+
if file_path.is_relative_to(project_path)
|
|
127
|
+
else file_path
|
|
128
|
+
)
|
|
129
|
+
console.print(f" • {relative_path} ({result.get('change_count', 0)} changes)")
|
|
130
|
+
|
|
131
|
+
# Confirm
|
|
132
|
+
if not yes:
|
|
133
|
+
console.print()
|
|
134
|
+
if not Confirm.ask("Apply these changes?"):
|
|
135
|
+
console.print("[yellow]Aborted.[/]")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Apply changes
|
|
139
|
+
applied_count = 0
|
|
140
|
+
failed_count = 0
|
|
141
|
+
backup_dir = project_path / ".codeshift" / "backups" / datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
142
|
+
|
|
143
|
+
for result in results:
|
|
144
|
+
file_path = Path(result["file_path"])
|
|
145
|
+
relative_path = (
|
|
146
|
+
file_path.relative_to(project_path)
|
|
147
|
+
if file_path.is_relative_to(project_path)
|
|
148
|
+
else file_path
|
|
149
|
+
)
|
|
150
|
+
transformed_code = result.get("transformed_code", "")
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
# Create backup if requested
|
|
154
|
+
if backup:
|
|
155
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
backup_path = backup_dir / relative_path
|
|
157
|
+
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
shutil.copy2(file_path, backup_path)
|
|
159
|
+
|
|
160
|
+
# Validate syntax if requested
|
|
161
|
+
if validate:
|
|
162
|
+
try:
|
|
163
|
+
compile(transformed_code, str(file_path), "exec")
|
|
164
|
+
except SyntaxError as e:
|
|
165
|
+
console.print(f"[red]✗[/] {relative_path} - Syntax error: {e}")
|
|
166
|
+
failed_count += 1
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
# Write the transformed code
|
|
170
|
+
file_path.write_text(transformed_code)
|
|
171
|
+
console.print(f"[green]✓[/] {relative_path}")
|
|
172
|
+
applied_count += 1
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
console.print(f"[red]✗[/] {relative_path} - {e}")
|
|
176
|
+
failed_count += 1
|
|
177
|
+
|
|
178
|
+
# Summary
|
|
179
|
+
console.print()
|
|
180
|
+
if applied_count > 0:
|
|
181
|
+
console.print(f"[green]Successfully applied changes to {applied_count} file(s)[/]")
|
|
182
|
+
|
|
183
|
+
# Record usage event for applied changes
|
|
184
|
+
record_usage(
|
|
185
|
+
event_type="file_migrated",
|
|
186
|
+
library=library,
|
|
187
|
+
quantity=applied_count,
|
|
188
|
+
metadata={
|
|
189
|
+
"target_version": target_version,
|
|
190
|
+
"total_changes": total_changes,
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
record_usage(
|
|
194
|
+
event_type="apply",
|
|
195
|
+
library=library,
|
|
196
|
+
quantity=1,
|
|
197
|
+
metadata={
|
|
198
|
+
"files_applied": applied_count,
|
|
199
|
+
"files_failed": failed_count,
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if failed_count > 0:
|
|
204
|
+
console.print(f"[red]Failed to apply changes to {failed_count} file(s)[/]")
|
|
205
|
+
|
|
206
|
+
if backup and applied_count > 0:
|
|
207
|
+
console.print(f"[dim]Backups saved to: {backup_dir}[/]")
|
|
208
|
+
|
|
209
|
+
# Update state
|
|
210
|
+
if file:
|
|
211
|
+
# Remove only the applied files from state
|
|
212
|
+
remaining_results = [r for r in state.get("results", []) if r not in results]
|
|
213
|
+
if remaining_results:
|
|
214
|
+
state["results"] = remaining_results
|
|
215
|
+
save_state(project_path, state)
|
|
216
|
+
else:
|
|
217
|
+
# All done, remove state
|
|
218
|
+
state_file = project_path / ".codeshift" / "state.json"
|
|
219
|
+
if state_file.exists():
|
|
220
|
+
state_file.unlink()
|
|
221
|
+
console.print("\n[green]Migration complete![/]")
|
|
222
|
+
else:
|
|
223
|
+
# All files processed, remove state
|
|
224
|
+
state_file = project_path / ".codeshift" / "state.json"
|
|
225
|
+
if state_file.exists():
|
|
226
|
+
state_file.unlink()
|
|
227
|
+
console.print("\n[green]Migration complete![/]")
|
|
228
|
+
|
|
229
|
+
# Next steps
|
|
230
|
+
console.print("\n[bold]Recommended next steps:[/]")
|
|
231
|
+
console.print(" 1. Review the changes in your editor")
|
|
232
|
+
console.print(" 2. Run your test suite: [cyan]pytest[/]")
|
|
233
|
+
console.print(" 3. Update your dependencies: [cyan]pip install pydantic>=2.0[/]")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@click.command()
|
|
237
|
+
@click.option(
|
|
238
|
+
"--path",
|
|
239
|
+
"-p",
|
|
240
|
+
type=click.Path(exists=True),
|
|
241
|
+
default=".",
|
|
242
|
+
help="Path to the project",
|
|
243
|
+
)
|
|
244
|
+
@click.option(
|
|
245
|
+
"--yes",
|
|
246
|
+
"-y",
|
|
247
|
+
is_flag=True,
|
|
248
|
+
help="Skip confirmation prompt",
|
|
249
|
+
)
|
|
250
|
+
def reset(path: str, yes: bool) -> None:
|
|
251
|
+
"""Reset/cancel the current migration state.
|
|
252
|
+
|
|
253
|
+
This removes any pending changes without applying them.
|
|
254
|
+
|
|
255
|
+
\b
|
|
256
|
+
Examples:
|
|
257
|
+
codeshift reset
|
|
258
|
+
codeshift reset -y
|
|
259
|
+
"""
|
|
260
|
+
project_path = Path(path).resolve()
|
|
261
|
+
state = load_state(project_path)
|
|
262
|
+
|
|
263
|
+
if state is None:
|
|
264
|
+
console.print("[yellow]No pending migration to reset.[/]")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
library = state.get("library", "unknown")
|
|
268
|
+
results = state.get("results", [])
|
|
269
|
+
|
|
270
|
+
console.print(
|
|
271
|
+
Panel(
|
|
272
|
+
f"[bold]Pending migration: {library}[/]\n" f"Files with changes: {len(results)}",
|
|
273
|
+
title="Reset Migration",
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if not yes:
|
|
278
|
+
if not Confirm.ask("Are you sure you want to discard these changes?"):
|
|
279
|
+
console.print("[yellow]Aborted.[/]")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
state_file = project_path / ".codeshift" / "state.json"
|
|
283
|
+
if state_file.exists():
|
|
284
|
+
state_file.unlink()
|
|
285
|
+
|
|
286
|
+
console.print("[green]Migration state reset.[/]")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@click.command()
|
|
290
|
+
@click.argument("backup_dir", type=click.Path(exists=True))
|
|
291
|
+
@click.option(
|
|
292
|
+
"--path",
|
|
293
|
+
"-p",
|
|
294
|
+
type=click.Path(exists=True),
|
|
295
|
+
default=".",
|
|
296
|
+
help="Path to the project",
|
|
297
|
+
)
|
|
298
|
+
@click.option(
|
|
299
|
+
"--yes",
|
|
300
|
+
"-y",
|
|
301
|
+
is_flag=True,
|
|
302
|
+
help="Skip confirmation prompt",
|
|
303
|
+
)
|
|
304
|
+
def restore(backup_dir: str, path: str, yes: bool) -> None:
|
|
305
|
+
"""Restore files from a backup.
|
|
306
|
+
|
|
307
|
+
\b
|
|
308
|
+
Examples:
|
|
309
|
+
codeshift restore .codeshift/backups/20240115_143022
|
|
310
|
+
"""
|
|
311
|
+
project_path = Path(path).resolve()
|
|
312
|
+
backup_path = Path(backup_dir).resolve()
|
|
313
|
+
|
|
314
|
+
# Find all files in backup
|
|
315
|
+
backup_files = list(backup_path.rglob("*.py"))
|
|
316
|
+
|
|
317
|
+
if not backup_files:
|
|
318
|
+
console.print(f"[yellow]No Python files found in backup: {backup_dir}[/]")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
console.print(
|
|
322
|
+
Panel(
|
|
323
|
+
f"[bold]Restore from backup[/]\n\n"
|
|
324
|
+
f"Backup: {backup_path}\n"
|
|
325
|
+
f"Files: {len(backup_files)}",
|
|
326
|
+
title="Restore Backup",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
for bf in backup_files:
|
|
331
|
+
relative = bf.relative_to(backup_path)
|
|
332
|
+
console.print(f" • {relative}")
|
|
333
|
+
|
|
334
|
+
if not yes:
|
|
335
|
+
console.print()
|
|
336
|
+
if not Confirm.ask("Restore these files?"):
|
|
337
|
+
console.print("[yellow]Aborted.[/]")
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
restored = 0
|
|
341
|
+
for backup_file in backup_files:
|
|
342
|
+
relative = backup_file.relative_to(backup_path)
|
|
343
|
+
target = project_path / relative
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
shutil.copy2(backup_file, target)
|
|
347
|
+
console.print(f"[green]✓[/] {relative}")
|
|
348
|
+
restored += 1
|
|
349
|
+
except Exception as e:
|
|
350
|
+
console.print(f"[red]✗[/] {relative} - {e}")
|
|
351
|
+
|
|
352
|
+
console.print(f"\n[green]Restored {restored} file(s)[/]")
|