codeshift 0.3.7__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.
Files changed (44) hide show
  1. codeshift/__init__.py +2 -2
  2. codeshift/cli/__init__.py +1 -1
  3. codeshift/cli/commands/__init__.py +1 -1
  4. codeshift/cli/commands/auth.py +46 -30
  5. codeshift/cli/commands/scan.py +2 -5
  6. codeshift/cli/commands/upgrade.py +69 -61
  7. codeshift/cli/commands/upgrade_all.py +1 -1
  8. codeshift/cli/main.py +2 -2
  9. codeshift/knowledge/generator.py +6 -0
  10. codeshift/knowledge_base/libraries/aiohttp.yaml +3 -3
  11. codeshift/knowledge_base/libraries/httpx.yaml +4 -4
  12. codeshift/knowledge_base/libraries/pytest.yaml +1 -1
  13. codeshift/knowledge_base/models.py +1 -0
  14. codeshift/migrator/llm_migrator.py +8 -12
  15. codeshift/migrator/transforms/marshmallow_transformer.py +50 -0
  16. codeshift/migrator/transforms/pydantic_v1_to_v2.py +191 -22
  17. codeshift/scanner/code_scanner.py +22 -2
  18. codeshift/utils/__init__.py +1 -1
  19. codeshift/utils/api_client.py +155 -15
  20. codeshift/utils/cache.py +1 -1
  21. codeshift/utils/credential_store.py +393 -0
  22. codeshift/utils/llm_client.py +111 -9
  23. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/METADATA +4 -16
  24. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/RECORD +28 -43
  25. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/licenses/LICENSE +1 -1
  26. codeshift/api/__init__.py +0 -1
  27. codeshift/api/auth.py +0 -182
  28. codeshift/api/config.py +0 -73
  29. codeshift/api/database.py +0 -215
  30. codeshift/api/main.py +0 -103
  31. codeshift/api/models/__init__.py +0 -55
  32. codeshift/api/models/auth.py +0 -108
  33. codeshift/api/models/billing.py +0 -92
  34. codeshift/api/models/migrate.py +0 -42
  35. codeshift/api/models/usage.py +0 -116
  36. codeshift/api/routers/__init__.py +0 -5
  37. codeshift/api/routers/auth.py +0 -440
  38. codeshift/api/routers/billing.py +0 -395
  39. codeshift/api/routers/migrate.py +0 -304
  40. codeshift/api/routers/usage.py +0 -291
  41. codeshift/api/routers/webhooks.py +0 -289
  42. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/WHEEL +0 -0
  43. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/entry_points.txt +0 -0
  44. {codeshift-0.3.7.dist-info → codeshift-0.5.0.dist-info}/top_level.txt +0 -0
