codeshift 0.4.0__py3-none-any.whl → 0.5.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.
@@ -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.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"
@@ -7,33 +7,33 @@ codeshift/cli/package_manager.py,sha256=K7spYHSQ6VoHlrzM3L9B2JoxBTe_9gLd57P6ME0P
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
12
  codeshift/cli/commands/scan.py,sha256=Eia3xW6sVZSiKtxd7JXyjIcOUsxduLGOuFjBhhp1ut0,11373
13
- codeshift/cli/commands/upgrade.py,sha256=JjPKbjMNDzBDZuz39JYas48Ch4vgrrtyc6AEoPq6Q6Q,15853
13
+ codeshift/cli/commands/upgrade.py,sha256=15SPmu09oi7Gu7KLvyDLbcF-YndkEoa0sPhlMZ-zsuo,15853
14
14
  codeshift/cli/commands/upgrade_all.py,sha256=FimS7SYJeYkQvhrWACLQG4QiyyynCgi9SapUb8Ax0Ik,18964
15
15
  codeshift/knowledge/__init__.py,sha256=_YwrLgjvsJQuYajfnIhUQqFeircF0MfkI9zJBPZTupc,1221
16
16
  codeshift/knowledge/cache.py,sha256=aEx9aNDfrzCYMzlPRBzBOYiFIAGDcZJfQvcXroa5vsA,4837
17
- codeshift/knowledge/generator.py,sha256=WVCF0ULYmVQRHBiK6BTyS6FuArSCj-UDWzPJLujVbxU,7304
17
+ codeshift/knowledge/generator.py,sha256=t30Rf8ByFxjz3wUk8Dq5fWGcL2MLS_rP4Xik4OspAUs,7386
18
18
  codeshift/knowledge/models.py,sha256=tvM6dnZJ00nJttIDMYxKlc8fYadgCdP2bqMVTA7o3HY,5157
19
19
  codeshift/knowledge/parser.py,sha256=uWm8IjOYoV06Sx5_odyrLB93XL1GNRHdzuNVSFM-fMA,8493
20
20
  codeshift/knowledge/sources.py,sha256=4GA4z4gHADCAfeBTF3dAO2S6H4s9leUKoxKPK2Vcopk,12280
21
21
  codeshift/knowledge_base/__init__.py,sha256=-xiDpdKrizX-5FeA8NjnHxASkkv44BwiMORTYF29Vj4,368
22
22
  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
23
+ codeshift/knowledge_base/models.py,sha256=YQ-Voe3y6O4Pge88YBsqM0Q64797-_7_lkyvmXArTeU,3836
24
+ codeshift/knowledge_base/libraries/aiohttp.yaml,sha256=LB-uyxpgghc6n4I7hKctvIvKsW-bbXo8HQQORCkDBFs,6472
25
25
  codeshift/knowledge_base/libraries/attrs.yaml,sha256=iih_l9GaETP-FTy0ktqgOHlcUIiXaIT7TInfquV8XHo,5776
26
26
  codeshift/knowledge_base/libraries/celery.yaml,sha256=jE0gvUv5VpzgIp8Pmgh_pL64SJoXpMZojTuxtczMgQg,8455
27
27
  codeshift/knowledge_base/libraries/click.yaml,sha256=JoGFDVzV_49ZWp0It0VTPjrfQOXBoDJXlEzuJxwUSFQ,6689
28
28
  codeshift/knowledge_base/libraries/django.yaml,sha256=F5FGfiZBOBAiHcTrUhNfjt4DUoa8MaNZCPCyFgSeFu8,12224
29
29
  codeshift/knowledge_base/libraries/fastapi.yaml,sha256=i6dXGbEP1bn2k06XgqzgvqE2Wle_NZRhUBTk06KhW3Y,6419
30
30
  codeshift/knowledge_base/libraries/flask.yaml,sha256=HETNUj5nrsCMH8sFwN1zPmUXGfcGWo5KhCDY4-hilR0,9401
