codeshift 0.4.0__py3-none-any.whl → 0.7.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 (32) hide show
  1. codeshift/__init__.py +1 -1
  2. codeshift/cli/commands/auth.py +41 -25
  3. codeshift/cli/commands/health.py +244 -0
  4. codeshift/cli/commands/upgrade.py +68 -55
  5. codeshift/cli/main.py +2 -0
  6. codeshift/health/__init__.py +50 -0
  7. codeshift/health/calculator.py +217 -0
  8. codeshift/health/metrics/__init__.py +63 -0
  9. codeshift/health/metrics/documentation.py +209 -0
  10. codeshift/health/metrics/freshness.py +180 -0
  11. codeshift/health/metrics/migration_readiness.py +142 -0
  12. codeshift/health/metrics/security.py +225 -0
  13. codeshift/health/metrics/test_coverage.py +191 -0
  14. codeshift/health/models.py +284 -0
  15. codeshift/health/report.py +310 -0
  16. codeshift/knowledge/generator.py +6 -0
  17. codeshift/knowledge_base/libraries/aiohttp.yaml +3 -3
  18. codeshift/knowledge_base/libraries/httpx.yaml +4 -4
  19. codeshift/knowledge_base/libraries/pytest.yaml +1 -1
  20. codeshift/knowledge_base/models.py +1 -0
  21. codeshift/migrator/transforms/marshmallow_transformer.py +50 -0
  22. codeshift/migrator/transforms/pydantic_v1_to_v2.py +191 -22
  23. codeshift/scanner/code_scanner.py +22 -2
  24. codeshift/utils/api_client.py +144 -4
  25. codeshift/utils/credential_store.py +393 -0
  26. codeshift/utils/llm_client.py +111 -9
  27. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/METADATA +4 -1
  28. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/RECORD +32 -20
  29. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/WHEEL +0 -0
  30. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/entry_points.txt +0 -0
  31. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/licenses/LICENSE +0 -0
  32. {codeshift-0.4.0.dist-info → codeshift-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,393 @@
1
+ """Secure credential storage with encryption.
2
+
3
+ This module provides encrypted storage for sensitive credentials like API keys.
4
+ Credentials are encrypted using Fernet (AES-128-CBC) with a key derived from
5
+ a machine-specific identifier using PBKDF2-SHA256.
6
+
7
+ Security features:
8
+ - AES-128 encryption via Fernet
9
+ - Machine-bound encryption key (prevents credential theft across machines)
10
+ - PBKDF2-SHA256 key derivation with 100,000 iterations
11
+ - File permissions restricted to owner only (0o600)
12
+ - Automatic migration from plaintext credentials with secure deletion
13
+ """
14
+
15
+ import base64
16
+ import hashlib
17
+ import json
18
+ import logging
19
+ import os
20
+ import platform
21
+ import secrets
22
+ import uuid
23
+ from pathlib import Path
24
+ from typing import Any, cast
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Try to import cryptography, provide helpful error if not installed
29
+ try:
30
+ from cryptography.fernet import Fernet, InvalidToken
31
+ from cryptography.hazmat.backends import default_backend
32
+ from cryptography.hazmat.primitives import hashes
33
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
34
+
35
+ CRYPTOGRAPHY_AVAILABLE = True
36
+ except ImportError:
37
+ CRYPTOGRAPHY_AVAILABLE = False
38
+ Fernet = None # type: ignore
39
+ InvalidToken = Exception # type: ignore
40
+
41
+
42
+ class CredentialDecryptionError(Exception):
43
+ """Raised when credentials cannot be decrypted.
44
+
45
+ This typically occurs when:
46
+ - Credentials were created on a different machine
47
+ - The machine identifier has changed
48
+ - The credential file is corrupted
49
+ """
50
+
51
+ def __init__(self, message: str | None = None):
52
+ default_msg = (
53
+ "Failed to decrypt credentials. This may happen if credentials were "
54
+ "created on a different machine. Please run 'codeshift login' to "
55
+ "re-authenticate."
56
+ )
57
+ super().__init__(message or default_msg)
58
+
59
+
60
+ class CredentialStore:
61
+ """Secure encrypted credential storage.
62
+
63
+ Credentials are encrypted using a key derived from a machine-specific
64
+ identifier, making them non-portable between machines for security.
65
+
66
+ Example:
67
+ store = CredentialStore()
68
+ store.save({"api_key": "secret123", "email": "user@example.com"})
69
+ creds = store.load()
70
+ """
71
+
72
+ # Default paths
73
+ DEFAULT_CONFIG_DIR = Path.home() / ".config" / "codeshift"
74
+ CREDENTIALS_FILE = "credentials.enc"
75
+ LEGACY_CREDENTIALS_FILE = "credentials.json"
76
+ SALT_FILE = ".salt"
77
+
78
+ # PBKDF2 parameters
79
+ PBKDF2_ITERATIONS = 100_000
80
+ KEY_LENGTH = 32 # 256 bits for Fernet
81
+
82
+ def __init__(self, config_dir: Path | None = None):
83
+ """Initialize the credential store.
84
+
85
+ Args:
86
+ config_dir: Directory for storing credentials. Defaults to ~/.config/codeshift
87
+ """
88
+ self.config_dir = config_dir or self.DEFAULT_CONFIG_DIR
89
+ self.credentials_path = self.config_dir / self.CREDENTIALS_FILE
90
+ self.legacy_path = self.config_dir / self.LEGACY_CREDENTIALS_FILE
91
+ self.salt_path = self.config_dir / self.SALT_FILE
92
+
93
+ # Check if cryptography is available
94
+ if not CRYPTOGRAPHY_AVAILABLE:
95
+ logger.warning(
96
+ "cryptography package not installed. "
97
+ "Credentials will be stored in plaintext. "
98
+ "Install with: pip install cryptography"
99
+ )
100
+
101
+ def _get_machine_identifier(self) -> str:
102
+ """Get a stable machine identifier for key derivation.
103
+
104
+ Combines multiple system attributes to create a stable identifier
105
+ that persists across reboots but changes between machines.
106
+
107
+ Returns:
108
+ A string identifier unique to this machine.
109
+ """
110
+ components = []
111
+
112
+ # Platform info (stable)
113
+ components.append(platform.node())
114
+ components.append(platform.system())
115
+ components.append(platform.machine())
116
+
117
+ # Try to get hardware UUID (most stable)
118
+ try:
119
+ if platform.system() == "Darwin":
120
+ # macOS: Use IOPlatformUUID
121
+ import subprocess
122
+
123
+ result = subprocess.run(
124
+ ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
125
+ capture_output=True,
126
+ text=True,
127
+ timeout=5,
128
+ )
129
+ for line in result.stdout.split("\n"):
130
+ if "IOPlatformUUID" in line:
131
+ uuid_str = line.split('"')[-2]
132
+ components.append(uuid_str)
133
+ break
134
+ elif platform.system() == "Linux":
135
+ # Linux: Try machine-id
136
+ for path in ["/etc/machine-id", "/var/lib/dbus/machine-id"]:
137
+ try:
138
+ with open(path) as f:
139
+ components.append(f.read().strip())
140
+ break
141
+ except (OSError, PermissionError):
142
+ continue
143
+ elif platform.system() == "Windows":
144
+ # Windows: Use MachineGuid from registry
145
+ try:
146
+ import winreg
147
+
148
+ key = winreg.OpenKey( # type: ignore[attr-defined]
149
+ winreg.HKEY_LOCAL_MACHINE, # type: ignore[attr-defined]
150
+ r"SOFTWARE\Microsoft\Cryptography",
151
+ )
152
+ machine_guid = winreg.QueryValueEx(key, "MachineGuid")[0] # type: ignore[attr-defined]
153
+ components.append(machine_guid)
154
+ except Exception:
155
+ pass
156
+ except Exception as e:
157
+ logger.debug(f"Could not get hardware UUID: {e}")
158
+
159
+ # Fallback to UUID based on hostname (less stable but better than nothing)
160
+ if len(components) < 4:
161
+ components.append(str(uuid.getnode()))
162
+
163
+ # Create hash of all components
164
+ combined = "|".join(components)
165
+ return hashlib.sha256(combined.encode()).hexdigest()
166
+
167
+ def _get_or_create_salt(self) -> bytes:
168
+ """Get or create a random salt for key derivation.
169
+
170
+ The salt is stored alongside credentials and is required for decryption.
171
+
172
+ Returns:
173
+ 32-byte random salt.
174
+ """
175
+ if self.salt_path.exists():
176
+ try:
177
+ return self.salt_path.read_bytes()
178
+ except OSError as e:
179
+ logger.warning(f"Could not read salt file: {e}")
180
+
181
+ # Generate new salt
182
+ salt = secrets.token_bytes(32)
183
+
184
+ # Save salt with restricted permissions
185
+ self.config_dir.mkdir(parents=True, exist_ok=True)
186
+ self.salt_path.write_bytes(salt)
187
+ os.chmod(self.salt_path, 0o600)
188
+
189
+ return salt
190
+
191
+ def _derive_key(self, salt: bytes) -> bytes:
192
+ """Derive an encryption key from the machine identifier.
193
+
194
+ Uses PBKDF2-SHA256 with 100,000 iterations for key derivation.
195
+
196
+ Args:
197
+ salt: Random salt for key derivation.
198
+
199
+ Returns:
200
+ 32-byte encryption key suitable for Fernet.
201
+ """
202
+ if not CRYPTOGRAPHY_AVAILABLE:
203
+ raise ImportError(
204
+ "cryptography package required for encryption. "
205
+ "Install with: pip install cryptography"
206
+ )
207
+
208
+ machine_id = self._get_machine_identifier()
209
+
210
+ kdf = PBKDF2HMAC(
211
+ algorithm=hashes.SHA256(),
212
+ length=self.KEY_LENGTH,
213
+ salt=salt,
214
+ iterations=self.PBKDF2_ITERATIONS,
215
+ backend=default_backend(),
216
+ )
217
+
218
+ key = kdf.derive(machine_id.encode())
219
+ return base64.urlsafe_b64encode(key)
220
+
221
+ def _encrypt(self, data: dict) -> bytes:
222
+ """Encrypt credential data.
223
+
224
+ Args:
225
+ data: Dictionary of credentials to encrypt.
226
+
227
+ Returns:
228
+ Encrypted bytes.
229
+ """
230
+ if not CRYPTOGRAPHY_AVAILABLE:
231
+ # Fallback to plaintext (with warning logged in __init__)
232
+ return json.dumps(data).encode()
233
+
234
+ salt = self._get_or_create_salt()
235
+ key = self._derive_key(salt)
236
+ f = Fernet(key)
237
+
238
+ plaintext = json.dumps(data).encode()
239
+ return f.encrypt(plaintext)
240
+
241
+ def _decrypt(self, ciphertext: bytes) -> dict:
242
+ """Decrypt credential data.
243
+
244
+ Args:
245
+ ciphertext: Encrypted bytes.
246
+
247
+ Returns:
248
+ Dictionary of credentials.
249
+
250
+ Raises:
251
+ CredentialDecryptionError: If decryption fails.
252
+ """
253
+ if not CRYPTOGRAPHY_AVAILABLE:
254
+ # Assume plaintext if cryptography not available
255
+ try:
256
+ return cast(dict[Any, Any], json.loads(ciphertext.decode()))
257
+ except (json.JSONDecodeError, UnicodeDecodeError) as e:
258
+ raise CredentialDecryptionError(f"Invalid credential format: {e}") from e
259
+
260
+ try:
261
+ salt = self._get_or_create_salt()
262
+ key = self._derive_key(salt)
263
+ f = Fernet(key)
264
+
265
+ plaintext = f.decrypt(ciphertext)
266
+ return cast(dict[Any, Any], json.loads(plaintext.decode()))
267
+ except InvalidToken as e:
268
+ raise CredentialDecryptionError(
269
+ "Failed to decrypt credentials. The encryption key may have changed "
270
+ "(different machine or hardware change). Please run 'codeshift login' "
271
+ "to re-authenticate."
272
+ ) from e
273
+ except (json.JSONDecodeError, UnicodeDecodeError) as e:
274
+ raise CredentialDecryptionError(f"Corrupted credential data: {e}") from e
275
+
276
+ def save(self, credentials: dict[str, Any]) -> None:
277
+ """Save credentials securely.
278
+
279
+ Args:
280
+ credentials: Dictionary containing credentials (api_key, email, etc.)
281
+ """
282
+ self.config_dir.mkdir(parents=True, exist_ok=True)
283
+
284
+ # Encrypt and save
285
+ encrypted = self._encrypt(credentials)
286
+ self.credentials_path.write_bytes(encrypted)
287
+
288
+ # Set restrictive permissions (owner read/write only)
289
+ os.chmod(self.credentials_path, 0o600)
290
+
291
+ logger.debug("Credentials saved securely")
292
+
293
+ def load(self) -> dict[str, Any] | None:
294
+ """Load credentials from secure storage.
295
+
296
+ Automatically migrates from plaintext storage if found.
297
+
298
+ Returns:
299
+ Dictionary of credentials, or None if not found.
300
+
301
+ Raises:
302
+ CredentialDecryptionError: If credentials exist but cannot be decrypted.
303
+ """
304
+ # Check for encrypted credentials first
305
+ if self.credentials_path.exists():
306
+ try:
307
+ ciphertext = self.credentials_path.read_bytes()
308
+ return self._decrypt(ciphertext)
309
+ except OSError as e:
310
+ logger.error(f"Could not read credentials file: {e}")
311
+ return None
312
+
313
+ # Check for legacy plaintext credentials and migrate
314
+ if self.legacy_path.exists():
315
+ logger.info("Migrating credentials from plaintext to encrypted storage")
316
+ try:
317
+ plaintext = self.legacy_path.read_text()
318
+ credentials: dict[str, Any] = json.loads(plaintext)
319
+
320
+ # Save encrypted
321
+ self.save(credentials)
322
+
323
+ # Securely delete legacy file
324
+ self._secure_delete(self.legacy_path)
325
+
326
+ return credentials
327
+ except (OSError, json.JSONDecodeError) as e:
328
+ logger.warning(f"Could not migrate legacy credentials: {e}")
329
+ return None
330
+
331
+ return None
332
+
333
+ def delete(self) -> None:
334
+ """Delete stored credentials securely."""
335
+ # Delete encrypted credentials
336
+ if self.credentials_path.exists():
337
+ self._secure_delete(self.credentials_path)
338
+
339
+ # Also delete legacy file if it exists
340
+ if self.legacy_path.exists():
341
+ self._secure_delete(self.legacy_path)
342
+
343
+ # Delete salt file
344
+ if self.salt_path.exists():
345
+ self._secure_delete(self.salt_path)
346
+
347
+ logger.debug("Credentials deleted")
348
+
349
+ def _secure_delete(self, path: Path) -> None:
350
+ """Securely delete a file by overwriting before unlinking.
351
+
352
+ Args:
353
+ path: Path to the file to delete.
354
+ """
355
+ try:
356
+ if path.exists():
357
+ # Overwrite with random data
358
+ size = path.stat().st_size
359
+ if size > 0:
360
+ with open(path, "wb") as f:
361
+ f.write(secrets.token_bytes(size))
362
+ f.flush()
363
+ os.fsync(f.fileno())
364
+
365
+ # Then delete
366
+ path.unlink()
367
+ except OSError as e:
368
+ logger.warning(f"Could not securely delete {path}: {e}")
369
+ # Try regular delete as fallback
370
+ try:
371
+ path.unlink()
372
+ except OSError:
373
+ pass
374
+
375
+ def exists(self) -> bool:
376
+ """Check if credentials exist.
377
+
378
+ Returns:
379
+ True if credentials file exists (encrypted or legacy).
380
+ """
381
+ return self.credentials_path.exists() or self.legacy_path.exists()
382
+
383
+
384
+ # Default credential store instance
385
+ _default_store: CredentialStore | None = None
386
+
387
+
388
+ def get_credential_store() -> CredentialStore:
389
+ """Get the default credential store instance."""
390
+ global _default_store
391
+ if _default_store is None:
392
+ _default_store = CredentialStore()
393
+ return _default_store
@@ -1,10 +1,38 @@
1
- """Anthropic Claude client wrapper for LLM-based migrations."""
1
+ """Anthropic Claude client wrapper for LLM-based migrations.
2
2
 
3
+ SECURITY NOTE: This module is intended for internal use only.
4
+ Direct access to the LLM client bypasses quota and billing controls.
5
+ Use the Codeshift API client (api_client.py) for all LLM operations.
6
+ """
7
+
8
+ import logging
3
9
  import os
10
+ import sys
4
11
  from dataclasses import dataclass
5
12
 
6
13
  from anthropic import Anthropic
7
14
 
15
+ # Prevent any exports from this module
16
+ __all__: list[str] = []
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class DirectLLMAccessError(Exception):
22
+ """Raised when code attempts to bypass the Codeshift API and access LLM directly.
23
+
24
+ The LLMClient is for internal server-side use only. Client applications
25
+ should use the Codeshift API which enforces quotas, billing, and access control.
26
+ """
27
+
28
+ def __init__(self, message: str | None = None):
29
+ default_msg = (
30
+ "Direct LLM access is not permitted. "
31
+ "Use the Codeshift API client for LLM operations. "
32
+ "Run 'codeshift login' to authenticate."
33
+ )
34
+ super().__init__(message or default_msg)
35
+
8
36
 
9
37
  @dataclass
10
38
  class LLMResponse:
@@ -17,8 +45,54 @@ class LLMResponse:
17
45
  error: str | None = None
18
46
 
19
47
 
20
- class LLMClient:
21
- """Client for interacting with Anthropic's Claude API."""
48
+ def _check_direct_access_attempt() -> None:
49
+ """Check if this is an unauthorized direct access attempt.
50
+
51
+ Raises:
52
+ DirectLLMAccessError: If direct access is detected from external code.
53
+ """
54
+ # Check if ANTHROPIC_API_KEY is set by the user (potential bypass attempt)
55
+ # This is allowed only for internal server use or development
56
+ if os.environ.get("ANTHROPIC_API_KEY"):
57
+ # Check if this is being called from within the codeshift package
58
+ frame = sys._getframe(2) # Caller's caller
59
+ caller_file = frame.f_code.co_filename
60
+
61
+ # Allow internal codeshift modules and tests
62
+ allowed_paths = (
63
+ "codeshift/",
64
+ "codeshift\\", # Windows path
65
+ "tests/",
66
+ "tests\\",
67
+ "<stdin>", # Interactive Python
68
+ "<string>", # exec/eval
69
+ )
70
+
71
+ is_internal = any(path in caller_file for path in allowed_paths)
72
+
73
+ # Also check for environment flag indicating authorized server use
74
+ is_authorized_server = os.environ.get("CODESHIFT_SERVER_MODE") == "true"
75
+
76
+ if not is_internal and not is_authorized_server:
77
+ logger.warning(
78
+ "Direct LLM access attempt detected from: %s. "
79
+ "This bypasses quota and billing controls.",
80
+ caller_file,
81
+ )
82
+ raise DirectLLMAccessError(
83
+ "Direct use of ANTHROPIC_API_KEY detected. "
84
+ "This bypasses Codeshift's quota and billing system. "
85
+ "Use 'codeshift upgrade' commands which route through the API."
86
+ )
87
+
88
+
89
+ class _LLMClient:
90
+ """Internal client for interacting with Anthropic's Claude API.
91
+
92
+ SECURITY: This class is private (prefixed with _) and should not be
93
+ instantiated directly by external code. All LLM operations should go
94
+ through the Codeshift API which enforces access controls.
95
+ """
22
96
 
23
97
  DEFAULT_MODEL = "claude-sonnet-4-20250514"
24
98
  MAX_TOKENS = 4096
@@ -27,13 +101,19 @@ class LLMClient:
27
101
  self,
28
102
  api_key: str | None = None,
29
103
  model: str | None = None,
104
+ _bypass_check: bool = False,
30
105
  ):