codeshift/api/main.py DELETED
@@ -1,103 +0,0 @@
1
- """FastAPI application for Codeshift billing API."""
2
-
3
- from collections.abc import AsyncGenerator
4
- from contextlib import asynccontextmanager
5
-
6
- from fastapi import FastAPI, Request
7
- from fastapi.middleware.cors import CORSMiddleware
8
- from fastapi.responses import JSONResponse
9
-
10
- from codeshift import __version__
11
- from codeshift.api.config import get_settings
12
- from codeshift.api.routers import auth, billing, migrate, usage, webhooks
13
-
14
-
15
- @asynccontextmanager
16
- async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
17
- """Application lifespan handler."""
18
- # Startup
19
- settings = get_settings()
20
- print(f"Codeshift API starting (environment: {settings.environment})")
21
- yield
22
- # Shutdown
23
- print("Codeshift API shutting down")
24
-
25
-
26
- app = FastAPI(
27
- title="Codeshift API",
28
- description="Billing and authentication API for Codeshift CLI",
29
- version=__version__,
30
- lifespan=lifespan,
31
- docs_url="/docs" if not get_settings().is_production else None,
32
- redoc_url="/redoc" if not get_settings().is_production else None,
33
- )
34
-
35
- # CORS configuration
36
- app.add_middleware(
37
- CORSMiddleware,
38
- allow_origins=[
39
- "https://codeshift.dev",
40
- "https://www.codeshift.dev",
41
- "http://localhost:3000", # Local development
42
- ],
43
- allow_credentials=True,
44
- allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
45
- allow_headers=["*"],
46
- )
47
-
48
-
49
- # Include routers
50
- app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
51
- app.include_router(billing.router, prefix="/billing", tags=["Billing"])
52
- app.include_router(migrate.router, tags=["Migration"])
53
- app.include_router(usage.router, prefix="/usage", tags=["Usage"])
54
- app.include_router(webhooks.router, prefix="/webhooks", tags=["Webhooks"])
55
-
56
-
57
- @app.get("/")
58
- async def root() -> dict:
59
- """API root endpoint."""
60
- return {
61
- "name": "Codeshift API",
62
- "version": __version__,
63
- "status": "healthy",
64
- }
65
-
66
-
67
- @app.get("/health")
68
- async def health_check() -> dict:
69
- """Health check endpoint."""
70
- return {"status": "healthy", "version": __version__}
71
-
72
-
73
- @app.exception_handler(Exception)
74
- async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse:
75
- """Global exception handler."""
76
- settings = get_settings()
77
-
78
- # Log the error
79
- print(f"Unhandled exception: {exc}")
80
-
81
- # Return appropriate response based on environment
82
- if settings.is_production:
83
- return JSONResponse(
84
- status_code=500,
85
- content={"detail": "Internal server error"},
86
- )
87
- else:
88
- return JSONResponse(
89
- status_code=500,
90
- content={"detail": str(exc), "type": type(exc).__name__},
91
- )
92
-
93
-
94
- # For running with uvicorn directly
95
- if __name__ == "__main__":
96
- import uvicorn
97
-
98
- uvicorn.run(
99
- "codeshift.api.main:app",
100
- host="0.0.0.0",
101
- port=8000,
102
- reload=True,
103
- )
@@ -1,55 +0,0 @@
1
- """Pydantic models for the PyResolve API."""
2
-
3
- from codeshift.api.models.auth import (
4
- APIKey,
5
- APIKeyCreate,
6
- APIKeyResponse,
7
- TokenResponse,
8
- UserInfo,
9
- )
10
- from codeshift.api.models.billing import (
11
- CheckoutSessionRequest,
12
- CheckoutSessionResponse,
13
- PortalSessionResponse,
14
- SubscriptionInfo,
15
- TierInfo,
16
- )
17
- from codeshift.api.models.migrate import (
18
- ExplainChangeRequest,
19
- ExplainChangeResponse,
20
- MigrateCodeRequest,
21
- MigrateCodeResponse,
22
- )
23
- from codeshift.api.models.usage import (
24
- QuotaInfo,
25
- UsageEvent,
26
- UsageEventCreate,
27
- UsageResponse,
28
- UsageSummary,
29
- )
30
-
31
- __all__ = [
32
- # Auth models
33
- "APIKey",
34
- "APIKeyCreate",
35
- "APIKeyResponse",
36
- "TokenResponse",
37
- "UserInfo",
38
- # Billing models
39
- "CheckoutSessionRequest",
40
- "CheckoutSessionResponse",
41
- "PortalSessionResponse",
42
- "SubscriptionInfo",
43
- "TierInfo",
44
- # Migrate models
45
- "ExplainChangeRequest",
46
- "ExplainChangeResponse",
47
- "MigrateCodeRequest",
48
- "MigrateCodeResponse",
49
- # Usage models
50
- "QuotaInfo",
51
- "UsageEvent",
52
- "UsageEventCreate",
53
- "UsageResponse",
54
- "UsageSummary",
55
- ]
@@ -1,108 +0,0 @@
1
- """Authentication models for the PyResolve API."""
2
-
3
- from datetime import datetime
4
-
5
- from pydantic import BaseModel, EmailStr, Field
6
-
7
-
8
- class UserInfo(BaseModel):
9
- """User profile information."""
10
-
11
- id: str
12
- email: EmailStr
13
- full_name: str | None = None
14
- tier: str = "free"
15
- stripe_customer_id: str | None = None
16
- created_at: datetime
17
-
18
- class Config:
19
- from_attributes = True
20
-
21
-
22
- class APIKeyCreate(BaseModel):
23
- """Request to create a new API key."""
24
-
25
- name: str = Field(default="CLI Key", min_length=1, max_length=100)
26
- scopes: list[str] = Field(default=["read", "write"])
27
-
28
-
29
- class APIKey(BaseModel):
30
- """API key information (without the secret)."""
31
-
32
- id: str
33
- name: str
34
- key_prefix: str
35
- scopes: list[str]
36
- revoked: bool = False
37
- last_used_at: datetime | None = None
38
- expires_at: datetime | None = None
39
- created_at: datetime
40
-
41
- class Config:
42
- from_attributes = True
43
-
44
-
45
- class APIKeyResponse(BaseModel):
46
- """Response when creating a new API key (includes full key once)."""
47
-
48
- id: str
49
- name: str
50
- key: str # Full API key - only shown once
51
- key_prefix: str
52
- scopes: list[str]
53
- created_at: datetime
54
-
55
-
56
- class TokenResponse(BaseModel):
57
- """Response for authentication token."""
58
-
59
- access_token: str
60
- token_type: str = "bearer"
61
- expires_in: int
62
- refresh_token: str | None = None
63
-
64
-
65
- class LoginRequest(BaseModel):
66
- """Request for CLI login."""
67
-
68
- email: EmailStr
69
- password: str = Field(min_length=6)
70
-
71
-
72
- class LoginResponse(BaseModel):
73
- """Response for CLI login."""
74
-
75
- api_key: str
76
- user: UserInfo
77
- message: str = "Login successful"
78
-
79
-
80
- class DeviceCodeRequest(BaseModel):
81
- """Request to initiate device code flow."""
82
-
83
- client_id: str = "codeshift-cli"
84
-
85
-
86
- class DeviceCodeResponse(BaseModel):
87
- """Response with device code for authentication."""
88
-
89
- device_code: str
90
- user_code: str
91
- verification_uri: str
92
- expires_in: int = 900 # 15 minutes
93
- interval: int = 5 # Polling interval in seconds
94
-
95
-
96
- class DeviceTokenRequest(BaseModel):
97
- """Request to exchange device code for token."""
98
-
99
- device_code: str
100
- client_id: str = "codeshift-cli"
101
-
102
-
103
- class RegisterRequest(BaseModel):
104
- """Request for new user registration."""
105
-
106
- email: EmailStr
107
- password: str = Field(min_length=8, description="Password must be at least 8 characters")
108
- full_name: str | None = Field(default=None, max_length=100)
@@ -1,92 +0,0 @@
1
- """Billing models for the PyResolve API."""
2
-
3
- from datetime import datetime
4
-
5
- from pydantic import BaseModel, Field
6
-
7
-
8
- class TierInfo(BaseModel):
9
- """Information about a pricing tier."""
10
-
11
- name: str
12
- display_name: str
13
- price_monthly: int # In cents
14
- files_per_month: int
15
- llm_calls_per_month: int
16
- features: list[str]
17
-
18
-
19
- class SubscriptionInfo(BaseModel):
20
- """Current subscription information."""
21
-
22
- tier: str
23
- status: str # active, canceled, past_due, etc.
24
- stripe_subscription_id: str | None = None
25
- current_period_start: datetime | None = None
26
- current_period_end: datetime | None = None
27
- cancel_at_period_end: bool = False
28
-
29
-
30
- class CheckoutSessionRequest(BaseModel):
31
- """Request to create a Stripe checkout session."""
32
-
33
- tier: str = Field(..., pattern="^(pro|unlimited)$")
34
- success_url: str | None = None
35
- cancel_url: str | None = None
36
-
37
-
38
- class CheckoutSessionResponse(BaseModel):
39
- """Response with Stripe checkout session."""
40
-
41
- checkout_url: str
42
- session_id: str
43
-
44
-
45
- class PortalSessionResponse(BaseModel):
46
- """Response with Stripe billing portal URL."""
47
-
48
- portal_url: str
49
-
50
-
51
- class PriceInfo(BaseModel):
52
- """Stripe price information."""
53
-
54
- id: str
55
- product_id: str
56
- unit_amount: int
57
- currency: str
58
- recurring_interval: str
59
-
60
-
61
- class InvoiceInfo(BaseModel):
62
- """Invoice information."""
63
-
64
- id: str
65
- status: str
66
- amount_due: int
67
- amount_paid: int
68
- currency: str
69
- created: datetime
70
- due_date: datetime | None = None
71
- hosted_invoice_url: str | None = None
72
- pdf_url: str | None = None
73
-
74
-
75
- class PaymentMethodInfo(BaseModel):
76
- """Payment method information."""
77
-
78
- id: str
79
- type: str # card, bank_account, etc.
80
- card_brand: str | None = None
81
- card_last4: str | None = None
82
- card_exp_month: int | None = None
83
- card_exp_year: int | None = None
84
-
85
-
86
- class BillingOverview(BaseModel):
87
- """Complete billing overview for a user."""
88
-
89
- subscription: SubscriptionInfo
90
- tier_info: TierInfo
91
- payment_method: PaymentMethodInfo | None = None
92
- upcoming_invoice: InvoiceInfo | None = None
@@ -1,42 +0,0 @@
1
- """Migration models for the PyResolve API."""
2
-
3
- from typing import Any
4
-
5
- from pydantic import BaseModel, Field
6
-
7
-
8
- class MigrateCodeRequest(BaseModel):
9
- """Request to migrate code using LLM."""
10
-
11
- code: str = Field(..., description="Source code to migrate")
12
- library: str = Field(..., description="Library being upgraded (e.g., 'pydantic')")
13
- from_version: str = Field(..., description="Current version (e.g., '1.10.0')")
14
- to_version: str = Field(..., description="Target version (e.g., '2.5.0')")
15
- context: str | None = Field(None, description="Optional context about the migration")
16
-
17
-
18
- class MigrateCodeResponse(BaseModel):
19
- """Response from LLM migration."""
20
-
21
- success: bool
22
- migrated_code: str
23
- original_code: str
24
- error: str | None = None
25
- usage: dict[str, Any] = Field(default_factory=dict)
26
- cached: bool = False
27
-
28
-
29
- class ExplainChangeRequest(BaseModel):
30
- """Request to explain a migration change."""
31
-
32
- original_code: str = Field(..., description="Original code before migration")
33
- transformed_code: str = Field(..., description="Transformed code after migration")
34
- library: str = Field(..., description="Library being upgraded")
35
-
36
-
37
- class ExplainChangeResponse(BaseModel):
38
- """Response with explanation of changes."""
39
-
40
- success: bool
41
- explanation: str | None = None
42
- error: str | None = None
@@ -1,116 +0,0 @@
1
- """Usage tracking models for the PyResolve API."""
2
-
3
- from datetime import datetime
4
- from typing import Any
5
-
6
- from pydantic import BaseModel, Field
7
-
8
-
9
- class UsageEventCreate(BaseModel):
10
- """Request to record a usage event."""
11
-
12
- event_type: str = Field(..., pattern="^(file_migrated|llm_call|scan|apply)$")
13
- library: str | None = None
14
- quantity: int = Field(default=1, ge=1)
15
- metadata: dict[str, Any] = Field(default_factory=dict)
16
-
17
-
18
- class UsageEvent(BaseModel):
19
- """A recorded usage event."""
20
-
21
- id: str
22
- user_id: str
23
- event_type: str
24
- library: str | None = None
25
- quantity: int
26
- metadata: dict[str, Any]
27
- billing_period: str
28
- created_at: datetime
29
-
30
- class Config:
31
- from_attributes = True
32
-
33
-
34
- class UsageSummary(BaseModel):
35
- """Summary of usage for a billing period."""
36
-
37
- billing_period: str
38
- files_migrated: int = 0
39
- llm_calls: int = 0
40
- scans: int = 0
41
- applies: int = 0
42
- total_events: int = 0
43
-
44
-
45
- class QuotaInfo(BaseModel):
46
- """Current quota information for a user."""
47
-
48
- tier: str
49
- billing_period: str
50
-
51
- # Current usage
52
- files_migrated: int = 0
53
- llm_calls: int = 0
54
-
55
- # Limits
56
- files_limit: int
57
- llm_calls_limit: int
58
-
59
- # Calculated fields
60
- files_remaining: int
61
- llm_calls_remaining: int
62
- files_percentage: float
63
- llm_calls_percentage: float
64
-
65
- @classmethod
66
- def from_usage(
67
- cls,
68
- tier: str,
69
- billing_period: str,
70
- files_migrated: int,
71
- llm_calls: int,
72
- files_limit: int,
73
- llm_calls_limit: int,
74
- ) -> "QuotaInfo":
75
- """Create QuotaInfo from usage data."""
76
- files_remaining = max(0, files_limit - files_migrated)
77
- llm_calls_remaining = max(0, llm_calls_limit - llm_calls)
78
-
79
- return cls(
80
- tier=tier,
81
- billing_period=billing_period,
82
- files_migrated=files_migrated,
83
- llm_calls=llm_calls,
84
- files_limit=files_limit,
85
- llm_calls_limit=llm_calls_limit,
86
- files_remaining=files_remaining,
87
- llm_calls_remaining=llm_calls_remaining,
88
- files_percentage=round(files_migrated / files_limit * 100, 1) if files_limit > 0 else 0,
89
- llm_calls_percentage=(
90
- round(llm_calls / llm_calls_limit * 100, 1) if llm_calls_limit > 0 else 0
91
- ),
92
- )
93
-
94
-
95
- class UsageResponse(BaseModel):
96
- """Response for usage queries."""
97
-
98
- quota: QuotaInfo
99
- recent_events: list[UsageEvent] = Field(default_factory=list)
100
-
101
-
102
- class QuotaCheckRequest(BaseModel):
103
- """Request to check if an operation is within quota."""
104
-
105
- event_type: str = Field(..., pattern="^(file_migrated|llm_call|scan|apply)$")
106
- quantity: int = Field(default=1, ge=1)
107
-
108
-
109
- class QuotaCheckResponse(BaseModel):
110
- """Response for quota check."""
111
-
112
- allowed: bool
113
- current_usage: int
114
- limit: int
115
- remaining: int
116
- message: str | None = None
@@ -1,5 +0,0 @@
1
- """API routers for PyResolve."""
2
-
3
- from codeshift.api.routers import auth, billing, migrate, usage, webhooks
4
-
5
- __all__ = ["auth", "billing", "migrate", "usage", "webhooks"]