codeshift 0.4.0__tar.gz → 0.5.0__tar.gz
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-0.4.0 → codeshift-0.5.0}/PKG-INFO +4 -1
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/auth.py +41 -25
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/upgrade.py +68 -55
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/generator.py +6 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/aiohttp.yaml +3 -3
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/httpx.yaml +4 -4
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/pytest.yaml +1 -1
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/models.py +1 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/marshmallow_transformer.py +50 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/pydantic_v1_to_v2.py +191 -22
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/scanner/code_scanner.py +22 -2
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/utils/api_client.py +144 -4
- codeshift-0.5.0/codeshift/utils/credential_store.py +393 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/utils/llm_client.py +111 -9
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/PKG-INFO +4 -1
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/SOURCES.txt +2 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/requires.txt +3 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/pyproject.toml +14 -2
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_marshmallow_transforms.py +79 -0
- codeshift-0.5.0/tests/test_pydantic_type_inference.py +235 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/LICENSE +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/README.md +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/analyzer/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/analyzer/risk_assessor.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/apply.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/diff.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/scan.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/commands/upgrade_all.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/main.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/package_manager.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/cli/quota.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/cache.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/models.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/parser.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge/sources.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/attrs.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/celery.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/click.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/django.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/fastapi.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/flask.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/marshmallow.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/numpy.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/pandas.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/pydantic.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/requests.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/libraries/sqlalchemy.yaml +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/knowledge_base/loader.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/ast_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/engine.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/llm_migrator.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/aiohttp_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/attrs_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/celery_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/click_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/django_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/fastapi_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/flask_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/httpx_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/numpy_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/pandas_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/pytest_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/requests_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/sqlalchemy_transformer.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/scanner/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/scanner/dependency_parser.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/utils/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/utils/cache.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/utils/config.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/validator/__init__.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/validator/syntax_checker.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift/validator/test_runner.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/dependency_links.txt +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/entry_points.txt +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/codeshift.egg-info/top_level.txt +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/setup.cfg +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_aiohttp_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_attrs_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_celery_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_click_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_code_scanner.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_django_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_fastapi_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_flask_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_httpx_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_knowledge_base.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_numpy_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_pandas_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_pydantic_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_pytest_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_requests_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_risk_assessor.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_sqlalchemy_transforms.py +0 -0
- {codeshift-0.4.0 → codeshift-0.5.0}/tests/test_syntax_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeshift
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: AI-powered CLI tool that migrates Python code to handle breaking dependency changes
|
|
5
5
|
Author: Ragab Technologies
|
|
6
6
|
License: MIT
|
|
@@ -27,6 +27,9 @@ Requires-Dist: rich>=13.0
|
|
|
27
27
|
Requires-Dist: toml>=0.10
|
|
28
28
|
Requires-Dist: packaging>=23.0
|
|
29
29
|
Requires-Dist: httpx>=0.25
|
|
30
|
+
Requires-Dist: black>=24.10.0
|
|
31
|
+
Requires-Dist: cryptography>=41.0
|
|
32
|
+
Requires-Dist: nox>=2025.11.12
|
|
30
33
|
Provides-Extra: dev
|
|
31
34
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
35
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""Authentication commands for Codeshift CLI."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import os
|
|
5
4
|
import time
|
|
6
5
|
import webbrowser
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
9
8
|
|
|
10
9
|
import click
|
|
11
10
|
import httpx
|
|
@@ -15,11 +14,16 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
|
15
14
|
from rich.prompt import Confirm, Prompt
|
|
16
15
|
from rich.table import Table
|
|
17
16
|
|
|
17
|
+
from codeshift.utils.credential_store import (
|
|
18
|
+
CredentialDecryptionError,
|
|
19
|
+
get_credential_store,
|
|
20
|
+
)
|
|
21
|
+
|
|
18
22
|
console = Console()
|
|
19
23
|
|
|
20
|
-
# Config directory for storing credentials
|
|
24
|
+
# Config directory for storing credentials (kept for backward compatibility reference)
|
|
21
25
|
CONFIG_DIR = Path.home() / ".config" / "codeshift"
|
|
22
|
-
CREDENTIALS_FILE = CONFIG_DIR / "credentials.json"
|
|
26
|
+
CREDENTIALS_FILE = CONFIG_DIR / "credentials.json" # Legacy path
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
def get_api_url() -> str:
|
|
@@ -28,28 +32,38 @@ def get_api_url() -> str:
|
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
def load_credentials() -> dict[str, Any] | None:
|
|
31
|
-
"""Load saved credentials from
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
"""Load saved credentials from secure storage.
|
|
36
|
+
|
|
37
|
+
Automatically handles migration from plaintext to encrypted storage.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dictionary of credentials, or None if not found.
|
|
41
|
+
"""
|
|
42
|
+
store = get_credential_store()
|
|
34
43
|
try:
|
|
35
|
-
return
|
|
36
|
-
except
|
|
44
|
+
return store.load()
|
|
45
|
+
except CredentialDecryptionError as e:
|
|
46
|
+
console.print(
|
|
47
|
+
Panel(
|
|
48
|
+
f"[red]Could not decrypt credentials:[/] {e}\n\n"
|
|
49
|
+
"This may happen if credentials were created on a different machine.\n"
|
|
50
|
+
"Please run [cyan]codeshift login[/] to re-authenticate.",
|
|
51
|
+
title="Credential Error",
|
|
52
|
+
)
|
|
53
|
+
)
|
|
37
54
|
return None
|
|
38
55
|
|
|
39
56
|
|
|
40
57
|
def save_credentials(credentials: dict) -> None:
|
|
41
|
-
"""Save credentials to
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# Set restrictive permissions
|
|
45
|
-
CREDENTIALS_FILE.write_text(json.dumps(credentials, indent=2))
|
|
46
|
-
os.chmod(CREDENTIALS_FILE, 0o600)
|
|
58
|
+
"""Save credentials to secure encrypted storage."""
|
|
59
|
+
store = get_credential_store()
|
|
60
|
+
store.save(credentials)
|
|
47
61
|
|
|
48
62
|
|
|
49
63
|
def delete_credentials() -> None:
|
|
50
|
-
"""Delete saved credentials."""
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
"""Delete saved credentials securely."""
|
|
65
|
+
store = get_credential_store()
|
|
66
|
+
store.delete()
|
|
53
67
|
|
|
54
68
|
|
|
55
69
|
def get_api_key() -> str | None:
|
|
@@ -107,7 +121,8 @@ def login(
|
|
|
107
121
|
2. API key: codeshift login -k pyr_xxxxx
|
|
108
122
|
3. Device flow: codeshift login --device
|
|
109
123
|
|
|
110
|
-
Your credentials are stored in ~/.config/codeshift/credentials.
|
|
124
|
+
Your credentials are stored securely in ~/.config/codeshift/credentials.enc
|
|
125
|
+
using AES encryption.
|
|
111
126
|
|
|
112
127
|
Don't have an account? Run: codeshift register
|
|
113
128
|
"""
|
|
@@ -154,7 +169,8 @@ def register(
|
|
|
154
169
|
Example:
|
|
155
170
|
codeshift register -e user@example.com -p yourpassword
|
|
156
171
|
|
|
157
|
-
Your credentials are stored in ~/.config/codeshift/credentials.
|
|
172
|
+
Your credentials are stored securely in ~/.config/codeshift/credentials.enc
|
|
173
|
+
using AES encryption.
|
|
158
174
|
"""
|
|
159
175
|
# Check if already logged in
|
|
160
176
|
existing = load_credentials()
|
|
@@ -211,7 +227,7 @@ def _register_account(email: str, password: str, full_name: str | None) -> None:
|
|
|
211
227
|
if response.status_code == 200:
|
|
212
228
|
data = response.json()
|
|
213
229
|
|
|
214
|
-
# Save credentials
|
|
230
|
+
# Save credentials securely
|
|
215
231
|
save_credentials(
|
|
216
232
|
{
|
|
217
233
|
"api_key": data["api_key"],
|
|
@@ -275,7 +291,7 @@ def _login_with_api_key(api_key: str) -> None:
|
|
|
275
291
|
if response.status_code == 200:
|
|
276
292
|
user = response.json()
|
|
277
293
|
|
|
278
|
-
# Save credentials
|
|
294
|
+
# Save credentials securely
|
|
279
295
|
save_credentials(
|
|
280
296
|
{
|
|
281
297
|
"api_key": api_key,
|
|
@@ -327,7 +343,7 @@ def _login_with_password(email: str, password: str) -> None:
|
|
|
327
343
|
if response.status_code == 200:
|
|
328
344
|
data = response.json()
|
|
329
345
|
|
|
330
|
-
# Save credentials
|
|
346
|
+
# Save credentials securely
|
|
331
347
|
save_credentials(
|
|
332
348
|
{
|
|
333
349
|
"api_key": data["api_key"],
|
|
@@ -422,7 +438,7 @@ def _login_with_device_code() -> None:
|
|
|
422
438
|
if response.status_code == 200:
|
|
423
439
|
data = response.json()
|
|
424
440
|
|
|
425
|
-
# Save credentials
|
|
441
|
+
# Save credentials securely
|
|
426
442
|
save_credentials(
|
|
427
443
|
{
|
|
428
444
|
"api_key": data["api_key"],
|
|
@@ -487,7 +503,7 @@ def logout() -> None:
|
|
|
487
503
|
except httpx.RequestError:
|
|
488
504
|
pass
|
|
489
505
|
|
|
490
|
-
# Delete local credentials
|
|
506
|
+
# Delete local credentials securely
|
|
491
507
|
delete_credentials()
|
|
492
508
|
|
|
493
509
|
console.print("[green]Successfully logged out[/]")
|
|
@@ -18,12 +18,8 @@ from codeshift.knowledge import (
|
|
|
18
18
|
is_tier_1_library,
|
|
19
19
|
)
|
|
20
20
|
from codeshift.knowledge_base import KnowledgeBaseLoader
|
|
21
|
-
from codeshift.
|
|
22
|
-
from codeshift.migrator.
|
|
23
|
-
from codeshift.migrator.transforms.pandas_transformer import transform_pandas
|
|
24
|
-
from codeshift.migrator.transforms.pydantic_v1_to_v2 import transform_pydantic_v1_to_v2
|
|
25
|
-
from codeshift.migrator.transforms.requests_transformer import transform_requests
|
|
26
|
-
from codeshift.migrator.transforms.sqlalchemy_transformer import transform_sqlalchemy
|
|
21
|
+
from codeshift.knowledge_base.models import LibraryKnowledge
|
|
22
|
+
from codeshift.migrator.ast_transforms import TransformResult, TransformStatus
|
|
27
23
|
from codeshift.scanner import CodeScanner, DependencyParser
|
|
28
24
|
from codeshift.utils.config import ProjectConfig
|
|
29
25
|
|
|
@@ -81,6 +77,11 @@ def save_state(project_path: Path, state: dict) -> None:
|
|
|
81
77
|
is_flag=True,
|
|
82
78
|
help="Show detailed output",
|
|
83
79
|
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--force-llm",
|
|
82
|
+
is_flag=True,
|
|
83
|
+
help="Force LLM migration even for libraries with AST transforms",
|
|
84
|
+
)
|
|
84
85
|
def upgrade(
|
|
85
86
|
library: str,
|
|
86
87
|
target: str,
|
|
@@ -88,6 +89,7 @@ def upgrade(
|
|
|
88
89
|
file: str | None,
|
|
89
90
|
dry_run: bool,
|
|
90
91
|
verbose: bool,
|
|
92
|
+
force_llm: bool,
|
|
91
93
|
) -> None:
|
|
92
94
|
"""Analyze your codebase and propose changes for a library upgrade.
|
|
93
95
|
|
|
@@ -100,34 +102,44 @@ def upgrade(
|
|
|
100
102
|
project_path = Path(path).resolve()
|
|
101
103
|
project_config = ProjectConfig.from_pyproject(project_path)
|
|
102
104
|
|
|
103
|
-
# Check quota before starting (allow offline for Tier 1 libraries)
|
|
105
|
+
# Check quota before starting (allow offline for Tier 1 libraries unless force-llm)
|
|
104
106
|
is_tier1 = is_tier_1_library(library)
|
|
105
107
|
try:
|
|
106
|
-
check_quota("file_migrated", quantity=1, allow_offline=is_tier1)
|
|
108
|
+
check_quota("file_migrated", quantity=1, allow_offline=is_tier1 and not force_llm)
|
|
107
109
|
except QuotaError as e:
|
|
108
110
|
show_quota_exceeded_message(e)
|
|
109
111
|
raise SystemExit(1) from e
|
|
110
112
|
|
|
111
|
-
# Load knowledge base
|
|
113
|
+
# Load knowledge base (optional - YAML may not exist for all libraries)
|
|
112
114
|
loader = KnowledgeBaseLoader()
|
|
115
|
+
knowledge: LibraryKnowledge | None = None
|
|
113
116
|
|
|
114
117
|
try:
|
|
115
118
|
knowledge = loader.load(library)
|
|
116
|
-
except FileNotFoundError
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
except FileNotFoundError:
|
|
120
|
+
if verbose:
|
|
121
|
+
console.print(
|
|
122
|
+
f"[dim]No knowledge base YAML for {library} - using generated knowledge[/]"
|
|
123
|
+
)
|
|
120
124
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
# Display migration info with fallback for missing YAML
|
|
126
|
+
if knowledge:
|
|
127
|
+
console.print(
|
|
128
|
+
Panel(
|
|
129
|
+
f"[bold]Upgrading {knowledge.display_name}[/] to version [cyan]{target}[/]\n\n"
|
|
130
|
+
f"{knowledge.description}\n"
|
|
131
|
+
f"Migration guide: {knowledge.migration_guide_url or 'N/A'}",
|
|
132
|
+
title="Codeshift Migration",
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
console.print(
|
|
137
|
+
Panel(
|
|
138
|
+
f"[bold]Upgrading {library}[/] to version [cyan]{target}[/]\n\n"
|
|
139
|
+
"Using AI-powered migration (no static knowledge base available)",
|
|
140
|
+
title="Codeshift Migration",
|
|
141
|
+
)
|
|
129
142
|
)
|
|
130
|
-
)
|
|
131
143
|
|
|
132
144
|
# Step 1: Parse dependencies
|
|
133
145
|
with Progress(
|
|
@@ -271,7 +283,25 @@ def upgrade(
|
|
|
271
283
|
console.print(f"\n[yellow]No {library} imports found in the codebase.[/]")
|
|
272
284
|
return
|
|
273
285
|
|
|
274
|
-
# Step 4: Apply transforms
|
|
286
|
+
# Step 4: Apply transforms using MigrationEngine
|
|
287
|
+
# Import here to avoid circular dependency (upgrade.py -> migrator -> llm_migrator -> api_client -> auth -> cli -> upgrade.py)
|
|
288
|
+
from codeshift.migrator import get_migration_engine
|
|
289
|
+
|
|
290
|
+
engine = get_migration_engine()
|
|
291
|
+
|
|
292
|
+
# Check auth for non-Tier1 libraries or force-llm mode
|
|
293
|
+
llm_required = force_llm or not is_tier1
|
|
294
|
+
if llm_required and not engine.llm_migrator.is_available:
|
|
295
|
+
console.print(
|
|
296
|
+
Panel(
|
|
297
|
+
f"[yellow]LLM migration required for {library}[/]\n\n"
|
|
298
|
+
"Run [cyan]codeshift login[/] and upgrade to Pro tier for LLM features.",
|
|
299
|
+
title="Authentication Required",
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
if not is_tier1:
|
|
303
|
+
raise SystemExit(1)
|
|
304
|
+
|
|
275
305
|
with Progress(
|
|
276
306
|
SpinnerColumn(),
|
|
277
307
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -286,45 +316,28 @@ def upgrade(
|
|
|
286
316
|
|
|
287
317
|
results: list[TransformResult] = []
|
|
288
318
|
|
|
319
|
+
def migration_progress(msg: str) -> None:
|
|
320
|
+
progress.update(task, description=msg)
|
|
321
|
+
|
|
289
322
|
for file_path in files_to_transform:
|
|
290
323
|
try:
|
|
291
324
|
source_code = file_path.read_text()
|
|
292
325
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
"
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if transform_func:
|
|
303
|
-
transformed_code, changes = transform_func(source_code)
|
|
304
|
-
# Create TransformResult from the function output
|
|
305
|
-
result = TransformResult(
|
|
306
|
-
file_path=file_path,
|
|
307
|
-
status=TransformStatus.SUCCESS if changes else TransformStatus.NO_CHANGES,
|
|
308
|
-
original_code=source_code,
|
|
309
|
-
transformed_code=transformed_code,
|
|
310
|
-
changes=[
|
|
311
|
-
TransformChange(
|
|
312
|
-
description=c.description,
|
|
313
|
-
line_number=c.line_number,
|
|
314
|
-
original=c.original,
|
|
315
|
-
replacement=c.replacement,
|
|
316
|
-
transform_name=c.transform_name,
|
|
317
|
-
confidence=getattr(c, "confidence", 1.0),
|
|
318
|
-
)
|
|
319
|
-
for c in changes
|
|
320
|
-
],
|
|
321
|
-
)
|
|
322
|
-
else:
|
|
323
|
-
console.print(f"[yellow]Warning:[/] No transformer available for {library}")
|
|
324
|
-
continue
|
|
326
|
+
result = engine.run_migration(
|
|
327
|
+
code=source_code,
|
|
328
|
+
file_path=file_path,
|
|
329
|
+
library=library,
|
|
330
|
+
old_version=current_version or "1.0",
|
|
331
|
+
new_version=target,
|
|
332
|
+
knowledge_base=generated_kb,
|
|
333
|
+
progress_callback=migration_progress if verbose else None,
|
|
334
|
+
)
|
|
325
335
|
|
|
326
336
|
if result.has_changes:
|
|
327
337
|
results.append(result)
|
|
338
|
+
elif result.errors:
|
|
339
|
+
for error in result.errors:
|
|
340
|
+
console.print(f"[yellow]Warning ({file_path.name}):[/] {error}")
|
|
328
341
|
|
|
329
342
|
except Exception as e:
|
|
330
343
|
console.print(f"[red]Error processing {file_path}:[/] {e}")
|
|
@@ -69,7 +69,7 @@ breaking_changes:
|
|
|
69
69
|
|
|
70
70
|
# connector_owner default change
|
|
71
71
|
- symbol: "ClientSession(connector=..., connector_owner=None)"
|
|
72
|
-
change_type:
|
|
72
|
+
change_type: behavior_changed
|
|
73
73
|
severity: medium
|
|
74
74
|
from_version: "3.7"
|
|
75
75
|
to_version: "3.9"
|
|
@@ -133,7 +133,7 @@ breaking_changes:
|
|
|
133
133
|
|
|
134
134
|
# Middleware signature changes (old-style to new-style)
|
|
135
135
|
- symbol: "@middleware"
|
|
136
|
-
change_type:
|
|
136
|
+
change_type: signature_changed
|
|
137
137
|
severity: high
|
|
138
138
|
from_version: "3.7"
|
|
139
139
|
to_version: "3.9"
|
|
@@ -154,7 +154,7 @@ breaking_changes:
|
|
|
154
154
|
transform_name: ws_connect_timeout_rename
|
|
155
155
|
|
|
156
156
|
- symbol: "ws_connect(receive_timeout=...)"
|
|
157
|
-
change_type:
|
|
157
|
+
change_type: behavior_changed
|
|
158
158
|
severity: low
|
|
159
159
|
from_version: "3.7"
|
|
160
160
|
to_version: "3.9"
|
|
@@ -119,7 +119,7 @@ breaking_changes:
|
|
|
119
119
|
|
|
120
120
|
# Response.iter_lines() behavior change
|
|
121
121
|
- symbol: "Response.iter_lines()"
|
|
122
|
-
change_type:
|
|
122
|
+
change_type: behavior_changed
|
|
123
123
|
severity: medium
|
|
124
124
|
from_version: "0.23"
|
|
125
125
|
to_version: "0.24"
|
|
@@ -130,7 +130,7 @@ breaking_changes:
|
|
|
130
130
|
|
|
131
131
|
# NetRC authentication change
|
|
132
132
|
- symbol: "trust_env=True"
|
|
133
|
-
change_type:
|
|
133
|
+
change_type: behavior_changed
|
|
134
134
|
severity: medium
|
|
135
135
|
from_version: "0.23"
|
|
136
136
|
to_version: "0.24"
|
|
@@ -141,7 +141,7 @@ breaking_changes:
|
|
|
141
141
|
|
|
142
142
|
# Query parameter encoding change
|
|
143
143
|
- symbol: "params encoding"
|
|
144
|
-
change_type:
|
|
144
|
+
change_type: behavior_changed
|
|
145
145
|
severity: low
|
|
146
146
|
from_version: "0.23"
|
|
147
147
|
to_version: "0.24"
|
|
@@ -173,7 +173,7 @@ breaking_changes:
|
|
|
173
173
|
|
|
174
174
|
# Follow redirects default change
|
|
175
175
|
- symbol: "follow_redirects"
|
|
176
|
-
change_type:
|
|
176
|
+
change_type: behavior_changed
|
|
177
177
|
severity: high
|
|
178
178
|
from_version: "0.19"
|
|
179
179
|
to_version: "0.20"
|
{codeshift-0.4.0 → codeshift-0.5.0}/codeshift/migrator/transforms/marshmallow_transformer.py
RENAMED
|
@@ -191,6 +191,10 @@ class MarshmallowTransformer(BaseTransformer):
|
|
|
191
191
|
- default -> dump_default
|
|
192
192
|
- load_from -> data_key
|
|
193
193
|
- dump_to -> data_key
|
|
194
|
+
|
|
195
|
+
Special handling: When both load_from and dump_to are present, only one data_key
|
|
196
|
+
is kept (preferring load_from) and a warning comment is added about the removed
|
|
197
|
+
dump_to value.
|
|
194
198
|
"""
|
|
195
199
|
# Check if this is a fields.* call or a Field-like call
|
|
196
200
|
func_name = self._get_call_func_name(node.func)
|
|
@@ -232,6 +236,27 @@ class MarshmallowTransformer(BaseTransformer):
|
|
|
232
236
|
if func_name not in field_types:
|
|
233
237
|
return node
|
|
234
238
|
|
|
239
|
+
# First pass: detect if both load_from and dump_to are present
|
|
240
|
+
load_from_arg = None
|
|
241
|
+
dump_to_arg = None
|
|
242
|
+
load_from_value = None
|
|
243
|
+
dump_to_value = None
|
|
244
|
+
|
|
245
|
+
for arg in node.args:
|
|
246
|
+
if isinstance(arg.keyword, cst.Name):
|
|
247
|
+
if arg.keyword.value == "load_from":
|
|
248
|
+
load_from_arg = arg
|
|
249
|
+
# Extract the value for comparison/warning
|
|
250
|
+
if isinstance(arg.value, cst.SimpleString):
|
|
251
|
+
load_from_value = arg.value.value
|
|
252
|
+
elif arg.keyword.value == "dump_to":
|
|
253
|
+
dump_to_arg = arg
|
|
254
|
+
# Extract the value for comparison/warning
|
|
255
|
+
if isinstance(arg.value, cst.SimpleString):
|
|
256
|
+
dump_to_value = arg.value.value
|
|
257
|
+
|
|
258
|
+
has_both_load_from_and_dump_to = load_from_arg is not None and dump_to_arg is not None
|
|
259
|
+
|
|
235
260
|
new_args = []
|
|
236
261
|
changed = False
|
|
237
262
|
param_mappings = {
|
|
@@ -245,6 +270,31 @@ class MarshmallowTransformer(BaseTransformer):
|
|
|
245
270
|
if isinstance(arg.keyword, cst.Name) and arg.keyword.value in param_mappings:
|
|
246
271
|
old_name = arg.keyword.value
|
|
247
272
|
new_name = param_mappings[old_name]
|
|
273
|
+
|
|
274
|
+
# Special case: skip dump_to when both load_from and dump_to exist
|
|
275
|
+
if old_name == "dump_to" and has_both_load_from_and_dump_to:
|
|
276
|
+
changed = True
|
|
277
|
+
# Record that dump_to was removed due to conflict
|
|
278
|
+
self.record_change(
|
|
279
|
+
description=(
|
|
280
|
+
f"Remove '{old_name}' parameter - Marshmallow 3.x uses single "
|
|
281
|
+
f"data_key for both load/dump. load_from value kept, dump_to="
|
|
282
|
+
f"{dump_to_value} removed. Manual review may be needed if "
|
|
283
|
+
f"load_from ({load_from_value}) != dump_to ({dump_to_value})."
|
|
284
|
+
),
|
|
285
|
+
line_number=1,
|
|
286
|
+
original=f"{func_name}(load_from=..., dump_to=...)",
|
|
287
|
+
replacement=f"{func_name}(data_key=...)",
|
|
288
|
+
transform_name="remove_dump_to_conflict",
|
|
289
|
+
notes=(
|
|
290
|
+
f"dump_to={dump_to_value} was removed because load_from="
|
|
291
|
+
f"{load_from_value} was also present. In Marshmallow 3.x, "
|
|
292
|
+
"data_key serves both purposes."
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
# Skip adding this arg
|
|
296
|
+
continue
|
|
297
|
+
|
|
248
298
|
new_arg = arg.with_changes(keyword=cst.Name(new_name))
|
|
249
299
|
new_args.append(new_arg)
|
|
250
300
|
changed = True
|