start-vibing-stacks 1.7.4 โ†’ 1.8.0

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.
@@ -14,3 +14,4 @@ export declare function detectPhpFramework(projectDir: string): string | null;
14
14
  * Detect framework within a Node.js project
15
15
  */
16
16
  export declare function detectNodeFramework(projectDir: string): string | null;
17
+ export declare function detectPythonFramework(projectDir: string): string | null;
package/dist/detector.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Auto-detects project stack by scanning for known files.
5
5
  * Also detects .cursorrules, CLAUDE.md, and git presence.
6
6
  */
7
- import { existsSync, readdirSync } from 'fs';
7
+ import { existsSync, readdirSync, readFileSync } from 'fs';
8
8
  import { join } from 'path';
9
9
  const STACK_SIGNATURES = [
10
10
  // PHP
@@ -101,3 +101,20 @@ export function detectNodeFramework(projectDir) {
101
101
  return 'astro';
102
102
  return null;
103
103
  }
104
+ export function detectPythonFramework(projectDir) {
105
+ // Check manage.py (Django)
106
+ if (existsSync(join(projectDir, 'manage.py')))
107
+ return 'django';
108
+ // Check for FastAPI in requirements or pyproject
109
+ for (const reqFile of ['requirements.txt', 'pyproject.toml', 'Pipfile']) {
110
+ const filePath = join(projectDir, reqFile);
111
+ if (existsSync(filePath)) {
112
+ const content = readFileSync(filePath, 'utf8').toLowerCase();
113
+ if (content.includes('fastapi'))
114
+ return 'fastapi';
115
+ if (content.includes('flask'))
116
+ return 'flask';
117
+ }
118
+ }
119
+ return null;
120
+ }
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { join, basename } from 'path';
10
10
  import inquirer from 'inquirer';
11
11
  import chalk from 'chalk';
12
12
  import * as ui from './ui.js';
13
- import { detectProject, detectPhpFramework, detectNodeFramework } from './detector.js';
13
+ import { detectProject, detectPhpFramework, detectNodeFramework, detectPythonFramework } from './detector.js';
14
14
  import { autoInstall, installComposer, installClaudeCode } from './installer.js';
15
15
  import { loadStackConfig, setupProject } from './setup.js';
16
16
  import { selectMcpServers, installMcpServers } from './mcp.js';
@@ -56,7 +56,7 @@ if (FLAGS.help) {
56
56
  const AVAILABLE_STACKS = [
57
57
  { id: 'php', name: 'PHP 8.3+', icon: '๐Ÿ˜', available: true },
58
58
  { id: 'nodejs', name: 'Node.js / TypeScript', icon: '๐Ÿ“ฆ', available: true },
59
- { id: 'python', name: 'Python', icon: '๐Ÿ', available: false },
59
+ { id: 'python', name: 'Python 3.12+', icon: '๐Ÿ', available: true },
60
60
  { id: 'rust', name: 'Rust', icon: '๐Ÿฆ€', available: false },
61
61
  { id: 'go', name: 'Go', icon: '๐Ÿน', available: false },
62
62
  ];
@@ -158,7 +158,9 @@ async function main() {
158
158
  ? detectPhpFramework(projectDir)
159
159
  : stackId === 'nodejs'
160
160
  ? detectNodeFramework(projectDir)
161
- : null;
161
+ : stackId === 'python'
162
+ ? detectPythonFramework(projectDir)
163
+ : null;
162
164
  const { framework } = await inquirer.prompt([
163
165
  {
164
166
  type: 'list',
package/dist/ui.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Start Vibing Stacks โ€” Terminal UI
3
3
  */
4
4
  import chalk from 'chalk';
5
- const VERSION = '1.7.4';
5
+ const VERSION = '1.8.0';
6
6
  const gradient = (text) => {
7
7
  const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
8
8
  return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,98 @@
1
+ # Async Python Patterns โ€” Concurrency & Performance
2
+
3
+ **ALWAYS invoke when writing async/await code or concurrent operations.**
4
+
5
+ ## Core: asyncio.gather vs TaskGroup
6
+
7
+ ```python
8
+ import asyncio
9
+
10
+ # Gather โ€” run multiple coroutines concurrently
11
+ async def fetch_all():
12
+ results = await asyncio.gather(
13
+ fetch_users(),
14
+ fetch_products(),
15
+ fetch_orders(),
16
+ )
17
+ users, products, orders = results
18
+
19
+ # TaskGroup (Python 3.11+) โ€” structured concurrency with proper cancellation
20
+ async def fetch_all_safe():
21
+ async with asyncio.TaskGroup() as tg:
22
+ t1 = tg.create_task(fetch_users())
23
+ t2 = tg.create_task(fetch_products())
24
+ return t1.result(), t2.result()
25
+ # If any task fails, ALL are cancelled
26
+ ```
27
+
28
+ ## HTTP Client (httpx)
29
+
30
+ ```python
31
+ import httpx
32
+
33
+ # REUSE client (connection pooling)
34
+ async def fetch_data():
35
+ async with httpx.AsyncClient(timeout=10.0) as client:
36
+ response = await client.get("https://api.example.com/data")
37
+ response.raise_for_status()
38
+ return response.json()
39
+
40
+ # Concurrent requests
41
+ async def fetch_many(urls: list[str]) -> list[dict]:
42
+ async with httpx.AsyncClient() as client:
43
+ tasks = [client.get(url) for url in urls]
44
+ responses = await asyncio.gather(*tasks)
45
+ return [r.json() for r in responses]
46
+ ```
47
+
48
+ ## Semaphore (rate limiting)
49
+
50
+ ```python
51
+ # Limit concurrent operations
52
+ sem = asyncio.Semaphore(10) # Max 10 concurrent
53
+
54
+ async def fetch_with_limit(url: str):
55
+ async with sem:
56
+ async with httpx.AsyncClient() as client:
57
+ return await client.get(url)
58
+ ```
59
+
60
+ ## Queue Pattern (producer/consumer)
61
+
62
+ ```python
63
+ async def producer(queue: asyncio.Queue):
64
+ for item in data:
65
+ await queue.put(item)
66
+ await queue.put(None) # Sentinel
67
+
68
+ async def consumer(queue: asyncio.Queue):
69
+ while True:
70
+ item = await queue.get()
71
+ if item is None:
72
+ break
73
+ await process(item)
74
+ queue.task_done()
75
+
76
+ async def main():
77
+ queue = asyncio.Queue(maxsize=100) # Backpressure
78
+ await asyncio.gather(producer(queue), consumer(queue))
79
+ ```
80
+
81
+ ## Timeouts
82
+
83
+ ```python
84
+ # Always add timeouts to external calls
85
+ try:
86
+ result = await asyncio.wait_for(fetch_data(), timeout=5.0)
87
+ except asyncio.TimeoutError:
88
+ logger.warning("External API timed out")
89
+ result = default_value
90
+ ```
91
+
92
+ ## FORBIDDEN
93
+
94
+ 1. **`time.sleep()` in async code** โ€” use `await asyncio.sleep()`
95
+ 2. **Sync HTTP in async context** โ€” use `httpx.AsyncClient`, not `requests`
96
+ 3. **No timeout on external calls** โ€” always `asyncio.wait_for()` or `httpx.Timeout`
97
+ 4. **Creating new client per request** โ€” reuse with `async with`
98
+ 5. **Bare `asyncio.gather` without error handling** โ€” use `return_exceptions=True` or TaskGroup
@@ -0,0 +1,101 @@
1
+ # Django Patterns โ€” Full-Stack Python (Django 5+)
2
+
3
+ **ALWAYS invoke when writing Django models, views, or serializers.**
4
+
5
+ ## Model Design (Fat Models, Thin Views)
6
+
7
+ ```python
8
+ from django.db import models
9
+ from django.utils import timezone
10
+ import uuid
11
+
12
+ class TimeStampedModel(models.Model):
13
+ """Abstract base โ€” reuse in all models."""
14
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
15
+ created_at = models.DateTimeField(auto_now_add=True)
16
+ updated_at = models.DateTimeField(auto_now=True)
17
+
18
+ class Meta:
19
+ abstract = True
20
+
21
+ class User(TimeStampedModel):
22
+ email = models.EmailField(unique=True, db_index=True)
23
+ name = models.CharField(max_length=255)
24
+ is_active = models.BooleanField(default=True)
25
+
26
+ # Fat model: business logic HERE
27
+ def deactivate(self):
28
+ self.is_active = False
29
+ self.save(update_fields=['is_active', 'updated_at'])
30
+
31
+ # Custom manager
32
+ objects = UserManager()
33
+ ```
34
+
35
+ ## QuerySet Optimization
36
+
37
+ ```python
38
+ # ALWAYS use select_related for ForeignKey
39
+ users = User.objects.select_related('profile').filter(is_active=True)
40
+
41
+ # ALWAYS use prefetch_related for ManyToMany
42
+ posts = Post.objects.prefetch_related('tags', 'comments').all()
43
+
44
+ # Use .only() for specific fields
45
+ User.objects.only('id', 'email').filter(is_active=True)
46
+
47
+ # Avoid N+1 โ€” NEVER loop queries
48
+ # WRONG:
49
+ for post in Post.objects.all():
50
+ print(post.author.name) # N+1!
51
+
52
+ # CORRECT:
53
+ for post in Post.objects.select_related('author'):
54
+ print(post.author.name) # 1 query
55
+ ```
56
+
57
+ ## Django REST Framework
58
+
59
+ ```python
60
+ # serializers.py
61
+ class UserSerializer(serializers.ModelSerializer):
62
+ class Meta:
63
+ model = User
64
+ fields = ['id', 'email', 'name', 'created_at']
65
+ read_only_fields = ['id', 'created_at']
66
+
67
+ # views.py
68
+ class UserViewSet(viewsets.ModelViewSet):
69
+ queryset = User.objects.filter(is_active=True)
70
+ serializer_class = UserSerializer
71
+ permission_classes = [IsAuthenticated]
72
+ pagination_class = PageNumberPagination
73
+ ```
74
+
75
+ ## Async Views (Django 5.0+)
76
+
77
+ ```python
78
+ # Use async when calling external APIs or heavy I/O
79
+ async def fetch_external_data(request):
80
+ async with httpx.AsyncClient() as client:
81
+ response = await client.get('https://api.example.com/data')
82
+ return JsonResponse(response.json())
83
+ ```
84
+
85
+ ## Migrations Best Practices
86
+
87
+ ```bash
88
+ python manage.py makemigrations --name descriptive_name
89
+ python manage.py migrate
90
+
91
+ # Check for missing migrations in CI
92
+ python manage.py makemigrations --check --dry-run
93
+ ```
94
+
95
+ ## FORBIDDEN
96
+
97
+ 1. **Logic in views** โ€” fat models, thin views
98
+ 2. **N+1 queries** โ€” always `select_related`/`prefetch_related`
99
+ 3. **`objects.all()` without limit** โ€” paginate or `.only()`
100
+ 4. **Raw SQL without parameterization** โ€” use ORM or `params=[]`
101
+ 5. **Skipping migrations** โ€” always `makemigrations` after model changes
@@ -0,0 +1,129 @@
1
+ # FastAPI Patterns โ€” High-Performance Async APIs
2
+
3
+ **ALWAYS invoke when writing FastAPI routes, dependencies, or middleware.**
4
+
5
+ ## Route Pattern
6
+
7
+ ```python
8
+ from fastapi import APIRouter, Depends, HTTPException, status
9
+ from app.schemas.user import UserCreate, UserResponse
10
+ from app.services.user import UserService
11
+ from app.api.deps import get_current_user, get_db
12
+
13
+ router = APIRouter(prefix="/users", tags=["users"])
14
+
15
+ @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
16
+ async def create_user(data: UserCreate, db=Depends(get_db)):
17
+ service = UserService(db)
18
+ return await service.create(data)
19
+
20
+ @router.get("/me", response_model=UserResponse)
21
+ async def get_me(user=Depends(get_current_user)):
22
+ return user
23
+ ```
24
+
25
+ ## Dependency Injection
26
+
27
+ ```python
28
+ # app/api/deps.py
29
+ from fastapi import Depends, HTTPException, status
30
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
31
+
32
+ security = HTTPBearer()
33
+
34
+ async def get_db():
35
+ async with async_session() as session:
36
+ yield session # Auto-cleanup
37
+
38
+ async def get_current_user(
39
+ credentials: HTTPAuthorizationCredentials = Depends(security),
40
+ db=Depends(get_db),
41
+ ) -> User:
42
+ user = await verify_token(credentials.credentials, db)
43
+ if not user:
44
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
45
+ return user
46
+ ```
47
+
48
+ ## Pydantic Settings (env config)
49
+
50
+ ```python
51
+ from pydantic_settings import BaseSettings
52
+
53
+ class Settings(BaseSettings):
54
+ DATABASE_URL: str
55
+ SECRET_KEY: str
56
+ DEBUG: bool = False
57
+ ALLOWED_ORIGINS: list[str] = ["http://localhost:3000"]
58
+
59
+ class Config:
60
+ env_file = ".env"
61
+
62
+ settings = Settings()
63
+ ```
64
+
65
+ ## Middleware
66
+
67
+ ```python
68
+ from fastapi.middleware.cors import CORSMiddleware
69
+
70
+ app.add_middleware(
71
+ CORSMiddleware,
72
+ allow_origins=settings.ALLOWED_ORIGINS,
73
+ allow_credentials=True,
74
+ allow_methods=["*"],
75
+ allow_headers=["*"],
76
+ )
77
+
78
+ # Custom middleware
79
+ @app.middleware("http")
80
+ async def add_request_id(request, call_next):
81
+ request_id = str(uuid4())
82
+ request.state.request_id = request_id
83
+ response = await call_next(request)
84
+ response.headers["X-Request-ID"] = request_id
85
+ return response
86
+ ```
87
+
88
+ ## SQLAlchemy 2.0 Async
89
+
90
+ ```python
91
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
92
+
93
+ engine = create_async_engine(settings.DATABASE_URL, pool_size=20, max_overflow=10)
94
+ async_session = async_sessionmaker(engine, expire_on_commit=False)
95
+
96
+ # Repository pattern
97
+ class UserRepository:
98
+ def __init__(self, session):
99
+ self.session = session
100
+
101
+ async def get_by_id(self, id: UUID) -> User | None:
102
+ result = await self.session.execute(
103
+ select(User).where(User.id == id)
104
+ )
105
+ return result.scalar_one_or_none()
106
+ ```
107
+
108
+ ## Production Deployment
109
+
110
+ ```bash
111
+ # uvicorn with gunicorn (production)
112
+ gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
113
+
114
+ # Docker
115
+ FROM python:3.12-slim
116
+ WORKDIR /app
117
+ COPY requirements.txt .
118
+ RUN pip install --no-cache-dir -r requirements.txt
119
+ COPY . .
120
+ CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
121
+ ```
122
+
123
+ ## FORBIDDEN
124
+
125
+ 1. **`def` routes with async DB calls** โ€” use `async def`
126
+ 2. **Business logic in routes** โ€” use services layer
127
+ 3. **Hardcoded secrets** โ€” use Pydantic Settings + `.env`
128
+ 4. **No response_model** โ€” always specify for auto-docs
129
+ 5. **Catching bare `Exception`** โ€” catch specific exceptions
@@ -0,0 +1,103 @@
1
+ # Pydantic Validation โ€” Runtime Type Safety for Python
2
+
3
+ **ALWAYS use Pydantic for API schemas, config, and data boundaries.**
4
+
5
+ ## Multi-Model Pattern
6
+
7
+ ```python
8
+ from pydantic import BaseModel, Field, EmailStr
9
+ from datetime import datetime
10
+ from uuid import UUID
11
+
12
+ # Base โ€” shared fields
13
+ class UserBase(BaseModel):
14
+ name: str = Field(..., min_length=2, max_length=100)
15
+ email: EmailStr
16
+
17
+ # Create โ€” request body (required fields)
18
+ class UserCreate(UserBase):
19
+ password: str = Field(..., min_length=8)
20
+
21
+ # Update โ€” PATCH (all optional)
22
+ class UserUpdate(BaseModel):
23
+ name: str | None = Field(None, min_length=2)
24
+ email: EmailStr | None = None
25
+
26
+ # Response โ€” API output
27
+ class UserResponse(UserBase):
28
+ id: UUID
29
+ created_at: datetime
30
+ is_active: bool
31
+
32
+ model_config = {"from_attributes": True} # Works with ORM objects
33
+
34
+ # InDB โ€” database document (Cosmos, Mongo)
35
+ class UserInDB(UserResponse):
36
+ doc_type: str = "user"
37
+ hashed_password: str
38
+ ```
39
+
40
+ ## Validators
41
+
42
+ ```python
43
+ from pydantic import field_validator, model_validator
44
+
45
+ class OrderCreate(BaseModel):
46
+ quantity: int
47
+ price: float
48
+ discount: float = 0.0
49
+
50
+ @field_validator('quantity')
51
+ @classmethod
52
+ def quantity_positive(cls, v):
53
+ if v <= 0:
54
+ raise ValueError('Quantity must be positive')
55
+ return v
56
+
57
+ @model_validator(mode='after')
58
+ def discount_not_exceed_price(self):
59
+ if self.discount > self.price * self.quantity:
60
+ raise ValueError('Discount exceeds total')
61
+ return self
62
+ ```
63
+
64
+ ## Settings (env config)
65
+
66
+ ```python
67
+ from pydantic_settings import BaseSettings
68
+
69
+ class Settings(BaseSettings):
70
+ DATABASE_URL: str
71
+ SECRET_KEY: str
72
+ DEBUG: bool = False
73
+ REDIS_URL: str = "redis://localhost:6379"
74
+
75
+ model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
76
+
77
+ settings = Settings()
78
+ ```
79
+
80
+ ## camelCase API (Python snake_case โ†’ JSON camelCase)
81
+
82
+ ```python
83
+ from pydantic import ConfigDict
84
+
85
+ class ApiModel(BaseModel):
86
+ model_config = ConfigDict(
87
+ populate_by_name=True,
88
+ alias_generator=lambda s: ''.join(
89
+ w.capitalize() if i else w for i, w in enumerate(s.split('_'))
90
+ ),
91
+ )
92
+
93
+ class UserResponse(ApiModel):
94
+ first_name: str # JSON: "firstName"
95
+ created_at: datetime # JSON: "createdAt"
96
+ ```
97
+
98
+ ## FORBIDDEN
99
+
100
+ 1. **Raw dicts for API I/O** โ€” always Pydantic models
101
+ 2. **Skipping validation** โ€” `model_validate()` not `dict()`
102
+ 3. **Single model for everything** โ€” use Base/Create/Update/Response pattern
103
+ 4. **No `from_attributes`** โ€” needed for ORM integration
@@ -0,0 +1,108 @@
1
+ # Pytest Testing โ€” Python Testing Patterns
2
+
3
+ **ALWAYS invoke AFTER implementing any feature.**
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ tests/
9
+ โ”œโ”€โ”€ conftest.py # Shared fixtures
10
+ โ”œโ”€โ”€ unit/
11
+ โ”‚ โ”œโ”€โ”€ test_services.py
12
+ โ”‚ โ””โ”€โ”€ test_models.py
13
+ โ”œโ”€โ”€ integration/
14
+ โ”‚ โ”œโ”€โ”€ test_api.py
15
+ โ”‚ โ””โ”€โ”€ test_db.py
16
+ โ””โ”€โ”€ e2e/
17
+ โ””โ”€โ”€ test_flows.py
18
+ ```
19
+
20
+ ## Fixtures
21
+
22
+ ```python
23
+ import pytest
24
+ from httpx import AsyncClient, ASGITransport
25
+ from app.main import app
26
+ from app.core.config import settings
27
+
28
+ @pytest.fixture
29
+ async def client():
30
+ transport = ASGITransport(app=app)
31
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
32
+ yield ac
33
+
34
+ @pytest.fixture
35
+ async def db_session():
36
+ async with async_session() as session:
37
+ yield session
38
+ await session.rollback() # Cleanup
39
+
40
+ @pytest.fixture
41
+ def sample_user():
42
+ return {"name": "Test User", "email": f"test_{uuid4().hex[:8]}@test.com", "password": "Pass1234!"}
43
+ ```
44
+
45
+ ## Async Tests
46
+
47
+ ```python
48
+ import pytest
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_create_user(client, sample_user):
52
+ response = await client.post("/api/v1/users", json=sample_user)
53
+ assert response.status_code == 201
54
+ data = response.json()
55
+ assert data["email"] == sample_user["email"]
56
+ assert "id" in data
57
+ assert "password" not in data # Never leak passwords
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_get_me_unauthorized(client):
61
+ response = await client.get("/api/v1/users/me")
62
+ assert response.status_code == 401
63
+ ```
64
+
65
+ ## Parameterized Tests
66
+
67
+ ```python
68
+ @pytest.mark.parametrize("email,expected", [
69
+ ("valid@test.com", 201),
70
+ ("invalid-email", 422),
71
+ ("", 422),
72
+ ])
73
+ async def test_email_validation(client, email, expected):
74
+ response = await client.post("/api/v1/users", json={"name": "Test", "email": email, "password": "Pass1234!"})
75
+ assert response.status_code == expected
76
+ ```
77
+
78
+ ## Mocking
79
+
80
+ ```python
81
+ from unittest.mock import AsyncMock, patch
82
+
83
+ @pytest.mark.asyncio
84
+ async def test_external_api_failure(client):
85
+ with patch("app.services.external.fetch_data", new_callable=AsyncMock) as mock:
86
+ mock.side_effect = ConnectionError("API down")
87
+ response = await client.get("/api/v1/data")
88
+ assert response.status_code == 503
89
+ ```
90
+
91
+ ## Commands
92
+
93
+ ```bash
94
+ pytest # Run all
95
+ pytest -x # Stop on first failure
96
+ pytest --tb=short # Short traceback
97
+ pytest -k "test_create" # Filter by name
98
+ pytest --cov=app --cov-report=html # Coverage
99
+ pytest -n auto # Parallel (pytest-xdist)
100
+ ```
101
+
102
+ ## FORBIDDEN
103
+
104
+ 1. **No fixtures for cleanup** โ€” always rollback/cleanup
105
+ 2. **Hardcoded test data** โ€” use factories/uuid
106
+ 3. **Testing implementation** โ€” test behavior, not internals
107
+ 4. **Skipping async tests** โ€” use `pytest-asyncio`
108
+ 5. **No coverage in CI** โ€” `--cov --cov-fail-under=70`
@@ -0,0 +1,110 @@
1
+ # Python Patterns โ€” Architecture & Decision-Making
2
+
3
+ **ALWAYS invoke when making Python architecture decisions.**
4
+
5
+ ## Framework Selection
6
+
7
+ ```
8
+ What are you building?
9
+ โ”œโ”€โ”€ API / Microservices โ†’ FastAPI (async, Pydantic, fast)
10
+ โ”œโ”€โ”€ Full-stack / CMS / Admin โ†’ Django (batteries-included)
11
+ โ”œโ”€โ”€ Simple / Script โ†’ Flask (minimal)
12
+ โ”œโ”€โ”€ AI/ML API serving โ†’ FastAPI (Pydantic, uvicorn)
13
+ โ””โ”€โ”€ Background workers โ†’ Celery + any framework
14
+ ```
15
+
16
+ ## Async vs Sync
17
+
18
+ ```
19
+ I/O-bound (waiting for DB, HTTP, files) โ†’ async def
20
+ CPU-bound (computing, parsing) โ†’ def + multiprocessing
21
+
22
+ Don't:
23
+ โ”œโ”€โ”€ Mix sync and async carelessly
24
+ โ”œโ”€โ”€ Use sync libraries in async code (blocks event loop!)
25
+ โ””โ”€โ”€ Force async for CPU work
26
+ ```
27
+
28
+ ### Async Library Selection
29
+
30
+ | Need | Library |
31
+ |------|---------|
32
+ | HTTP client | `httpx` |
33
+ | PostgreSQL | `asyncpg` |
34
+ | Redis | `redis[async]` |
35
+ | File I/O | `aiofiles` |
36
+ | ORM | SQLAlchemy 2.0+ async, Tortoise |
37
+
38
+ ## Type Hints (MANDATORY for public APIs)
39
+
40
+ ```python
41
+ from typing import Optional
42
+
43
+ def find_user(id: int) -> Optional[User]: ...
44
+ def process(data: str | dict) -> None: ...
45
+ def get_items() -> list[Item]: ...
46
+ ```
47
+
48
+ ## Project Structure
49
+
50
+ ### FastAPI
51
+ ```
52
+ app/
53
+ โ”œโ”€โ”€ main.py # FastAPI app + startup
54
+ โ”œโ”€โ”€ api/v1/
55
+ โ”‚ โ”œโ”€โ”€ routes/ # Endpoints
56
+ โ”‚ โ””โ”€โ”€ deps.py # Dependencies (auth, db)
57
+ โ”œโ”€โ”€ models/ # SQLAlchemy / Beanie models
58
+ โ”œโ”€โ”€ schemas/ # Pydantic schemas
59
+ โ”œโ”€โ”€ services/ # Business logic
60
+ โ”œโ”€โ”€ core/
61
+ โ”‚ โ”œโ”€โ”€ config.py # Pydantic Settings
62
+ โ”‚ โ””โ”€โ”€ security.py # Auth helpers
63
+ โ””โ”€โ”€ tests/
64
+ ```
65
+
66
+ ### Django
67
+ ```
68
+ myproject/
69
+ โ”œโ”€โ”€ manage.py
70
+ โ”œโ”€โ”€ config/ # Settings, URLs, ASGI/WSGI
71
+ โ”œโ”€โ”€ apps/
72
+ โ”‚ โ”œโ”€โ”€ users/ # Per-app: models, views, serializers, tests
73
+ โ”‚ โ””โ”€โ”€ products/
74
+ โ””โ”€โ”€ tests/
75
+ ```
76
+
77
+ ## Error Handling
78
+
79
+ ```python
80
+ # Custom exceptions in services
81
+ class NotFoundError(Exception):
82
+ def __init__(self, resource: str, id: str):
83
+ self.resource = resource
84
+ self.id = id
85
+
86
+ # FastAPI exception handler
87
+ @app.exception_handler(NotFoundError)
88
+ async def not_found_handler(request, exc):
89
+ return JSONResponse(status_code=404, content={
90
+ "error": "not_found",
91
+ "message": f"{exc.resource} {exc.id} not found"
92
+ })
93
+ ```
94
+
95
+ ## Background Tasks
96
+
97
+ | Solution | Best For |
98
+ |----------|----------|
99
+ | `BackgroundTasks` | Simple, in-process, fire-and-forget |
100
+ | `Celery` | Distributed, retries, complex workflows |
101
+ | `ARQ` | Async, Redis-based, lightweight |
102
+ | `Dramatiq` | Actor-based, simpler than Celery |
103
+
104
+ ## FORBIDDEN
105
+
106
+ 1. **Business logic in routes/views** โ€” use services layer
107
+ 2. **Sync libraries in async code** โ€” blocks event loop
108
+ 3. **No type hints on public APIs** โ€” always type
109
+ 4. **Raw SQL without parameterization** โ€” injection risk
110
+ 5. **`import *`** โ€” explicit imports only
@@ -0,0 +1,109 @@
1
+ # Python Performance โ€” Profiling & Optimization
2
+
3
+ **ALWAYS invoke when optimizing slow Python code.**
4
+
5
+ ## Profiling Tools
6
+
7
+ ```bash
8
+ # CPU profiling
9
+ python -m cProfile -s cumulative app.py
10
+
11
+ # Line profiling (pip install line-profiler)
12
+ kernprof -l -v script.py
13
+
14
+ # Memory profiling (pip install memory-profiler)
15
+ python -m memory_profiler script.py
16
+
17
+ # py-spy (production-safe, no code changes)
18
+ py-spy top --pid 12345
19
+ py-spy record -o profile.svg --pid 12345
20
+ ```
21
+
22
+ ## Common Optimizations
23
+
24
+ ### Data Structures
25
+ ```python
26
+ # Use set for membership testing (O(1) vs O(n))
27
+ # SLOW:
28
+ if item in large_list: ...
29
+ # FAST:
30
+ large_set = set(large_list)
31
+ if item in large_set: ...
32
+
33
+ # Use dict.get() instead of try/except
34
+ value = data.get('key', default)
35
+
36
+ # Use collections for specialized needs
37
+ from collections import defaultdict, Counter, deque
38
+ ```
39
+
40
+ ### Generators (memory)
41
+ ```python
42
+ # WRONG โ€” loads everything in memory
43
+ def get_all():
44
+ return [process(x) for x in huge_dataset]
45
+
46
+ # CORRECT โ€” lazy evaluation
47
+ def get_all():
48
+ for x in huge_dataset:
49
+ yield process(x)
50
+
51
+ # Or generator expression
52
+ total = sum(x.price for x in orders) # Not [x.price for x in orders]
53
+ ```
54
+
55
+ ### String Operations
56
+ ```python
57
+ # SLOW โ€” string concatenation in loop
58
+ result = ""
59
+ for s in strings:
60
+ result += s # O(nยฒ)
61
+
62
+ # FAST โ€” join
63
+ result = "".join(strings) # O(n)
64
+ ```
65
+
66
+ ### Database (SQLAlchemy / Django ORM)
67
+ ```python
68
+ # Bulk operations (not one-by-one)
69
+ # SLOW:
70
+ for item in items:
71
+ db.add(Item(**item))
72
+
73
+ # FAST:
74
+ db.add_all([Item(**item) for item in items])
75
+ await db.commit()
76
+
77
+ # Django bulk
78
+ Item.objects.bulk_create([Item(**item) for item in items], batch_size=1000)
79
+ ```
80
+
81
+ ### Caching
82
+ ```python
83
+ from functools import lru_cache
84
+
85
+ @lru_cache(maxsize=256)
86
+ def expensive_computation(n: int) -> int:
87
+ return sum(range(n))
88
+
89
+ # Async caching with Redis
90
+ import redis.asyncio as redis
91
+
92
+ cache = redis.from_url("redis://localhost")
93
+
94
+ async def get_user(id: str) -> User:
95
+ cached = await cache.get(f"user:{id}")
96
+ if cached:
97
+ return User.model_validate_json(cached)
98
+ user = await db.get(User, id)
99
+ await cache.setex(f"user:{id}", 300, user.model_dump_json())
100
+ return user
101
+ ```
102
+
103
+ ## FORBIDDEN
104
+
105
+ 1. **Premature optimization** โ€” profile FIRST, optimize SECOND
106
+ 2. **`+` for string concatenation in loops** โ€” use `"".join()`
107
+ 3. **`list` for membership testing** โ€” use `set`
108
+ 4. **Loading entire dataset in memory** โ€” use generators/pagination
109
+ 5. **Individual DB inserts in loop** โ€” use bulk operations
@@ -0,0 +1,45 @@
1
+ {
2
+ "id": "python",
3
+ "name": "Python 3.12+",
4
+ "icon": "๐Ÿ",
5
+ "runtime": "python",
6
+ "requirements": [
7
+ { "command": "python3", "minVersion": "3.12.0", "install": "brew install python@3.12" },
8
+ { "command": "pip3", "minVersion": "23.0", "install": "python3 -m ensurepip --upgrade" },
9
+ { "command": "uv", "minVersion": "0.1.0", "install": "curl -LsSf https://astral.sh/uv/install.sh | sh", "optional": true }
10
+ ],
11
+ "frameworks": [
12
+ { "id": "fastapi", "name": "FastAPI + Uvicorn", "icon": "โšก" },
13
+ { "id": "django", "name": "Django 5+", "icon": "๐ŸŽธ" },
14
+ { "id": "flask", "name": "Flask", "icon": "๐Ÿงช" },
15
+ { "id": "vanilla", "name": "Vanilla Python", "icon": "๐Ÿ" }
16
+ ],
17
+ "databases": [
18
+ { "id": "postgresql", "name": "PostgreSQL", "icon": "๐Ÿ˜" },
19
+ { "id": "mysql", "name": "MySQL / MariaDB", "icon": "๐Ÿฌ" },
20
+ { "id": "mongodb", "name": "MongoDB", "icon": "๐Ÿƒ" },
21
+ { "id": "sqlite", "name": "SQLite", "icon": "๐Ÿ“ฆ" }
22
+ ],
23
+ "frontendOptions": [
24
+ { "id": "react", "name": "React (SPA)", "icon": "โš›๏ธ" },
25
+ { "id": "htmx", "name": "HTMX + Jinja2", "icon": "๐Ÿ”„" },
26
+ { "id": "none", "name": "API only (no frontend)", "icon": "๐Ÿ”Œ" }
27
+ ],
28
+ "deployTargets": [
29
+ { "id": "github", "name": "GitHub (git push)", "icon": "๐Ÿ™" }
30
+ ],
31
+ "skills": [
32
+ "python-patterns",
33
+ "fastapi-patterns",
34
+ "django-patterns",
35
+ "pydantic-validation",
36
+ "pytest-testing",
37
+ "async-patterns",
38
+ "python-performance"
39
+ ],
40
+ "qualityGates": [
41
+ { "name": "mypy", "command": "mypy .", "description": "Type checking" },
42
+ { "name": "ruff", "command": "ruff check .", "description": "Linting" },
43
+ { "name": "pytest", "command": "pytest --tb=short", "description": "Tests" }
44
+ ]
45
+ }