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.
Files changed (65) hide show
  1. codeshift/__init__.py +8 -0
  2. codeshift/analyzer/__init__.py +5 -0
  3. codeshift/analyzer/risk_assessor.py +388 -0
  4. codeshift/api/__init__.py +1 -0
  5. codeshift/api/auth.py +182 -0
  6. codeshift/api/config.py +73 -0
  7. codeshift/api/database.py +215 -0
  8. codeshift/api/main.py +103 -0
  9. codeshift/api/models/__init__.py +55 -0
  10. codeshift/api/models/auth.py +108 -0
  11. codeshift/api/models/billing.py +92 -0
  12. codeshift/api/models/migrate.py +42 -0
  13. codeshift/api/models/usage.py +116 -0
  14. codeshift/api/routers/__init__.py +5 -0
  15. codeshift/api/routers/auth.py +440 -0
  16. codeshift/api/routers/billing.py +395 -0
  17. codeshift/api/routers/migrate.py +304 -0
  18. codeshift/api/routers/usage.py +291 -0
  19. codeshift/api/routers/webhooks.py +289 -0
  20. codeshift/cli/__init__.py +5 -0
  21. codeshift/cli/commands/__init__.py +7 -0
  22. codeshift/cli/commands/apply.py +352 -0
  23. codeshift/cli/commands/auth.py +842 -0
  24. codeshift/cli/commands/diff.py +221 -0
  25. codeshift/cli/commands/scan.py +368 -0
  26. codeshift/cli/commands/upgrade.py +436 -0
  27. codeshift/cli/commands/upgrade_all.py +518 -0
  28. codeshift/cli/main.py +221 -0
  29. codeshift/cli/quota.py +210 -0
  30. codeshift/knowledge/__init__.py +50 -0
  31. codeshift/knowledge/cache.py +167 -0
  32. codeshift/knowledge/generator.py +231 -0
  33. codeshift/knowledge/models.py +151 -0
  34. codeshift/knowledge/parser.py +270 -0
  35. codeshift/knowledge/sources.py +388 -0
  36. codeshift/knowledge_base/__init__.py +17 -0
  37. codeshift/knowledge_base/loader.py +102 -0
  38. codeshift/knowledge_base/models.py +110 -0
  39. codeshift/migrator/__init__.py +23 -0
  40. codeshift/migrator/ast_transforms.py +256 -0
  41. codeshift/migrator/engine.py +395 -0
  42. codeshift/migrator/llm_migrator.py +320 -0
  43. codeshift/migrator/transforms/__init__.py +19 -0
  44. codeshift/migrator/transforms/fastapi_transformer.py +174 -0
  45. codeshift/migrator/transforms/pandas_transformer.py +236 -0
  46. codeshift/migrator/transforms/pydantic_v1_to_v2.py +637 -0
  47. codeshift/migrator/transforms/requests_transformer.py +218 -0
  48. codeshift/migrator/transforms/sqlalchemy_transformer.py +175 -0
  49. codeshift/scanner/__init__.py +6 -0
  50. codeshift/scanner/code_scanner.py +352 -0
  51. codeshift/scanner/dependency_parser.py +473 -0
  52. codeshift/utils/__init__.py +5 -0
  53. codeshift/utils/api_client.py +266 -0
  54. codeshift/utils/cache.py +318 -0
  55. codeshift/utils/config.py +71 -0
  56. codeshift/utils/llm_client.py +221 -0
  57. codeshift/validator/__init__.py +6 -0
  58. codeshift/validator/syntax_checker.py +183 -0
  59. codeshift/validator/test_runner.py +224 -0
  60. codeshift-0.2.0.dist-info/METADATA +326 -0
  61. codeshift-0.2.0.dist-info/RECORD +65 -0
  62. codeshift-0.2.0.dist-info/WHEEL +5 -0
  63. codeshift-0.2.0.dist-info/entry_points.txt +2 -0
  64. codeshift-0.2.0.dist-info/licenses/LICENSE +21 -0
  65. 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)[/]")