UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,742 lines (1,472 loc) 54.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.starletteTemplate = void 0; exports.starletteTemplate = { id: 'starlette', name: 'Starlette', displayName: 'Starlette', description: 'Lightweight ASGI framework for high-performance asyncio services', language: 'python', framework: 'starlette', version: '0.37.2', tags: ['asgi', 'async', 'lightweight', 'high-performance', 'websockets'], port: 8000, features: [ 'ASGI framework', 'WebSocket support', 'GraphQL with Ariadne', 'Session middleware', 'CORS middleware', 'Authentication', 'Static files', 'Jinja2 templating', 'Background tasks', 'SQLAlchemy integration', 'pytest testing', 'Docker support' ], dependencies: { 'starlette': '^0.37.2', 'uvicorn[standard]': '^0.30.1', 'python-multipart': '^0.0.9', 'jinja2': '^3.1.4', 'itsdangerous': '^2.2.0', 'sqlalchemy': '^2.0.30', 'alembic': '^1.13.1', 'asyncpg': '^0.29.0', 'ariadne': '^0.23.0', 'python-jose[cryptography]': '^3.3.0', 'passlib[bcrypt]': '^1.7.4', 'python-dotenv': '^1.0.1', 'httpx': '^0.27.0', 'redis': '^5.0.3', 'aiofiles': '^23.2.1', 'pytest': '^8.2.0', 'pytest-asyncio': '^0.23.7', 'pytest-cov': '^5.0.0', 'black': '^24.4.2', 'flake8': '^7.0.0', 'mypy': '^1.10.0', 'isort': '^5.13.2', 'pre-commit': '^3.7.1' }, files: { 'src/': { '__init__.py': '', 'main.py': `import os from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.trustedhost import TrustedHostMiddleware from starlette.routing import Mount from starlette.staticfiles import StaticFiles from .config import settings from .database import database from .middleware import AuthenticationMiddleware, RequestIdMiddleware from .routes import api_routes, websocket_routes, graphql_app from .templates import templates # Configure middleware middleware = [ Middleware( TrustedHostMiddleware, allowed_hosts=settings.ALLOWED_HOSTS ), Middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=\["*"\], allow_headers=\["*"\], ), Middleware( SessionMiddleware, secret_key=settings.SECRET_KEY, https_only=settings.HTTPS_ONLY, same_site="lax" ), Middleware(RequestIdMiddleware), Middleware(AuthenticationMiddleware), ] # Create application app = Starlette( debug=settings.DEBUG, middleware=middleware, on_startup=[database.connect], on_shutdown=[database.disconnect], ) # Mount routes app.mount("/api", api_routes, name="api") app.mount("/ws", websocket_routes, name="websocket") app.mount("/graphql", graphql_app, name="graphql") app.mount("/static", StaticFiles(directory="static"), name="static") # Add template routes @app.route("/") async def homepage(request): return templates.TemplateResponse( "index.html", \{"request": request, "title": "Starlette App"\} ) @app.route("/health") async def health_check(request): return {"status": "healthy", "service": "starlette-backend"} if __name__ == "__main__": import uvicorn uvicorn.run( "src.main:app", host="0.0.0.0", port=8000, reload=settings.DEBUG, log_level=settings.LOG_LEVEL.lower(), )`, 'config.py': `import os from typing import List from functools import lru_cache from pydantic import BaseSettings, Field class Settings(BaseSettings): # Application APP_NAME: str = "Starlette Backend" DEBUG: bool = Field(False, env="DEBUG") LOG_LEVEL: str = Field("INFO", env="LOG_LEVEL") SECRET_KEY: str = Field(..., env="SECRET_KEY") # Server ALLOWED_HOSTS: List[str] = Field(\["*"\], env="ALLOWED_HOSTS") CORS_ORIGINS: List[str] = Field(\["http://localhost:3000"\], env="CORS_ORIGINS") HTTPS_ONLY: bool = Field(False, env="HTTPS_ONLY") # Database DATABASE_URL: str = Field(..., env="DATABASE_URL") DATABASE_POOL_SIZE: int = Field(10, env="DATABASE_POOL_SIZE") DATABASE_MAX_OVERFLOW: int = Field(20, env="DATABASE_MAX_OVERFLOW") # Redis REDIS_URL: str = Field("redis://localhost:6379", env="REDIS_URL") # JWT JWT_SECRET_KEY: str = Field(..., env="JWT_SECRET_KEY") JWT_ALGORITHM: str = Field("HS256", env="JWT_ALGORITHM") JWT_EXPIRATION_MINUTES: int = Field(30, env="JWT_EXPIRATION_MINUTES") # External Services SMTP_HOST: str = Field("", env="SMTP_HOST") SMTP_PORT: int = Field(587, env="SMTP_PORT") SMTP_USERNAME: str = Field("", env="SMTP_USERNAME") SMTP_PASSWORD: str = Field("", env="SMTP_PASSWORD") class Config: env_file = ".env" case_sensitive = True @lru_cache() def get_settings() -> Settings: return Settings() settings = get_settings()`, 'database.py': `from sqlalchemy import create_engine, MetaData from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from databases import Database from .config import settings # Database setup database = Database(settings.DATABASE_URL) metadata = MetaData() # SQLAlchemy setup engine = create_engine( settings.DATABASE_URL, pool_size=settings.DATABASE_POOL_SIZE, max_overflow=settings.DATABASE_MAX_OVERFLOW, ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() # Dependency async def get_db(): async with database.transaction(): yield database`, 'models/': { '__init__.py': `from .user import User from .session import Session __all__ = ["User", "Session"]`, 'user.py': `from datetime import datetime from sqlalchemy import Column, Integer, String, Boolean, DateTime from sqlalchemy.orm import relationship from ..database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) username = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) is_active = Column(Boolean, default=True) is_verified = Column(Boolean, default=False) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Relationships sessions = relationship("Session", back_populates="user", cascade="all, delete-orphan")`, 'session.py': `from datetime import datetime from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean from sqlalchemy.orm import relationship from ..database import Base class Session(Base): __tablename__ = "sessions" id = Column(Integer, primary_key=True, index=True) token = Column(String, unique=True, index=True, nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) is_active = Column(Boolean, default=True) expires_at = Column(DateTime, nullable=False) created_at = Column(DateTime, default=datetime.utcnow) # Relationships user = relationship("User", back_populates="sessions")` }, 'schemas/': { '__init__.py': `from .user import UserCreate, UserUpdate, UserResponse from .auth import LoginRequest, LoginResponse, TokenData __all__ = [ "UserCreate", "UserUpdate", "UserResponse", "LoginRequest", "LoginResponse", "TokenData" ]`, 'user.py': `from datetime import datetime from typing import Optional from pydantic import BaseModel, EmailStr, Field class UserBase(BaseModel): email: EmailStr username: str = Field(..., min_length=3, max_length=50) class UserCreate(UserBase): password: str = Field(..., min_length=8) class UserUpdate(BaseModel): email: Optional[EmailStr] = None username: Optional[str] = Field(None, min_length=3, max_length=50) password: Optional[str] = Field(None, min_length=8) class UserResponse(UserBase): id: int is_active: bool is_verified: bool created_at: datetime updated_at: datetime class Config: orm_mode = True`, 'auth.py': `from typing import Optional from pydantic import BaseModel, EmailStr class LoginRequest(BaseModel): username: str # Can be email or username password: str class LoginResponse(BaseModel): access_token: str token_type: str = "bearer" expires_in: int class TokenData(BaseModel): sub: Optional[str] = None exp: Optional[int] = None` }, 'routes/': { '__init__.py': `from starlette.routing import Mount, Route from starlette.applications import Starlette from .auth import auth_routes from .users import user_routes from .websocket import websocket_endpoint, websocket_routes from .graphql import graphql_app # API Routes api_routes = Starlette(routes=[ Mount("/auth", app=auth_routes, name="auth"), Mount("/users", app=user_routes, name="users"), ]) __all__ = ["api_routes", "websocket_routes", "graphql_app"]`, 'auth.py': `from starlette.applications import Starlette from starlette.routing import Route from starlette.responses import JSONResponse from starlette.exceptions import HTTPException from ..schemas import LoginRequest, LoginResponse from ..services.auth import AuthService from ..utils.dependencies import get_auth_service async def login(request): data = await request.json() login_data = LoginRequest(**data) auth_service = get_auth_service() try: token_response = await auth_service.authenticate_user( login_data.username, login_data.password ) return JSONResponse(token_response.dict()) except ValueError as e: raise HTTPException(status_code=401, detail=str(e)) async def logout(request): # Get token from header token = request.headers.get("Authorization", "").replace("Bearer ", "") if not token: raise HTTPException(status_code=401, detail="Not authenticated") auth_service = get_auth_service() await auth_service.revoke_token(token) return JSONResponse(\{"message": "Successfully logged out"\}) async def refresh(request): # Get refresh token from request data = await request.json() refresh_token = data.get("refresh_token") if not refresh_token: raise HTTPException(status_code=400, detail="Refresh token required") auth_service = get_auth_service() try: token_response = await auth_service.refresh_token(refresh_token) return JSONResponse(token_response.dict()) except ValueError as e: raise HTTPException(status_code=401, detail=str(e)) auth_routes = Starlette(routes=[ Route("/login", login, methods=\["POST"\]), Route("/logout", logout, methods=\["POST"\]), Route("/refresh", refresh, methods=\["POST"\]), ])`, 'users.py': `from starlette.applications import Starlette from starlette.routing import Route from starlette.responses import JSONResponse from starlette.exceptions import HTTPException from ..schemas import UserCreate, UserUpdate, UserResponse from ..services.user import UserService from ..utils.dependencies import get_user_service, require_auth async def create_user(request): data = await request.json() user_data = UserCreate(**data) user_service = get_user_service() try: user = await user_service.create_user(user_data) return JSONResponse( UserResponse.from_orm(user).dict(), status_code=201 ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @require_auth async def get_current_user(request): user = request.state.user return JSONResponse(UserResponse.from_orm(user).dict()) @require_auth async def update_user(request): user = request.state.user data = await request.json() update_data = UserUpdate(**data) user_service = get_user_service() try: updated_user = await user_service.update_user(user.id, update_data) return JSONResponse(UserResponse.from_orm(updated_user).dict()) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @require_auth async def delete_user(request): user = request.state.user user_service = get_user_service() await user_service.delete_user(user.id) return JSONResponse(\{"message": "User deleted successfully"\}) user_routes = Starlette(routes=[ Route("/", create_user, methods=\["POST"\]), Route("/me", get_current_user, methods=\["GET"\]), Route("/me", update_user, methods=\["PUT"\]), Route("/me", delete_user, methods=\["DELETE"\]), ])`, 'websocket.py': `from starlette.applications import Starlette from starlette.routing import WebSocketRoute from starlette.websockets import WebSocket from starlette.exceptions import WebSocketException import json import asyncio from typing import Dict, Set # Connection manager class ConnectionManager: def __init__(self): self.active_connections: Dict[str, Set[WebSocket]] = \{\} async def connect(self, websocket: WebSocket, room: str = "default"): await websocket.accept() if room not in self.active_connections: self.active_connections[room] = set() self.active_connections[room].add(websocket) def disconnect(self, websocket: WebSocket, room: str = "default"): if room in self.active_connections: self.active_connections[room].discard(websocket) if not self.active_connections[room]: del self.active_connections[room] async def broadcast(self, message: dict, room: str = "default"): if room in self.active_connections: # Create tasks for all sends tasks = [] for connection in self.active_connections[room]: tasks.append(connection.send_json(message)) # Send to all connections concurrently await asyncio.gather(*tasks, return_exceptions=True) manager = ConnectionManager() async def websocket_endpoint(websocket: WebSocket): room = websocket.query_params.get("room", "default") await manager.connect(websocket, room) try: while True: # Receive message data = await websocket.receive_json() # Process message based on type message_type = data.get("type", "message") if message_type == "message": # Broadcast to room await manager.broadcast(\{ "type": "message", "data": data.get("data"), "sender": websocket.client.host, "room": room \}, room) elif message_type == "join": new_room = data.get("room", "default") manager.disconnect(websocket, room) await manager.connect(websocket, new_room) room = new_room await websocket.send_json(\{ "type": "room_joined", "room": room \}) elif message_type == "ping": await websocket.send_json(\{"type": "pong"\}) except Exception as e: manager.disconnect(websocket, room) raise finally: manager.disconnect(websocket, room) websocket_routes = Starlette(routes=[ WebSocketRoute("/", websocket_endpoint), ])`, 'graphql.py': `from ariadne import QueryType, MutationType, make_executable_schema from ariadne.asgi import GraphQL from starlette.authentication import requires from ..services.user import UserService from ..utils.dependencies import get_user_service # Type definitions type_defs = """ type Query { me: User user(id: ID!): User users(limit: Int = 10, offset: Int = 0): [User!]! } type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! } type User { id: ID! email: String! username: String! isActive: Boolean! isVerified: Boolean! createdAt: String! updatedAt: String! } input CreateUserInput { email: String! username: String! password: String! } input UpdateUserInput { email: String username: String password: String } """ # Resolvers query = QueryType() mutation = MutationType() @query.field("me") @requires("authenticated") async def resolve_me(obj, info): user = info.context\["request"\].state.user return user @query.field("user") async def resolve_user(obj, info, id): user_service = get_user_service() return await user_service.get_user(int(id)) @query.field("users") async def resolve_users(obj, info, limit=10, offset=0): user_service = get_user_service() return await user_service.list_users(limit=limit, offset=offset) @mutation.field("createUser") async def resolve_create_user(obj, info, input): user_service = get_user_service() return await user_service.create_user(input) @mutation.field("updateUser") @requires("authenticated") async def resolve_update_user(obj, info, id, input): user_service = get_user_service() return await user_service.update_user(int(id), input) @mutation.field("deleteUser") @requires("authenticated") async def resolve_delete_user(obj, info, id): user_service = get_user_service() await user_service.delete_user(int(id)) return True # Create executable schema schema = make_executable_schema(type_defs, query, mutation) # Create GraphQL app graphql_app = GraphQL(schema, debug=True)` }, 'services/': { '__init__.py': '', 'auth.py': `from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from ..config import settings from ..database import database from ..models import User, Session from ..schemas import LoginResponse, TokenData pwd_context = CryptContext(schemes=\["bcrypt"\], deprecated="auto") class AuthService: def __init__(self): self.secret_key = settings.JWT_SECRET_KEY self.algorithm = settings.JWT_ALGORITHM self.expiration_minutes = settings.JWT_EXPIRATION_MINUTES def verify_password(self, plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) def get_password_hash(self, password: str) -> str: return pwd_context.hash(password) def create_access_token(self, data: dict) -> str: to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=self.expiration_minutes) to_encode.update(\{"exp": expire\}) return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm) async def authenticate_user(self, username: str, password: str) -> LoginResponse: # Check if username is email or username query = """ SELECT * FROM users WHERE email = :username OR username = :username """ user = await database.fetch_one(query=query, values=\{"username": username\}) if not user or not self.verify_password(password, user\["hashed_password"\]): raise ValueError("Invalid credentials") if not user\["is_active"\]: raise ValueError("User account is inactive") # Create token access_token = self.create_access_token(data=\{"sub": str(user\["id"\])\}) # Store session expires_at = datetime.utcnow() + timedelta(minutes=self.expiration_minutes) query = """ INSERT INTO sessions (token, user_id, expires_at) VALUES (:token, :user_id, :expires_at) """ await database.execute( query=query, values=\{ "token": access_token, "user_id": user\["id"\], "expires_at": expires_at \} ) return LoginResponse( access_token=access_token, expires_in=self.expiration_minutes * 60 ) async def get_current_user(self, token: str) -> Optional[User]: try: payload = jwt.decode(token, self.secret_key, algorithms=\[self.algorithm\]) user_id: str = payload.get("sub") if user_id is None: return None except JWTError: return None # Check if session is valid query = """ SELECT u.* FROM users u JOIN sessions s ON u.id = s.user_id WHERE s.token = :token AND s.is_active = true AND s.expires_at > :now """ user = await database.fetch_one( query=query, values=\{"token": token, "now": datetime.utcnow()\} ) return user async def revoke_token(self, token: str): query = """ UPDATE sessions SET is_active = false WHERE token = :token """ await database.execute(query=query, values=\{"token": token\})`, 'user.py': `from typing import List, Optional from sqlalchemy import select, update, delete from ..database import database from ..models import User from ..schemas import UserCreate, UserUpdate from .auth import AuthService class UserService: def __init__(self): self.auth_service = AuthService() async def create_user(self, user_data: UserCreate) -> User: # Check if user exists query = select(User).where( (User.email == user_data.email) | (User.username == user_data.username) ) existing = await database.fetch_one(query) if existing: raise ValueError("User with this email or username already exists") # Hash password hashed_password = self.auth_service.get_password_hash(user_data.password) # Create user query = """ INSERT INTO users (email, username, hashed_password) VALUES (:email, :username, :hashed_password) RETURNING * """ user = await database.fetch_one( query=query, values=\{ "email": user_data.email, "username": user_data.username, "hashed_password": hashed_password \} ) return user async def get_user(self, user_id: int) -> Optional[User]: query = select(User).where(User.id == user_id) return await database.fetch_one(query) async def list_users(self, limit: int = 10, offset: int = 0) -> List[User]: query = select(User).limit(limit).offset(offset) return await database.fetch_all(query) async def update_user(self, user_id: int, update_data: UserUpdate) -> User: values = \{\} if update_data.email: # Check if email is taken query = select(User).where( (User.email == update_data.email) & (User.id != user_id) ) if await database.fetch_one(query): raise ValueError("Email already taken") values\["email"\] = update_data.email if update_data.username: # Check if username is taken query = select(User).where( (User.username == update_data.username) & (User.id != user_id) ) if await database.fetch_one(query): raise ValueError("Username already taken") values\["username"\] = update_data.username if update_data.password: values\["hashed_password"\] = self.auth_service.get_password_hash( update_data.password ) if not values: # No updates return await self.get_user(user_id) # Update user query = f""" UPDATE users SET \{', '.join(f'\{k\} = :\{k\}' for k in values.keys())\}, updated_at = CURRENT_TIMESTAMP WHERE id = :user_id RETURNING * """ values\["user_id"\] = user_id user = await database.fetch_one(query=query, values=values) if not user: raise ValueError("User not found") return user async def delete_user(self, user_id: int): query = delete(User).where(User.id == user_id) result = await database.execute(query) if result == 0: raise ValueError("User not found")` }, 'middleware/': { '__init__.py': `from .auth import AuthenticationMiddleware from .request_id import RequestIdMiddleware __all__ = ["AuthenticationMiddleware", "RequestIdMiddleware"]`, 'auth.py': `from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse from ..services.auth import AuthService class AuthenticationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): # Skip auth for public routes public_paths = \["/", "/health", "/api/auth/login", "/api/auth/refresh", "/static"\] if any(request.url.path.startswith(path) for path in public_paths): return await call_next(request) # Get token from header auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): return JSONResponse( {"detail": "Invalid authentication credentials"}, status_code=401 ) token = auth_header.replace("Bearer ", "") auth_service = AuthService() # Validate token and get user user = await auth_service.get_current_user(token) if not user: return JSONResponse( {"detail": "Invalid or expired token"}, status_code=401 ) # Add user to request state request.state.user = user request.state.token = token response = await call_next(request) return response`, 'request_id.py': `import uuid from starlette.middleware.base import BaseHTTPMiddleware class RequestIdMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): # Generate request ID request_id = str(uuid.uuid4()) # Add to request state request.state.request_id = request_id # Process request response = await call_next(request) # Add to response headers response.headers\["X-Request-ID"\] = request_id return response` }, 'utils/': { '__init__.py': '', 'dependencies.py': `from functools import wraps from starlette.exceptions import HTTPException from ..services.auth import AuthService from ..services.user import UserService # Service instances (could be replaced with DI container) _auth_service = None _user_service = None def get_auth_service() -> AuthService: global _auth_service if _auth_service is None: _auth_service = AuthService() return _auth_service def get_user_service() -> UserService: global _user_service if _user_service is None: _user_service = UserService() return _user_service # Decorator for routes that require authentication def require_auth(func): @wraps(func) async def wrapper(request, *args, **kwargs): if not hasattr(request.state, "user"): raise HTTPException(status_code=401, detail="Not authenticated") return await func(request, *args, **kwargs) return wrapper # Decorator for routes that require specific permissions def require_permission(permission: str): def decorator(func): @wraps(func) async def wrapper(request, *args, **kwargs): if not hasattr(request.state, "user"): raise HTTPException(status_code=401, detail="Not authenticated") # Check permission (implement your permission logic) # For now, just pass through return await func(request, *args, **kwargs) return wrapper return decorator`, 'background.py': `import asyncio from typing import Callable, Any from starlette.background import BackgroundTask class BackgroundTaskManager: def __init__(self): self.tasks = [] def add_task(self, func: Callable, *args, **kwargs): """Add a background task to be executed""" task = BackgroundTask(func, *args, **kwargs) self.tasks.append(task) return task async def run_task_async(self, func: Callable, *args, **kwargs): """Run an async task in the background""" loop = asyncio.get_event_loop() loop.create_task(func(*args, **kwargs)) # Global task manager task_manager = BackgroundTaskManager() # Example background tasks async def send_email_async(to: str, subject: str, body: str): """Send email in background""" # Implement email sending logic await asyncio.sleep(1) # Simulate work print(f"Email sent to \{to\}: \{subject\}") async def cleanup_expired_sessions(): """Clean up expired sessions periodically""" from ..database import database while True: try: query = """ DELETE FROM sessions WHERE expires_at < CURRENT_TIMESTAMP """ await database.execute(query) await asyncio.sleep(3600) # Run every hour except Exception as e: print(f"Error cleaning sessions: \{e\}") await asyncio.sleep(300) # Retry in 5 minutes` }, 'templates.py': `from starlette.templating import Jinja2Templates # Configure Jinja2 templates = Jinja2Templates(directory="templates") # Add custom filters @templates.env.filter def datetime_format(value, format="%Y-%m-%d %H:%M:%S"): """Format datetime objects""" if value is None: return "" return value.strftime(format) @templates.env.filter def currency(value): """Format currency values""" return f"$\{value:,.2f\}" # Add global template variables templates.env.globals.update(\{ "app_name": "Starlette Backend", "current_year": 2024, \})` }, 'templates/': { 'base.html': `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>\{% block title %\}\{\{ title|default(app_name) \}\}\{% endblock %\}</title> <link rel="stylesheet" href="\{\{ url_for('static', path='/css/style.css') \}\}"> \{% block extra_css %\}\{% endblock %\} </head> <body> <nav class="navbar"> <div class="container"> <a href="/" class="brand">\{\{ app_name \}\}</a> <ul class="nav-links"> <li><a href="/">Home</a></li> <li><a href="/api/docs">API Docs</a></li> <li><a href="/graphql">GraphQL</a></li> </ul> </div> </nav> <main class="main-content"> <div class="container"> \{% block content %\}\{% endblock %\} </div> </main> <footer class="footer"> <div class="container"> <p>&copy; \{\{ current_year \}\} \{\{ app_name \}\}. All rights reserved.</p> </div> </footer> <script src="\{\{ url_for('static', path='/js/main.js') \}\}"></script> \{% block extra_js %\}\{% endblock %\} </body> </html>`, 'index.html': `\{% extends "base.html" %\} \{% block content %\} <div class="hero"> <h1>Welcome to \{\{ app_name \}\}</h1> <p class="lead">A lightweight ASGI framework for high-performance asyncio services</p> <div class="features"> <div class="feature"> <h3>⚡ Fast & Lightweight</h3> <p>Built on ASGI for maximum performance</p> </div> <div class="feature"> <h3>🔄 WebSocket Support</h3> <p>Real-time communication built-in</p> </div> <div class="feature"> <h3>🎯 GraphQL Ready</h3> <p>Modern API development with Ariadne</p> </div> </div> <div class="cta"> <a href="/api/docs" class="btn btn-primary">Explore API</a> <a href="/graphql" class="btn btn-secondary">Try GraphQL</a> </div> </div> <div class="demo"> <h2>WebSocket Demo</h2> <div id="ws-demo"> <input type="text" id="message-input" placeholder="Type a message..."> <button id="send-btn">Send</button> <div id="messages"></div> </div> </div> <script> // WebSocket demo const ws = new WebSocket('ws://localhost:8000/ws/?room=demo'); const messagesDiv = document.getElementById('messages'); const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'message') { const messageEl = document.createElement('div'); messageEl.className = 'message'; messageEl.textContent = data.data; messagesDiv.appendChild(messageEl); } }; sendBtn.onclick = () => { const message = messageInput.value.trim(); if (message) { ws.send(JSON.stringify({ type: 'message', data: message })); messageInput.value = ''; } }; messageInput.onkeypress = (e) => { if (e.key === 'Enter') { sendBtn.click(); } }; </script> \{% endblock %\}` }, 'static/': { 'css/': { 'style.css': `/* Starlette App Styles */ :root { --primary-color: #5352ed; --secondary-color: #ff6348; --bg-color: #f8f9fa; --text-color: #2c3e50; --border-color: #dfe6e9; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: var(--text-color); background-color: var(--bg-color); line-height: 1.6; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } /* Navigation */ .navbar { background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem 0; } .navbar .container { display: flex; justify-content: space-between; align-items: center; } .brand { font-size: 1.5rem; font-weight: 700; color: var(--primary-color); text-decoration: none; } .nav-links { display: flex; list-style: none; gap: 2rem; } .nav-links a { color: var(--text-color); text-decoration: none; transition: color 0.3s; } .nav-links a:hover { color: var(--primary-color); } /* Main Content */ .main-content { min-height: calc(100vh - 120px); padding: 2rem 0; } /* Hero Section */ .hero { text-align: center; padding: 4rem 0; } .hero h1 { font-size: 3rem; margin-bottom: 1rem; color: var(--primary-color); } .lead { font-size: 1.25rem; color: #718096; margin-bottom: 3rem; } /* Features */ .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; margin-bottom: 3rem; } .feature { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); text-align: center; } .feature h3 { font-size: 1.5rem; margin-bottom: 1rem; color: var(--primary-color); } /* Buttons */ .cta { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; } .btn { padding: 0.75rem 2rem; border-radius: 4px; text-decoration: none; font-weight: 500; transition: all 0.3s; display: inline-block; } .btn-primary { background: var(--primary-color); color: white; } .btn-primary:hover { background: #4342d4; transform: translateY(-2px); } .btn-secondary { background: var(--secondary-color); color: white; } .btn-secondary:hover { background: #ff5232; transform: translateY(-2px); } /* WebSocket Demo */ .demo { margin-top: 4rem; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } #ws-demo { margin-top: 1rem; } #message-input { width: 70%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 4px; font-size: 1rem; } #send-btn { width: 25%; padding: 0.5rem; margin-left: 1%; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; } #send-btn:hover { background: #4342d4; } #messages { margin-top: 1rem; max-height: 300px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 4px; padding: 1rem; background: #f8f9fa; } .message { padding: 0.5rem; margin-bottom: 0.5rem; background: white; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } /* Footer */ .footer { background: white; padding: 2rem 0; text-align: center; border-top: 1px solid var(--border-color); margin-top: 4rem; } .footer p { color: #718096; }` }, 'js/': { 'main.js': `// Starlette App JavaScript console.log('Starlette app loaded'); // Add active class to current nav item document.addEventListener('DOMContentLoaded', () => { const currentPath = window.location.pathname; const navLinks = document.querySelectorAll('.nav-links a'); navLinks.forEach(link => { if (link.getAttribute('href') === currentPath) { link.classList.add('active'); } }); });` } }, 'tests/': { '__init__.py': '', 'conftest.py': `import pytest import asyncio from typing import AsyncGenerator from httpx import AsyncClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from src.main import app from src.database import Base, database from src.config import settings # Test database URL TEST_DATABASE_URL = "sqlite:///./test.db" @pytest.fixture(scope="session") def event_loop(): """Create an instance of the default event loop for the test session.""" loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() @pytest.fixture(scope="session") async def setup_database(): """Setup test database""" # Create test database engine = create_engine(TEST_DATABASE_URL, connect_args=\{"check_same_thread": False\}) Base.metadata.create_all(bind=engine) # Override database URL settings.DATABASE_URL = TEST_DATABASE_URL # Connect to test database await database.connect() yield # Cleanup await database.disconnect() Base.metadata.drop_all(bind=engine) @pytest.fixture async def client(setup_database) -> AsyncGenerator[AsyncClient, None]: """Create test client""" async with AsyncClient(app=app, base_url="http://test") as ac: yield ac @pytest.fixture async def authenticated_client(client: AsyncClient) -> AsyncClient: """Create authenticated test client""" # Create test user response = await client.post( "/api/users/", json=\{ "email": "test@example.com", "username": "testuser", "password": "testpass123" \} ) assert response.status_code == 201 # Login response = await client.post( "/api/auth/login", json=\{ "username": "testuser", "password": "testpass123" \} ) assert response.status_code == 200 token = response.json()\["access_token"\] # Add auth header client.headers\["Authorization"\] = f"Bearer \{token\}" return client`, 'test_auth.py': `import pytest from httpx import AsyncClient @pytest.mark.asyncio async def test_login_success(client: AsyncClient): # Create user await client.post( "/api/users/", json=\{ "email": "auth@example.com", "username": "authuser", "password": "password123" \} ) # Login response = await client.post( "/api/auth/login", json=\{ "username": "authuser", "password": "password123" \} ) assert response.status_code == 200 data = response.json() assert "access_token" in data assert data\["token_type"\] == "bearer" assert data\["expires_in"\] > 0 @pytest.mark.asyncio async def test_login_invalid_credentials(client: AsyncClient): response = await client.post( "/api/auth/login", json=\{ "username": "nonexistent", "password": "wrongpass" \} ) assert response.status_code == 401 assert "Invalid credentials" in response.json()\["detail"\] @pytest.mark.asyncio async def test_logout(authenticated_client: AsyncClient): response = await authenticated_client.post("/api/auth/logout") assert response.status_code == 200 # Try to access protected route response = await authenticated_client.get("/api/users/me") assert response.status_code == 401`, 'test_users.py': `import pytest from httpx import AsyncClient @pytest.mark.asyncio async def test_create_user(client: AsyncClient): response = await client.post( "/api/users/", json=\{ "email": "new@example.com", "username": "newuser", "password": "password123" \} ) assert response.status_code == 201 data = response.json() assert data\["email"\] == "new@example.com" assert data\["username"\] == "newuser" assert "hashed_password" not in data @pytest.mark.asyncio async def test_create_duplicate_user(client: AsyncClient): # Create first user await client.post( "/api/users/", json=\{ "email": "dup@example.com", "username": "dupuser", "password": "password123" \} ) # Try to create duplicate response = await client.post( "/api/users/", json=\{ "email": "dup@example.com", "username": "dupuser2", "password": "password123" \} ) assert response.status_code == 400 assert "already exists" in response.json()\["detail"\] @pytest.mark.asyncio async def test_get_current_user(authenticated_client: AsyncClient): response = await authenticated_client.get("/api/users/me") assert response.status_code == 200 data = response.json() assert data\["email"\] == "test@example.com" assert data\["username"\] == "testuser" @pytest.mark.asyncio async def test_update_user(authenticated_client: AsyncClient): response = await authenticated_client.put( "/api/users/me", json=\{"email": "updated@example.com"\} ) assert response.status_code == 200 data = response.json() assert data\["email"\] == "updated@example.com" @pytest.mark.asyncio async def test_delete_user(authenticated_client: AsyncClient): response = await authenticated_client.delete("/api/users/me") assert response.status_code == 200 # Try to get deleted user response = await authenticated_client.get("/api/users/me") assert response.status_code == 401`, 'test_websocket.py': `import pytest import asyncio from starlette.testclient import TestClient from src.main import app def test_websocket_connection(): client = TestClient(app) with client.websocket_connect("/ws/") as websocket: # Send message websocket.send_json(\{ "type": "message", "data": "Hello, WebSocket!" \}) # Receive broadcast data = websocket.receive_json() assert data\["type"\] == "message" assert data\["data"\] == "Hello, WebSocket!" def test_websocket_rooms(): client = TestClient(app) with client.websocket_connect("/ws/?room=test-room") as websocket: # Join different room websocket.send_json(\{ "type": "join", "room": "another-room" \}) # Receive confirmation data = websocket.receive_json() assert data\["type"\] == "room_joined" assert data\["room"\] == "another-room" def test_websocket_ping_pong(): client = TestClient(app) with client.websocket_connect("/ws/") as websocket: # Send ping websocket.send_json(\{"type": "ping"\}) # Receive pong data = websocket.receive_json() assert data\["type"\] == "pong"` }, 'alembic/': { 'alembic.ini': `# Alembic Configuration [alembic] script_location = alembic prepend_sys_path = . version_path_separator = os sqlalchemy.url = driver://user:pass@localhost/dbname [post_write_hooks] hooks = black black.type = console_scripts black.entrypoint = black black.options = -l 88 REVISION_SCRIPT_FILENAME [loggers] keys = root,sqlalchemy,alembic [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console qualname = [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine [logger_alembic] level = INFO handlers = qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S`, 'env.py': `from logging.config import fileConfig from sqlalchemy import engine_from_config from sqlalchemy import pool from alembic import context import os import sys from pathlib import Path # Add parent directory to path sys.path.append(str(Path(__file__).parent.parent)) from src.database import Base from src.config import settings # this is the Alembic Config object config = context.config # Set database URL from settings config.set_main_option("sqlalchemy.url", settings.DATABASE_URL) # Interpret the config file for Python logging if config.config_file_name is not None: fileConfig(config.config_file_name) # Add model's MetaData object for 'autogenerate' support target_metadata = Base.metadata def run_migrations_offline() -> None: """Run migrations in 'offline' mode.""" url = config.get_main_option("sqlalchemy.url") context.configure( url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts=\{"paramstyle": "named"\}, ) with context.begin_transaction(): context.run_migrations() def run_migrations_online() -> None: """Run migrations in 'online' mode.""" connectable = engine_from_config( config.get_section(config.config_ini_section), prefix="sqlalchemy.", poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata ) with context.begin_transaction(): context.run_migrations() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()`, 'versions/': { '.gitkeep': '' } }, '.env.example': `# Application DEBUG=true LOG_LEVEL=INFO SECRET_KEY=your-secret-key-here # Server ALLOWED_HOSTS=\["*"\] CORS_ORIGINS=\["http://localhost:3000"\] HTTPS_ONLY=false # Database DATABASE_URL=postgresql://user:password@localhost/dbname DATABASE_POOL_SIZE=10 DATABASE_MAX_OVERFLOW=20 # Redis REDIS_URL=redis://localhost:6379 # JWT JWT_SECRET_KEY=your-jwt-secret-key JWT_ALGORITHM=HS256 JWT_EXPIRATION_MINUTES=30 # Email SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USERNAME=your-email@gmail.com SMTP_PASSWORD=your-app-password`, 'requirements.txt': `# Production dependencies starlette==0.37.2 uvicorn[standard]==0.30.1 python-multipart==0.0.9 jinja2==3.1.4 itsdangerous==2.2.0 sqlalchemy==2.0.30 alembic==1.13.1 asyncpg==0.29.0 databases==0.9.0 ariadne==0.23.0 python-jose[cryptography]==3.3.0 passlib[bcrypt]==1.7.4 python-dotenv==1.0.1 httpx==0.27.0 redis==5.0.3 aiofiles==23.2.1 # Development dependencies pytest==8.2.0 pytest-asyncio==0.23.7 pytest-cov==5.0.0 black==24.4.2 flake8==7.0.0 mypy==1.10.0 isort==5.13.2 pre-commit==3.7.1`, 'Dockerfile': `FROM python:3.11-slim # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ POETRY_VERSION=1.7.1 # Install system dependencies RUN apt-get update && apt-get install -y \ build-essential \ postgresql-client \ curl \ && rm -rf /var/lib/apt/lists/* # Set work directory WORKDIR /app # Install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy project files COP