31
- codeshift/knowledge_base/libraries/httpx.yaml,sha256=Pgr13Vs3O8jKNu0Ttga9cfaQpYwPkjNHzvCbAvu-itg,6516
31
+ codeshift/knowledge_base/libraries/httpx.yaml,sha256=goeaxr40vYglOIjHtm1M8zmzw8SafJhX77KA6kVAKRI,6520
32
32
  codeshift/knowledge_base/libraries/marshmallow.yaml,sha256=1K3VrNGsjka7bfHMOwUrlvBv-j2tgLOg7MDGflWDDZM,8307
33
33
  codeshift/knowledge_base/libraries/numpy.yaml,sha256=kpV1BNXTmLE9Lbki6caLHKpRzLYpPDkc-e3NtELtH1Y,13020
34
34
  codeshift/knowledge_base/libraries/pandas.yaml,sha256=cgu-dAaXIVyZivRr7swh3Df-GEpVfcxh5-J-6ow_pLc,10366
35
35
  codeshift/knowledge_base/libraries/pydantic.yaml,sha256=nTleM2JaabzrcJoMPrIYhPbV93Gl3GZ1uQnQSeHBw_w,8150
36
- codeshift/knowledge_base/libraries/pytest.yaml,sha256=I9_hz3_nqFxZVFoRJL2x7AP30bQxXHM1T6aU0PyEvQs,6601
36
+ codeshift/knowledge_base/libraries/pytest.yaml,sha256=6mSeeETC4yF4s1J22UIhhhplvYUOIGkDWo5ZSuq4nCw,6602
37
37
  codeshift/knowledge_base/libraries/requests.yaml,sha256=8Msjlt8v4mCEmTIdyvlpgtWsCXarpPqXNMPFlFd_Ojc,9035
38
38
  codeshift/knowledge_base/libraries/sqlalchemy.yaml,sha256=qEk1Nc2ovgCiBLPLsBrcsWM_KZaJn8MlHotDyZkzX9w,9717
39
39
  codeshift/migrator/__init__.py,sha256=V5ATKxw4hV6Ec3g78IeHkP92-VM4l6OzH0gi-lnU09w,467
@@ -49,27 +49,28 @@ codeshift/migrator/transforms/django_transformer.py,sha256=21gHStxJNHRj8x_pVfRB8
49
49
  codeshift/migrator/transforms/fastapi_transformer.py,sha256=axFZhCdMo3gAtHPxboSDhx56RasMjREcfaI5aweivfA,7495
50
50
  codeshift/migrator/transforms/flask_transformer.py,sha256=VcBlXDlhwgsk_1S2fyi3QsRSOUeE_DohMPjXbp4DY_A,20031
51
51
  codeshift/migrator/transforms/httpx_transformer.py,sha256=fxqmnJhWdUHuY0a9uXQZ2MFdF9kdxjm6c3UDgeMiFP0,16952
52
- codeshift/migrator/transforms/marshmallow_transformer.py,sha256=tLXX7Vko8OJQ9E1oSDooqGU-bjIgaC7-ZsK_aixPhrk,18339
52
+ codeshift/migrator/transforms/marshmallow_transformer.py,sha256=1XLd_KRU3QDs4HAs_L-w0UEqDOTChYhbmf1-3OJfVF4,20827
53
53
  codeshift/migrator/transforms/numpy_transformer.py,sha256=y4lJcAiT3UK8ZtZptCoSkli0UGNzhAyfUIf_I1F7WSE,15165
54
54
  codeshift/migrator/transforms/pandas_transformer.py,sha256=93WoeY9FOLChAWKr9xTbkQCpOS1kwajzNfWqfrNE-bY,8905
55
- codeshift/migrator/transforms/pydantic_v1_to_v2.py,sha256=J89WJF_gOeg0FUB0G2X5OCUt31zbCrGmhsBojqDgqX0,26879
55
+ codeshift/migrator/transforms/pydantic_v1_to_v2.py,sha256=Zo-KiQwyShowD2m0X1kiohtvbxpafRIsXxdFmvCm6Pk,35260
56
56
  codeshift/migrator/transforms/pytest_transformer.py,sha256=zcNoY0HWONvT2jpAoP01Nk-N-drwVLttTHbOZgb6HlM,13724