31
106
  """Initialize the LLM client.
32
107
 
33
108
  Args:
34
109
  api_key: Anthropic API key. Defaults to ANTHROPIC_API_KEY env var.
35
110
  model: Model to use. Defaults to claude-sonnet-4-20250514.
111
+ _bypass_check: Internal flag to bypass access check (for server use).
36
112
  """
113
+ # Security check for unauthorized direct access
114
+ if not _bypass_check:
115
+ _check_direct_access_attempt()
116
+
37
117
  self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
38
118
  self.model = model or self.DEFAULT_MODEL
39
119
  self._client: Anthropic | None = None
@@ -45,7 +125,7 @@ class LLMClient:
45
125
  if not self.api_key:
46
126
  raise ValueError(
47
127
  "Anthropic API key not found. Set ANTHROPIC_API_KEY environment variable "
48
- "or pass api_key to LLMClient."
128
+ "or pass api_key to _LLMClient."
49
129
  )
50
130
  self._client = Anthropic(api_key=self.api_key)
51
131
  return self._client
@@ -209,13 +289,35 @@ Provide a brief explanation (2-3 sentences) of what changed and why:"""
209
289
  return self.generate(prompt, system_prompt=system_prompt, max_tokens=500)
210
290
 
211
291
 
212
- # Singleton instance for convenience
213
- _default_client: LLMClient | None = None
292
+ # Keep backward compatibility alias but mark as deprecated
293
+ # This will be removed in a future version
294
+ LLMClient = _LLMClient
295
+
296
+
297
+ # Singleton instance for internal use only
298
+ _default_client: _LLMClient | None = None
214
299
 
215
300
 
216
- def get_llm_client() -> LLMClient:
217
- """Get the default LLM client instance."""
301
+ def _get_llm_client(_bypass_check: bool = False) -> _LLMClient:
302
+ """Get the default LLM client instance.
303
+
304
+ INTERNAL USE ONLY: This function is for internal codeshift server use.
305
+ External code should use the Codeshift API client.
306
+
307
+ Args:
308
+ _bypass_check: Internal flag to bypass access check (for server use).
309
+ """
218
310
  global _default_client
219
311
  if _default_client is None:
220
- _default_client = LLMClient()
312
+ _default_client = _LLMClient(_bypass_check=_bypass_check)
221
313
  return _default_client
314
+
315
+
316
+ # Keep backward compatibility but with security check
317
+ def get_llm_client() -> _LLMClient:
318
+ """Get the default LLM client instance.
319
+
320
+ DEPRECATED: Use the Codeshift API client (api_client.py) instead.
321
+ This function will be removed in a future version.
322
+ """
323
+ return _get_llm_client(_bypass_check=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeshift
3
- Version: 0.4.0
3
+ Version: 0.7.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,39 +1,50 @@
1
- codeshift/__init__.py,sha256=L-ImO-zi7CvCuKCl_PYICUgTDppPXgYDaUoIQWukRzU,202
1
+ codeshift/__init__.py,sha256=PqYe0Tu6RgBALNREdCA84JuTWQPa29eB4_cBTueBalc,202
2
2
  codeshift/analyzer/__init__.py,sha256=m61k8rtOyHQNoPLDeoe0S9WSy9syvCyMgLZ07ojpoNU,188
3
3
  codeshift/analyzer/risk_assessor.py,sha256=nKplyymbsqcbsZyDLVZ2zukpQlPD5KIvO-FBJ4AZFYc,13422
4
4
  codeshift/cli/__init__.py,sha256=rf0c8oMqzYktdLH8ga2sA7rS5GsJse8dGfIbfW4DDXo,87
5
- codeshift/cli/main.py,sha256=iLXy2QMLXoiM88sn-XEHk9vJncB-WeKfEq0eps3ZHig,6983
5
+ codeshift/cli/main.py,sha256=clTP_TFu1x6T0TLRvI-g2WbO4l3pbMZckQQ3AwnsIy8,7056
6
6
  codeshift/cli/package_manager.py,sha256=K7spYHSQ6VoHlrzM3L9B2JoxBTe_9gLd57P6ME0PsSI,2807
7
7
  codeshift/cli/quota.py,sha256=zBiY3zqCGEyxbS3vnoQdgBld1emMQzLc0_5cUOWy9U8,5989
8
8
  codeshift/cli/commands/__init__.py,sha256=Kbs7DFUWOXkw5-9jiiR03cuUJeo5byusLr_Y3cKFE3E,218
9
9
  codeshift/cli/commands/apply.py,sha256=JqUiu6I5WD25677SXpOejKxLWNIUQUKrbOQ_JbpoEak,11213
10
- codeshift/cli/commands/auth.py,sha256=FO60p545yxYySw6v6CcJPUh-Q5byM_AUz1pxypccrUA,28066
10
+ codeshift/cli/commands/auth.py,sha256=bCGF9aEz-5PLsp1rOkHZ8A3GRtd9NtpC3tePlJ3ZO6M,28638
11
11
  codeshift/cli/commands/diff.py,sha256=4LjrVRu4lhj-aOBvClOnEnN0l2nZEU5S1_qzYoXL4dQ,6357
12
+ codeshift/cli/commands/health.py,sha256=aH2QMIr8ucf1WVSZHH4Bjux1WZosz18dtdA95TH_eX0,7701
12
13
  codeshift/cli/commands/scan.py,sha256=Eia3xW6sVZSiKtxd7JXyjIcOUsxduLGOuFjBhhp1ut0,11373
13
- codeshift/cli/commands/upgrade.py,sha256=JjPKbjMNDzBDZuz39JYas48Ch4vgrrtyc6AEoPq6Q6Q,15853
14
+ codeshift/cli/commands/upgrade.py,sha256=15SPmu09oi7Gu7KLvyDLbcF-YndkEoa0sPhlMZ-zsuo,15853
14
15
  codeshift/cli/commands/upgrade_all.py,sha256=FimS7SYJeYkQvhrWACLQG4QiyyynCgi9SapUb8Ax0Ik,18964
16
+ codeshift/health/__init__.py,sha256=UwyuKdvFJnqhZSUHFQ2wgtxpXol9AHelDiUaOnrKYGc,1207
17
+ codeshift/health/calculator.py,sha256=JD5UTVqHKxcJc8mUaWF3V2fy6EpyPO7VIbHGBrONDcI,8032
18
+ codeshift/health/models.py,sha256=7yTthtQyQNIEDlggg8JjXb_G4qfqYOvg5a87kuBvVis,8672
19
+ codeshift/health/report.py,sha256=fEolswSGjL8pETowqCPnmBbDIMPXazG-SliGFNbBfJU,10551
20
+ codeshift/health/metrics/__init__.py,sha256=9qS6nUPjbO99DICqJiUWuhYfMAn-Fxqxw3e3grks8IA,1748
21
+ codeshift/health/metrics/documentation.py,sha256=3dJ_oR6HcV-bFpIF2FCT1FT38Hh0_vWsMXabkh-oXLk,6375
22
+ codeshift/health/metrics/freshness.py,sha256=xzDYOq3_K_F2dYVF6W1Y5MSBW864FQKInv7rhgMb6VA,6096
23
+ codeshift/health/metrics/migration_readiness.py,sha256=mhXo_1yEmxe0kwW-0e6c4Dqx7mqbPZVL9mpfmgL2E14,5000
24
+ codeshift/health/metrics/security.py,sha256=rUco4Or_hXLY8ZSuNMT0DCyPJDfUHTrtDzIrsMJrjWk,7447
25
+ codeshift/health/metrics/test_coverage.py,sha256=0jsPcNGfhEnGDRB2LhReQDsiir5U9kVi2MKJ_6zCt-k,6046
15
26
  codeshift/knowledge/__init__.py,sha256=_YwrLgjvsJQuYajfnIhUQqFeircF0MfkI9zJBPZTupc,1221
16
27
  codeshift/knowledge/cache.py,sha256=aEx9aNDfrzCYMzlPRBzBOYiFIAGDcZJfQvcXroa5vsA,4837
17
- codeshift/knowledge/generator.py,sha256=WVCF0ULYmVQRHBiK6BTyS6FuArSCj-UDWzPJLujVbxU,7304
28
+ codeshift/knowledge/generator.py,sha256=t30Rf8ByFxjz3wUk8Dq5fWGcL2MLS_rP4Xik4OspAUs,7386
18
29
  codeshift/knowledge/models.py,sha256=tvM6dnZJ00nJttIDMYxKlc8fYadgCdP2bqMVTA7o3HY,5157
19
30
  codeshift/knowledge/parser.py,sha256=uWm8IjOYoV06Sx5_odyrLB93XL1GNRHdzuNVSFM-fMA,8493
20
31
  codeshift/knowledge/sources.py,sha256=4GA4z4gHADCAfeBTF3dAO2S6H4s9leUKoxKPK2Vcopk,12280
21
32
  codeshift/knowledge_base/__init__.py,sha256=-xiDpdKrizX-5FeA8NjnHxASkkv44BwiMORTYF29Vj4,368
22
33
  codeshift/knowledge_base/loader.py,sha256=xMOSJEtnlzA1sGHGqeWdwzv2rF-LvVfSgEx9Q5WMTGk,3484
23
- codeshift/knowledge_base/models.py,sha256=OwA9ts14aMW-PfWBi8GFcwHIGls5ERur7FCQXYcIU7k,3816
24
- codeshift/knowledge_base/libraries/aiohttp.yaml,sha256=0JjP5BQdW172MmUE0U-xeCeavO0PN8_a1hM6W7zqQLI,6469
34
+ codeshift/knowledge_base/models.py,sha256=YQ-Voe3y6O4Pge88YBsqM0Q64797-_7_lkyvmXArTeU,3836
35
+ codeshift/knowledge_base/libraries/aiohttp.yaml,sha256=LB-uyxpgghc6n4I7hKctvIvKsW-bbXo8HQQORCkDBFs,6472
25
36
  codeshift/knowledge_base/libraries/attrs.yaml,sha256=iih_l9GaETP-FTy0ktqgOHlcUIiXaIT7TInfquV8XHo,5776
26
37
  codeshift/knowledge_base/libraries/celery.yaml,sha256=jE0gvUv5VpzgIp8Pmgh_pL64SJoXpMZojTuxtczMgQg,8455
27
38
  codeshift/knowledge_base/libraries/click.yaml,sha256=JoGFDVzV_49ZWp0It0VTPjrfQOXBoDJXlEzuJxwUSFQ,6689
28
39
  codeshift/knowledge_base/libraries/django.yaml,sha256=F5FGfiZBOBAiHcTrUhNfjt4DUoa8MaNZCPCyFgSeFu8,12224
29
40
  codeshift/knowledge_base/libraries/fastapi.yaml,sha256=i6dXGbEP1bn2k06XgqzgvqE2Wle_NZRhUBTk06KhW3Y,6419
30
41
  codeshift/knowledge_base/libraries/flask.yaml,sha256=HETNUj5nrsCMH8sFwN1zPmUXGfcGWo5KhCDY4-hilR0,9401
31
- codeshift/knowledge_base/libraries/httpx.yaml,sha256=Pgr13Vs3O8jKNu0Ttga9cfaQpYwPkjNHzvCbAvu-itg,6516
42
+ codeshift/knowledge_base/libraries/httpx.yaml,sha256=goeaxr40vYglOIjHtm1M8zmzw8SafJhX77KA6kVAKRI,6520
32
43
  codeshift/knowledge_base/libraries/marshmallow.yaml,sha256=1K3VrNGsjka7bfHMOwUrlvBv-j2tgLOg7MDGflWDDZM,8307
33
44
  codeshift/knowledge_base/libraries/numpy.yaml,sha256=kpV1BNXTmLE9Lbki6caLHKpRzLYpPDkc-e3NtELtH1Y,13020
34
45
  codeshift/knowledge_base/libraries/pandas.yaml,sha256=cgu-dAaXIVyZivRr7swh3Df-GEpVfcxh5-J-6ow_pLc,10366
35
46
  codeshift/knowledge_base/libraries/pydantic.yaml,sha256=nTleM2JaabzrcJoMPrIYhPbV93Gl3GZ1uQnQSeHBw_w,8150
36
- codeshift/knowledge_base/libraries/pytest.yaml,sha256=I9_hz3_nqFxZVFoRJL2x7AP30bQxXHM1T6aU0PyEvQs,6601
47
+ codeshift/knowledge_base/libraries/pytest.yaml,sha256=6mSeeETC4yF4s1J22UIhhhplvYUOIGkDWo5ZSuq4nCw,6602
37
48
  codeshift/knowledge_base/libraries/requests.yaml,sha256=8Msjlt8v4mCEmTIdyvlpgtWsCXarpPqXNMPFlFd_Ojc,9035
38
49
  codeshift/knowledge_base/libraries/sqlalchemy.yaml,sha256=qEk1Nc2ovgCiBLPLsBrcsWM_KZaJn8MlHotDyZkzX9w,9717
39
50
  codeshift/migrator/__init__.py,sha256=V5ATKxw4hV6Ec3g78IeHkP92-VM4l6OzH0gi-lnU09w,467
@@ -49,27 +60,28 @@ codeshift/migrator/transforms/django_transformer.py,sha256=21gHStxJNHRj8x_pVfRB8
49
60
  codeshift/migrator/transforms/fastapi_transformer.py,sha256=axFZhCdMo3gAtHPxboSDhx56RasMjREcfaI5aweivfA,7495
50
61
  codeshift/migrator/transforms/flask_transformer.py,sha256=VcBlXDlhwgsk_1S2fyi3QsRSOUeE_DohMPjXbp4DY_A,20031
51
62
  codeshift/migrator/transforms/httpx_transformer.py,sha256=fxqmnJhWdUHuY0a9uXQZ2MFdF9kdxjm6c3UDgeMiFP0,16952
52
- codeshift/migrator/transforms/marshmallow_transformer.py,sha256=tLXX7Vko8OJQ9E1oSDooqGU-bjIgaC7-ZsK_aixPhrk,18339
63
+ codeshift/migrator/transforms/marshmallow_transformer.py,sha256=1XLd_KRU3QDs4HAs_L-w0UEqDOTChYhbmf1-3OJfVF4,20827
53
64
  codeshift/migrator/transforms/numpy_transformer.py,sha256=y4lJcAiT3UK8ZtZptCoSkli0UGNzhAyfUIf_I1F7WSE,15165
54
65
  codeshift/migrator/transforms/pandas_transformer.py,sha256=93WoeY9FOLChAWKr9xTbkQCpOS1kwajzNfWqfrNE-bY,8905
55
- codeshift/migrator/transforms/pydantic_v1_to_v2.py,sha256=J89WJF_gOeg0FUB0G2X5OCUt31zbCrGmhsBojqDgqX0,26879
66
+ codeshift/migrator/transforms/pydantic_v1_to_v2.py,sha256=Zo-KiQwyShowD2m0X1kiohtvbxpafRIsXxdFmvCm6Pk,35260
56
67
  codeshift/migrator/transforms/pytest_transformer.py,sha256=zcNoY0HWONvT2jpAoP01Nk-N-drwVLttTHbOZgb6HlM,13724
57
68
  codeshift/migrator/transforms/requests_transformer.py,sha256=r2hqawzdvsjblUHPoYU1tQ-miVqi1EEVsBPLXpzZz_s,12537
58
69
  codeshift/migrator/transforms/sqlalchemy_transformer.py,sha256=rgoIk4_iDCuydZhy9svdiNlZPnmI0sQuJB5F3f7LH3Y,34286
59
70
  codeshift/scanner/__init__.py,sha256=GFx9yMPZVuxBC8mGOPZoINsCsJgHV4TSjiV4KSF3fPU,300
60
- codeshift/scanner/code_scanner.py,sha256=YGuHVI1FN0h8cGSARFlF5duFd8WBCJUSVMcqCbsjqEQ,12859
71
+ codeshift/scanner/code_scanner.py,sha256=YBbmUrFn8Qyep4A52iqLcgySMvYQr49GsnmV8kpdxEw,13935
61
72
  codeshift/scanner/dependency_parser.py,sha256=Vd-wbgcG2trgLN7wntbNrGwuXvamn3u7B5SGvORdPiY,15372
62
73
  codeshift/utils/__init__.py,sha256=8G28m1UBDdEqF_G8GN6qRFWhpjDhiXJmFd9gSgIvkQc,148
63
- codeshift/utils/api_client.py,sha256=W6Us-eSqk3jl9l29lnEVa0QfuddZiFwA3D6FPmShRQw,8010
74
+ codeshift/utils/api_client.py,sha256=PupRgfFrfXFfAG44s7p32XhtvYUxwmKucPhFKt5-kIE,12641
64
75
  codeshift/utils/cache.py,sha256=9vPjU54S48iKy4CDvcjgz0u9eeIx7aUAHCdk3coRF6w,8793
65
76
  codeshift/utils/config.py,sha256=8x-rEh4q99K0HvT4ZQHDQAeUT8Tc_HATkZOomBGVyIA,2454
66
- codeshift/utils/llm_client.py,sha256=WkT3KftJi7rsj8MXH4MVJvznugicb2XpkKqnqRET1eo,6369
77
+ codeshift/utils/credential_store.py,sha256=Yq5UDrYSwEYN5sXsNNoFxX3BG3V2qVMNrY4bNAJHapM,13366
78
+ codeshift/utils/llm_client.py,sha256=NNVBJIl0dbxU9PMOJuSdDCTvRZFNetgJIkmjVSjEM0c,10115
67
79
  codeshift/validator/__init__.py,sha256=WRQSfJ7eLJdjR2_f_dXSaBtfawkvu1Dlu20Gh76D12c,280
68
80
  codeshift/validator/syntax_checker.py,sha256=FJeLIqhNhV7_Xj2RskHScJZks6A9fybaqv5Z1-MGDfo,5343
69
81
  codeshift/validator/test_runner.py,sha256=VX0OqkuI3AJxOUzRW2_BEjdDsMw1N4a0od-pPbSF6O8,6760
70
- codeshift-0.4.0.dist-info/licenses/LICENSE,sha256=mHKnse9JK19WRK76lYEwKB9nJWyzMzRpG4gkUtbTyac,1066
71
- codeshift-0.4.0.dist-info/METADATA,sha256=UDI4FyXLTM0c8C03CCOZQ4SSC3_F_kyrNpkrE8ePrac,16746
72
- codeshift-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
73
- codeshift-0.4.0.dist-info/entry_points.txt,sha256=AlJ8V7a2pNyu-9UiRKUWiTMIJtaYAUnlg53Y-wFHiK0,53
74
- codeshift-0.4.0.dist-info/top_level.txt,sha256=Ct42mtGs5foZ4MyYSksd5rXP0qFhWSZz8Y8mON0EEds,10
75
- codeshift-0.4.0.dist-info/RECORD,,
82
+ codeshift-0.7.0.dist-info/licenses/LICENSE,sha256=mHKnse9JK19WRK76lYEwKB9nJWyzMzRpG4gkUtbTyac,1066
83
+ codeshift-0.7.0.dist-info/METADATA,sha256=CYqRP2CSoxpilkxYx8yxz3ceoofvLFsOW29YtSvTNkw,16841
84
+ codeshift-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
85
+ codeshift-0.7.0.dist-info/entry_points.txt,sha256=AlJ8V7a2pNyu-9UiRKUWiTMIJtaYAUnlg53Y-wFHiK0,53
86
+ codeshift-0.7.0.dist-info/top_level.txt,sha256=Ct42mtGs5foZ4MyYSksd5rXP0qFhWSZz8Y8mON0EEds,10
87
+ codeshift-0.7.0.dist-info/RECORD,,