57
57
  codeshift/migrator/transforms/requests_transformer.py,sha256=r2hqawzdvsjblUHPoYU1tQ-miVqi1EEVsBPLXpzZz_s,12537
58
58
  codeshift/migrator/transforms/sqlalchemy_transformer.py,sha256=rgoIk4_iDCuydZhy9svdiNlZPnmI0sQuJB5F3f7LH3Y,34286
59
59
  codeshift/scanner/__init__.py,sha256=GFx9yMPZVuxBC8mGOPZoINsCsJgHV4TSjiV4KSF3fPU,300
60
- codeshift/scanner/code_scanner.py,sha256=YGuHVI1FN0h8cGSARFlF5duFd8WBCJUSVMcqCbsjqEQ,12859
60
+ codeshift/scanner/code_scanner.py,sha256=YBbmUrFn8Qyep4A52iqLcgySMvYQr49GsnmV8kpdxEw,13935
61
61
  codeshift/scanner/dependency_parser.py,sha256=Vd-wbgcG2trgLN7wntbNrGwuXvamn3u7B5SGvORdPiY,15372
62
62
  codeshift/utils/__init__.py,sha256=8G28m1UBDdEqF_G8GN6qRFWhpjDhiXJmFd9gSgIvkQc,148
63
- codeshift/utils/api_client.py,sha256=W6Us-eSqk3jl9l29lnEVa0QfuddZiFwA3D6FPmShRQw,8010
63
+ codeshift/utils/api_client.py,sha256=PupRgfFrfXFfAG44s7p32XhtvYUxwmKucPhFKt5-kIE,12641
64
64
  codeshift/utils/cache.py,sha256=9vPjU54S48iKy4CDvcjgz0u9eeIx7aUAHCdk3coRF6w,8793
65
65
  codeshift/utils/config.py,sha256=8x-rEh4q99K0HvT4ZQHDQAeUT8Tc_HATkZOomBGVyIA,2454
66
- codeshift/utils/llm_client.py,sha256=WkT3KftJi7rsj8MXH4MVJvznugicb2XpkKqnqRET1eo,6369
66
+ codeshift/utils/credential_store.py,sha256=Yq5UDrYSwEYN5sXsNNoFxX3BG3V2qVMNrY4bNAJHapM,13366
67
+ codeshift/utils/llm_client.py,sha256=NNVBJIl0dbxU9PMOJuSdDCTvRZFNetgJIkmjVSjEM0c,10115
67
68
  codeshift/validator/__init__.py,sha256=WRQSfJ7eLJdjR2_f_dXSaBtfawkvu1Dlu20Gh76D12c,280
68
69
  codeshift/validator/syntax_checker.py,sha256=FJeLIqhNhV7_Xj2RskHScJZks6A9fybaqv5Z1-MGDfo,5343
69
70
  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,,
71
+ codeshift-0.5.0.dist-info/licenses/LICENSE,sha256=mHKnse9JK19WRK76lYEwKB9nJWyzMzRpG4gkUtbTyac,1066
72
+ codeshift-0.5.0.dist-info/METADATA,sha256=xzJZhOOKl7VZMhw-2qQBXfVhSAVmGJIcbpWoZFt_xOw,16841
73
+ codeshift-0.5.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
74
+ codeshift-0.5.0.dist-info/entry_points.txt,sha256=AlJ8V7a2pNyu-9UiRKUWiTMIJtaYAUnlg53Y-wFHiK0,53
75
+ codeshift-0.5.0.dist-info/top_level.txt,sha256=Ct42mtGs5foZ4MyYSksd5rXP0qFhWSZz8Y8mON0EEds,10
76
+ codeshift-0.5.0.dist-info/RECORD,,