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,598 lines (1,350 loc) • 60.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanicTemplate = void 0; exports.sanicTemplate = { id: 'sanic-py', name: 'Sanic + Python', displayName: 'Sanic + Python', description: 'Ultra-fast Sanic server with async/await support, blueprints, middleware, and high-performance async I/O optimized for speed', framework: 'sanic', language: 'python', version: '1.0.0', tags: ['sanic', 'python', 'async', 'ultra-fast', 'blueprints', 'middleware', 'high-performance'], dependencies: { sanic: '^23.12.1', 'sanic-ext': '^23.12.0', 'sanic-cors': '^2.2.0', 'asyncpg': '^0.29.0', 'asyncio-redis': '^0.16.0', 'pyjwt': '^2.8.0', 'bcrypt': '^4.1.2', 'python-dotenv': '^1.0.0', 'marshmallow': '^3.20.1', 'sqlalchemy': '^2.0.23', 'alembic': '^1.13.0', 'aiofiles': '^23.2.1', 'ujson': '^5.8.0', 'uvloop': '^0.19.0', 'httptools': '^0.6.1' }, devDependencies: { 'pytest': '^7.4.3', 'pytest-asyncio': '^0.21.1', 'pytest-sanic': '^1.9.1', 'pytest-cov': '^4.1.0', 'black': '^23.11.0', 'isort': '^5.12.0', 'mypy': '^1.7.1', 'flake8': '^6.1.0', 'pre-commit': '^3.6.0', 'sanic-testing': '^23.12.0' }, files: { 'requirements.txt': `sanic==23.12.1 sanic-ext==23.12.0 sanic-cors==2.2.0 asyncpg==0.29.0 asyncio-redis==0.16.0 pyjwt==2.8.0 bcrypt==4.1.2 python-dotenv==1.0.0 marshmallow==3.20.1 sqlalchemy==2.0.23 alembic==1.13.0 aiofiles==23.2.1 ujson==5.8.0 uvloop==0.19.0 httptools==0.6.1`, 'requirements-dev.txt': `pytest==7.4.3 pytest-asyncio==0.21.1 pytest-sanic==1.9.1 pytest-cov==4.1.0 black==23.11.0 isort==5.12.0 mypy==1.7.1 flake8==6.1.0 pre-commit==3.6.0 sanic-testing==23.12.0`, 'main.py': `#!/usr/bin/env python3 """ Sanic Application Entry Point """ import asyncio import sys from app.core.app_factory import create_app from app.core.config import settings from app.core.database import init_database, close_database from app.core.redis_client import init_redis, close_redis # Enable uvloop for better performance try: import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) except ImportError: pass def main(): """Main application entry point.""" try: # Create Sanic app app = create_app() # Setup startup and shutdown handlers @app.before_server_start async def setup_database(app, loop): """Initialize database connection pool.""" await init_database() await init_redis() print("šŸš€ Database and Redis connections established") @app.after_server_stop async def close_connections(app, loop): """Close database and Redis connections.""" await close_database() await close_redis() print("šŸ”Œ Connections closed gracefully") # Run the application app.run( host=settings.HOST, port=settings.PORT, debug=settings.DEBUG, auto_reload=settings.DEBUG, workers=settings.WORKERS if not settings.DEBUG else 1, access_log=settings.DEBUG, fast=True, # Enable fast mode for better performance motd=True # Show Sanic banner ) except KeyboardInterrupt: print("\\n⚔ Server shutdown requested by user") sys.exit(0) except Exception as e: print(f"āŒ Server startup failed: {e}") sys.exit(1) if __name__ == "__main__": main()`, 'app/__init__.py': '', 'app/core/__init__.py': '', 'app/core/config.py': `""" Application Configuration """ import os from typing import List from dotenv import load_dotenv load_dotenv() class Settings: """Application settings optimized for Sanic performance.""" # Server Configuration HOST: str = os.getenv("HOST", "0.0.0.0") PORT: int = int(os.getenv("PORT", 8000)) DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true" WORKERS: int = int(os.getenv("WORKERS", 1)) # Performance Settings KEEP_ALIVE_TIMEOUT: int = int(os.getenv("KEEP_ALIVE_TIMEOUT", 5)) REQUEST_TIMEOUT: int = int(os.getenv("REQUEST_TIMEOUT", 60)) REQUEST_MAX_SIZE: int = int(os.getenv("REQUEST_MAX_SIZE", 100000000)) # 100MB # Security SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-change-in-production") JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "jwt-secret-key-change-in-production") JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") JWT_EXPIRATION_DELTA: int = int(os.getenv("JWT_EXPIRATION_DELTA", 3600)) # Database DATABASE_URL: str = os.getenv( "DATABASE_URL", "postgresql://user:password@localhost:5432/sanic_app" ) DB_POOL_MIN_SIZE: int = int(os.getenv("DB_POOL_MIN_SIZE", 1)) DB_POOL_MAX_SIZE: int = int(os.getenv("DB_POOL_MAX_SIZE", 10)) # Redis REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0") REDIS_POOL_MIN_SIZE: int = int(os.getenv("REDIS_POOL_MIN_SIZE", 1)) REDIS_POOL_MAX_SIZE: int = int(os.getenv("REDIS_POOL_MAX_SIZE", 10)) # CORS CORS_ORIGINS: List[str] = os.getenv("CORS_ORIGINS", "*").split(",") CORS_METHODS: List[str] = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] CORS_HEADERS: List[str] = ["Content-Type", "Authorization", "X-Requested-With"] # Logging LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") # Rate Limiting RATE_LIMIT_ENABLED: bool = os.getenv("RATE_LIMIT_ENABLED", "true").lower() == "true" RATE_LIMIT_REQUESTS: int = int(os.getenv("RATE_LIMIT_REQUESTS", 100)) RATE_LIMIT_WINDOW: int = int(os.getenv("RATE_LIMIT_WINDOW", 60)) settings = Settings()`, 'app/core/app_factory.py': `""" Sanic Application Factory """ from sanic import Sanic from sanic.response import json from sanic_ext import Extend from sanic_cors import CORS from app.core.config import settings from app.blueprints.auth import auth_bp from app.blueprints.users import users_bp from app.blueprints.websocket import websocket_bp from app.blueprints.api import api_bp from app.middleware.authentication import add_authentication_middleware from app.middleware.rate_limiting import add_rate_limiting_middleware from app.middleware.error_handling import add_error_handling_middleware from app.middleware.logging import add_logging_middleware def create_app(test_config=None) -> Sanic: """Create and configure Sanic application.""" # Create Sanic app with optimized configuration app = Sanic( "SanicMicroservice", configure_logging=not settings.DEBUG, strict_slashes=False ) # Apply test configuration if provided if test_config: app.config.update(test_config) else: # Production configuration app.config.update({ 'SECRET_KEY': settings.SECRET_KEY, 'REQUEST_TIMEOUT': settings.REQUEST_TIMEOUT, 'REQUEST_MAX_SIZE': settings.REQUEST_MAX_SIZE, 'KEEP_ALIVE_TIMEOUT': settings.KEEP_ALIVE_TIMEOUT, 'GRACEFUL_SHUTDOWN_TIMEOUT': 15.0, 'ACCESS_LOG': settings.DEBUG, 'AUTO_RELOAD': settings.DEBUG, 'DEBUG': settings.DEBUG, }) # Enable Sanic Extensions for enhanced functionality Extend(app) # Configure CORS CORS( app, origins=settings.CORS_ORIGINS, methods=settings.CORS_METHODS, headers=settings.CORS_HEADERS, supports_credentials=True ) # Add middleware (order matters) add_logging_middleware(app) add_error_handling_middleware(app) add_authentication_middleware(app) if settings.RATE_LIMIT_ENABLED: add_rate_limiting_middleware(app) # Register blueprints app.blueprint(api_bp) app.blueprint(auth_bp) app.blueprint(users_bp) app.blueprint(websocket_bp) # Global health check endpoint @app.get("/health") async def health_check(request): """Global health check endpoint.""" return json({ "status": "healthy", "service": "sanic-microservice", "version": "1.0.0" }) # Add static files handling in development if settings.DEBUG: app.static("/static", "./static", name="static") return app`, 'app/core/database.py': `""" Database Configuration and Connection Pool """ import asyncio import asyncpg from typing import Optional from sqlalchemy import create_engine, MetaData from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool from app.core.config import settings # Database connection pool _connection_pool: Optional[asyncpg.pool.Pool] = None _engine = None _SessionLocal = None # SQLAlchemy Base Base = declarative_base() metadata = MetaData() async def init_database(): """Initialize asyncpg connection pool.""" global _connection_pool, _engine, _SessionLocal # Create asyncpg connection pool for high performance _connection_pool = await asyncpg.create_pool( dsn=settings.DATABASE_URL, min_size=settings.DB_POOL_MIN_SIZE, max_size=settings.DB_POOL_MAX_SIZE, command_timeout=60, server_settings={ 'application_name': 'sanic_microservice', } ) # Create SQLAlchemy engine for migrations and synchronous operations _engine = create_engine( settings.DATABASE_URL, poolclass=NullPool, echo=settings.DEBUG ) # Create session maker _SessionLocal = sessionmaker( autocommit=False, autoflush=False, bind=_engine ) print("šŸ“Š Database connection pool initialized") async def get_db_connection(): """Get database connection from pool.""" if _connection_pool is None: raise RuntimeError("Database not initialized") async with _connection_pool.acquire() as conn: yield conn def get_sync_db(): """Get synchronous database session for migrations.""" if _SessionLocal is None: raise RuntimeError("Database not initialized") db = _SessionLocal() try: yield db finally: db.close() async def execute_query(query: str, *args): """Execute a query using the connection pool.""" if _connection_pool is None: raise RuntimeError("Database not initialized") async with _connection_pool.acquire() as conn: return await conn.fetch(query, *args) async def execute_one(query: str, *args): """Execute a query and return one result.""" if _connection_pool is None: raise RuntimeError("Database not initialized") async with _connection_pool.acquire() as conn: return await conn.fetchrow(query, *args) async def execute_command(query: str, *args): """Execute a command (INSERT, UPDATE, DELETE).""" if _connection_pool is None: raise RuntimeError("Database not initialized") async with _connection_pool.acquire() as conn: return await conn.execute(query, *args) async def close_database(): """Close database connection pool.""" global _connection_pool if _connection_pool: await _connection_pool.close() _connection_pool = None print("šŸ“Š Database connection pool closed")`, 'app/core/redis_client.py': `""" Redis Client Configuration with Connection Pool """ import asyncio_redis from typing import Optional from app.core.config import settings # Redis connection pool _redis_pool: Optional[asyncio_redis.Pool] = None async def init_redis(): """Initialize Redis connection pool.""" global _redis_pool _redis_pool = await asyncio_redis.Pool.create( host=settings.REDIS_URL.split('://')[1].split(':')[0], port=int(settings.REDIS_URL.split(':')[-1].split('/')[0]), db=int(settings.REDIS_URL.split('/')[-1]), poolsize=settings.REDIS_POOL_MAX_SIZE, encoder=asyncio_redis.encoders.UTF8Encoder() ) # Test connection async with _redis_pool.get() as redis: await redis.ping() print("šŸ”“ Redis connection pool initialized") async def get_redis(): """Get Redis connection from pool.""" if _redis_pool is None: raise RuntimeError("Redis not initialized") return _redis_pool.get() async def set_cache(key: str, value: str, expire: int = None): """Set cache value with optional expiration.""" async with get_redis() as redis: await redis.set(key, value) if expire: await redis.expire(key, expire) async def get_cache(key: str) -> str: """Get cache value.""" async with get_redis() as redis: return await redis.get(key) async def delete_cache(key: str): """Delete cache key.""" async with get_redis() as redis: await redis.delete([key]) async def close_redis(): """Close Redis connection pool.""" global _redis_pool if _redis_pool: _redis_pool.close() _redis_pool = None print("šŸ”“ Redis connection pool closed")`, 'app/blueprints/__init__.py': '', 'app/blueprints/auth.py': `""" Authentication Blueprint """ import bcrypt import jwt from datetime import datetime, timedelta from sanic import Blueprint from sanic.response import json from sanic.exceptions import Unauthorized, BadRequest from marshmallow import Schema, fields, ValidationError from app.core.config import settings from app.core.redis_client import set_cache, get_cache, delete_cache from app.models.user import User, UserCreateSchema, UserLoginSchema auth_bp = Blueprint("auth", prefix="/api/v1/auth") @auth_bp.post("/register") async def register(request): """Register a new user.""" try: # Validate request data schema = UserCreateSchema() try: data = schema.load(request.json or {}) except ValidationError as e: return json({"error": "Validation error", "details": e.messages}, status=400) # Check if user already exists (simulated) email = data['email'] existing_user = await get_cache(f"user:email:{email}") if existing_user: return json({"error": "User already exists"}, status=409) # Hash password password_hash = bcrypt.hashpw( data['password'].encode('utf-8'), bcrypt.gensalt() ).decode('utf-8') # Create user (simulate database storage with Redis) user_id = 1 # In real app, get from database user_data = { "id": user_id, "email": email, "username": data.get('username', email.split('@')[0]), "is_active": True, "created_at": datetime.utcnow().isoformat() } # Store user in cache await set_cache(f"user:{user_id}", str(user_data), expire=3600) await set_cache(f"user:email:{email}", str(user_id), expire=3600) # Generate JWT token token_payload = { "user_id": user_id, "email": email, "exp": datetime.utcnow() + timedelta(seconds=settings.JWT_EXPIRATION_DELTA) } access_token = jwt.encode( token_payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM ) return json({ "message": "User registered successfully", "user": { "id": user_id, "email": email, "username": user_data['username'] }, "access_token": access_token, "token_type": "bearer" }, status=201) except Exception as e: return json({"error": f"Registration failed: {str(e)}"}, status=500) @auth_bp.post("/login") async def login(request): """Authenticate user and return JWT token.""" try: # Validate request data schema = UserLoginSchema() try: data = schema.load(request.json or {}) except ValidationError as e: return json({"error": "Validation error", "details": e.messages}, status=400) email = data['email'] password = data['password'] # Simulate user lookup and password verification user_data = { "id": 1, "email": email, "username": email.split('@')[0], "is_active": True } # Generate JWT token token_payload = { "user_id": user_data['id'], "email": user_data['email'], "exp": datetime.utcnow() + timedelta(seconds=settings.JWT_EXPIRATION_DELTA) } access_token = jwt.encode( token_payload, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM ) return json({ "message": "Login successful", "user": user_data, "access_token": access_token, "token_type": "bearer" }) except Exception as e: return json({"error": f"Login failed: {str(e)}"}, status=500) @auth_bp.post("/logout") async def logout(request): """Logout user by blacklisting JWT token.""" try: # Get user from middleware user = getattr(request.ctx, 'user', None) if not user: raise Unauthorized("Authentication required") # Get token from authorization header auth_header = request.headers.get("authorization", "") if not auth_header.startswith("Bearer "): raise Unauthorized("Invalid authorization header") token = auth_header.split(" ")[1] # Blacklist token in Redis await set_cache( f"blacklist:{token}", "true", expire=settings.JWT_EXPIRATION_DELTA ) return json({"message": "Logout successful"}) except Exception as e: return json({"error": f"Logout failed: {str(e)}"}, status=500) @auth_bp.get("/profile") async def get_profile(request): """Get current user profile.""" try: # Get user from middleware user = getattr(request.ctx, 'user', None) if not user: raise Unauthorized("Authentication required") return json({"user": user}) except Exception as e: return json({"error": f"Profile fetch failed: {str(e)}"}, status=500)`, 'app/blueprints/users.py': `""" Users Management Blueprint """ from sanic import Blueprint from sanic.response import json from sanic.exceptions import Unauthorized, NotFound from app.core.redis_client import get_cache, set_cache users_bp = Blueprint("users", prefix="/api/v1/users") @users_bp.get("/") async def list_users(request): """List all users with pagination.""" try: # Get user from middleware user = getattr(request.ctx, 'user', None) if not user: raise Unauthorized("Authentication required") # Get pagination parameters page = int(request.args.get('page', 1)) limit = int(request.args.get('limit', 10)) # Simulate user list users = [ { "id": i, "email": f"user{i}@example.com", "username": f"user{i}", "is_active": True } for i in range((page-1)*limit + 1, page*limit + 1) ] return json({ "users": users, "pagination": { "page": page, "limit": limit, "total": 100, # Simulated total "pages": 10 } }) except Exception as e: return json({"error": f"Failed to list users: {str(e)}"}, status=500) @users_bp.get("/<user_id:int>") async def get_user(request, user_id: int): """Get specific user by ID.""" try: # Get current user from middleware current_user = getattr(request.ctx, 'user', None) if not current_user: raise Unauthorized("Authentication required") # Simulate user lookup user_data = { "id": user_id, "email": f"user{user_id}@example.com", "username": f"user{user_id}", "is_active": True, "profile": { "bio": "Sanic developer", "location": "San Francisco, CA" } } return json({"user": user_data}) except Exception as e: return json({"error": f"Failed to get user: {str(e)}"}, status=500) @users_bp.put("/<user_id:int>") async def update_user(request, user_id: int): """Update user information.""" try: # Get current user from middleware current_user = getattr(request.ctx, 'user', None) if not current_user: raise Unauthorized("Authentication required") # Check if user can update this profile if current_user['user_id'] != user_id: return json({"error": "Permission denied"}, status=403) data = request.json or {} # Update user data (simulate database operation) updated_user = { "id": user_id, "email": data.get('email', current_user['email']), "username": data.get('username', f"user{user_id}"), "is_active": True, "updated_at": "2024-01-01T00:00:00Z" } return json({ "message": "User updated successfully", "user": updated_user }) except Exception as e: return json({"error": f"Failed to update user: {str(e)}"}, status=500) @users_bp.delete("/<user_id:int>") async def delete_user(request, user_id: int): """Delete user account.""" try: # Get current user from middleware current_user = getattr(request.ctx, 'user', None) if not current_user: raise Unauthorized("Authentication required") # Check if user can delete this profile if current_user['user_id'] != user_id: return json({"error": "Permission denied"}, status=403) # Delete user (simulate database operation) return json({"message": "User deleted successfully"}) except Exception as e: return json({"error": f"Failed to delete user: {str(e)}"}, status=500)`, 'app/blueprints/websocket.py': `""" WebSocket Blueprint for Real-time Communication """ import json import asyncio from datetime import datetime from sanic import Blueprint from sanic.exceptions import Unauthorized from app.core.redis_client import set_cache, get_cache websocket_bp = Blueprint("websocket") # Store active WebSocket connections active_connections = { 'chat': set(), 'notifications': set() } @websocket_bp.websocket("/ws/chat") async def chat_websocket(request, ws): """WebSocket endpoint for chat functionality.""" user_id = None room_id = None try: # Get user token from query parameters token = request.args.get('token') room_id = request.args.get('room', 'general') if not token: await ws.close(code=1000, reason="Authentication required") return # In a real app, you'd validate the JWT token user_id = 1 # Simulated user ID # Add to active connections active_connections['chat'].add(ws) # Send welcome message await ws.send(json.dumps({ 'type': 'system', 'message': f'Welcome to chat room {room_id}', 'room_id': room_id, 'timestamp': datetime.utcnow().isoformat() })) # Broadcast user joined await broadcast_to_room('chat', { 'type': 'user_joined', 'user_id': user_id, 'room_id': room_id, 'message': f'User {user_id} joined the room', 'timestamp': datetime.utcnow().isoformat() }, exclude=ws) # Handle incoming messages async for message in ws: try: data = json.loads(message) message_type = data.get('type', 'chat') if message_type == 'chat': # Handle chat message chat_message = { 'type': 'chat', 'id': f"msg_{datetime.utcnow().timestamp()}", 'user_id': user_id, 'room_id': room_id, 'content': data.get('content', ''), 'timestamp': datetime.utcnow().isoformat() } # Store message in Redis await set_cache( f"chat:room:{room_id}:latest", json.dumps(chat_message), expire=3600 ) # Broadcast to all users in room await broadcast_to_room('chat', chat_message) elif message_type == 'typing': # Handle typing indicator typing_data = { 'type': 'typing', 'user_id': user_id, 'room_id': room_id, 'is_typing': data.get('is_typing', False), 'timestamp': datetime.utcnow().isoformat() } await broadcast_to_room('chat', typing_data, exclude=ws) elif message_type == 'ping': # Handle ping/pong await ws.send(json.dumps({ 'type': 'pong', 'timestamp': datetime.utcnow().isoformat() })) except json.JSONDecodeError: await ws.send(json.dumps({ 'type': 'error', 'message': 'Invalid JSON message' })) except Exception as e: await ws.send(json.dumps({ 'type': 'error', 'message': f'Message handling error: {str(e)}' })) except Exception as e: print(f"WebSocket error: {e}") finally: # Clean up connection if ws in active_connections['chat']: active_connections['chat'].remove(ws) if user_id and room_id: # Broadcast user left await broadcast_to_room('chat', { 'type': 'user_left', 'user_id': user_id, 'room_id': room_id, 'message': f'User {user_id} left the room', 'timestamp': datetime.utcnow().isoformat() }) @websocket_bp.websocket("/ws/notifications") async def notifications_websocket(request, ws): """WebSocket endpoint for real-time notifications.""" user_id = None try: # Get user token from query parameters token = request.args.get('token') if not token: await ws.close(code=1000, reason="Authentication required") return # In a real app, you'd validate the JWT token user_id = 1 # Simulated user ID # Add to active connections active_connections['notifications'].add(ws) # Send welcome notification await ws.send(json.dumps({ 'type': 'welcome', 'title': 'Connected', 'message': 'You are now connected to real-time notifications', 'priority': 'info', 'timestamp': datetime.utcnow().isoformat() })) # Handle incoming messages async for message in ws: try: data = json.loads(message) message_type = data.get('type', 'ping') if message_type == 'ping': await ws.send(json.dumps({ 'type': 'pong', 'timestamp': datetime.utcnow().isoformat() })) elif message_type == 'subscribe': # Handle subscription to notification types topics = data.get('topics', []) await ws.send(json.dumps({ 'type': 'subscription_updated', 'message': f"Subscribed to {topics}", 'topics': topics, 'timestamp': datetime.utcnow().isoformat() })) except json.JSONDecodeError: await ws.send(json.dumps({ 'type': 'error', 'message': 'Invalid JSON message' })) except Exception as e: print(f"Notifications WebSocket error: {e}") finally: # Clean up connection if ws in active_connections['notifications']: active_connections['notifications'].remove(ws) async def broadcast_to_room(connection_type: str, message_data: dict, exclude=None): """Broadcast message to all connections in a room.""" if connection_type not in active_connections: return message = json.dumps(message_data) dead_connections = set() for connection in active_connections[connection_type]: if exclude and connection == exclude: continue try: await connection.send(message) except Exception: # Mark dead connections for removal dead_connections.add(connection) # Remove dead connections active_connections[connection_type] -= dead_connections async def send_notification_to_all(notification_data: dict): """Send notification to all connected users.""" await broadcast_to_room('notifications', notification_data)`, 'app/blueprints/api.py': `""" General API Blueprint """ import psutil import time from datetime import datetime from sanic import Blueprint from sanic.response import json from app.core.config import settings from app.core.redis_client import get_cache from app.blueprints.websocket import active_connections api_bp = Blueprint("api", prefix="/api/v1") @api_bp.get("/status") async def get_status(request): """Get detailed application status.""" try: # WebSocket connections count websocket_stats = { "chat_connections": len(active_connections.get('chat', set())), "notification_connections": len(active_connections.get('notifications', set())), "total_connections": sum(len(conns) for conns in active_connections.values()) } # Redis stats redis_stats = {} try: # Test Redis connection async with get_cache('test_key') as result: redis_stats = { "status": "healthy", "connected": True } except Exception: redis_stats = { "status": "unhealthy", "connected": False } # System metrics system_metrics = { "cpu_percent": psutil.cpu_percent(), "memory_percent": psutil.virtual_memory().percent, "disk_percent": psutil.disk_usage('/').percent, "load_average": psutil.getloadavg() if hasattr(psutil, 'getloadavg') else [0, 0, 0] } status_data = { "application": { "name": "Sanic Microservice", "version": "1.0.0", "framework": "Sanic", "environment": "development" if settings.DEBUG else "production", "debug_mode": settings.DEBUG }, "server": { "host": settings.HOST, "port": settings.PORT, "workers": settings.WORKERS, "uptime_seconds": time.time() # In real app, track actual uptime }, "performance": { "request_timeout": settings.REQUEST_TIMEOUT, "keep_alive_timeout": settings.KEEP_ALIVE_TIMEOUT, "max_request_size": settings.REQUEST_MAX_SIZE }, "websockets": websocket_stats, "redis": redis_stats, "system": system_metrics, "timestamp": datetime.utcnow().isoformat() } return json(status_data) except Exception as e: return json({"error": f"Failed to get status: {str(e)}"}, status=500) @api_bp.get("/metrics") async def get_metrics(request): """Get application metrics in Prometheus format.""" try: metrics = [] # WebSocket metrics for conn_type, connections in active_connections.items(): metrics.append(f'websocket_connections{{type="{conn_type}"}} {len(connections)}') # System metrics metrics.extend([ f'system_cpu_percent {psutil.cpu_percent()}', f'system_memory_percent {psutil.virtual_memory().percent}', f'system_disk_percent {psutil.disk_usage("/").percent}' ]) return "\\n".join(metrics), 200, {'Content-Type': 'text/plain'} except Exception as e: return json({"error": f"Failed to get metrics: {str(e)}"}, status=500) @api_bp.get("/info") async def get_info(request): """Get basic application information.""" return json({ "name": "Sanic Microservice", "version": "1.0.0", "framework": "Sanic", "language": "Python", "features": [ "Ultra-fast async performance", "Blueprint architecture", "JWT authentication", "WebSocket support", "Redis caching", "PostgreSQL integration", "Rate limiting", "CORS support", "Comprehensive testing" ], "endpoints": { "auth": "/api/v1/auth/*", "users": "/api/v1/users/*", "websockets": "/ws/*", "health": "/health", "status": "/api/v1/status", "metrics": "/api/v1/metrics" } })`, 'app/middleware/__init__.py': '', 'app/middleware/authentication.py': `""" Authentication Middleware """ import jwt from sanic.exceptions import Unauthorized from app.core.config import settings from app.core.redis_client import get_cache def add_authentication_middleware(app): """Add JWT authentication middleware.""" # Define routes that don't require authentication public_routes = { '/health', '/api/v1/auth/login', '/api/v1/auth/register', '/api/v1/info', '/api/v1/status', '/api/v1/metrics' } @app.middleware("request") async def authenticate_request(request): """Authenticate requests using JWT tokens.""" # Skip authentication for public routes if request.path in public_routes or request.path.startswith('/static'): return # Skip authentication for WebSocket upgrade requests if request.headers.get('upgrade', '').lower() == 'websocket': return # Get authorization header auth_header = request.headers.get("authorization", "") if not auth_header.startswith("Bearer "): raise Unauthorized("Missing or invalid authorization header") try: # Extract token token = auth_header.split(" ")[1] # Check if token is blacklisted blacklisted = await get_cache(f"blacklist:{token}") if blacklisted: raise Unauthorized("Token has been revoked") # Decode JWT token payload = jwt.decode( token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM] ) # Add user info to request context request.ctx.user = payload except jwt.ExpiredSignatureError: raise Unauthorized("Token has expired") except jwt.InvalidTokenError: raise Unauthorized("Invalid token") except Exception as e: raise Unauthorized(f"Authentication failed: {str(e)}")`, 'app/middleware/rate_limiting.py': `""" Rate Limiting Middleware """ import time from sanic.exceptions import TooManyRequests from app.core.config import settings from app.core.redis_client import get_cache, set_cache def add_rate_limiting_middleware(app): """Add rate limiting middleware.""" @app.middleware("request") async def rate_limit_request(request): """Rate limit requests based on IP address.""" if not settings.RATE_LIMIT_ENABLED: return # Get client IP client_ip = request.ip # Skip rate limiting for health checks if request.path == '/health': return # Create rate limit key now = int(time.time()) window_start = now - (now % settings.RATE_LIMIT_WINDOW) rate_limit_key = f"rate_limit:{client_ip}:{window_start}" try: # Get current request count current_count = await get_cache(rate_limit_key) current_count = int(current_count) if current_count else 0 # Check if limit exceeded if current_count >= settings.RATE_LIMIT_REQUESTS: raise TooManyRequests( f"Rate limit exceeded. Max {settings.RATE_LIMIT_REQUESTS} " f"requests per {settings.RATE_LIMIT_WINDOW} seconds." ) # Increment request count await set_cache( rate_limit_key, str(current_count + 1), expire=settings.RATE_LIMIT_WINDOW ) except TooManyRequests: raise except Exception as e: # Log error but don't block request print(f"Rate limiting error: {e}")`, 'app/middleware/error_handling.py': `""" Error Handling Middleware """ import traceback from sanic.response import json from sanic.exceptions import SanicException def add_error_handling_middleware(app): """Add global error handling middleware.""" @app.exception(Exception) async def handle_exception(request, exception): """Handle all exceptions globally.""" # Handle Sanic exceptions (they have proper status codes) if isinstance(exception, SanicException): return json({ "error": exception.__class__.__name__, "message": str(exception), "status_code": exception.status_code }, status=exception.status_code) # Handle other exceptions error_id = f"error_{int(time.time())}" error_response = { "error": "Internal Server Error", "message": "An unexpected error occurred", "error_id": error_id, "status_code": 500 } # Add detailed error info in debug mode if app.config.get('DEBUG', False): error_response.update({ "exception_type": exception.__class__.__name__, "exception_message": str(exception), "traceback": traceback.format_exc() }) # Log the error print(f"Error {error_id}: {exception}") if app.config.get('DEBUG', False): print(traceback.format_exc()) return json(error_response, status=500)`, 'app/middleware/logging.py': `""" Logging Middleware """ import time from sanic.response import json def add_logging_middleware(app): """Add request/response logging middleware.""" @app.middleware("request") async def log_request(request): """Log incoming requests.""" request.ctx.start_time = time.time() if app.config.get('DEBUG', False): print(f"šŸ“„ {request.method} {request.path} - {request.ip}") @app.middleware("response") async def log_response(request, response): """Log outgoing responses.""" # Calculate request duration start_time = getattr(request.ctx, 'start_time', time.time()) duration = (time.time() - start_time) * 1000 # Convert to milliseconds # Add performance headers response.headers['X-Response-Time'] = f"{duration:.2f}ms" response.headers['X-Powered-By'] = 'Sanic' if app.config.get('DEBUG', False): status_emoji = "āœ…" if response.status < 400 else "āŒ" print(f"{status_emoji} {request.method} {request.path} - {response.status} - {duration:.2f}ms")`, 'app/models/__init__.py': '', 'app/models/user.py': `""" User Models and Schemas """ from marshmallow import Schema, fields, validate from typing import Dict, Any class UserCreateSchema(Schema): """Schema for user creation.""" email = fields.Email(required=True) password = fields.Str( required=True, validate=validate.Length(min=8, max=128) ) username = fields.Str( validate=validate.Length(min=3, max=50), missing=None ) class Meta: unknown = 'EXCLUDE' class UserLoginSchema(Schema): """Schema for user login.""" email = fields.Email(required=True) password = fields.Str(required=True) class Meta: unknown = 'EXCLUDE' class UserUpdateSchema(Schema): """Schema for user updates.""" email = fields.Email() username = fields.Str(validate=validate.Length(min=3, max=50)) bio = fields.Str(validate=validate.Length(max=500)) location = fields.Str(validate=validate.Length(max=100)) website = fields.Url() class Meta: unknown = 'EXCLUDE' class User: """User model (simplified for template).""" def __init__(self, email: str, username: str = None, password_hash: str = None): self.email = email self.username = username or email.split('@')[0] self.password_hash = password_hash self.is_active = True self.created_at = None self.updated_at = None def to_dict(self) -> Dict[str, Any]: """Convert user to dictionary.""" return { 'email': self.email, 'username': self.username, 'is_active': self.is_active, 'created_at': self.created_at, 'updated_at': self.updated_at } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'User': """Create user from dictionary.""" user = cls( email=data['email'], username=data.get('username'), password_hash=data.get('password_hash') ) user.is_active = data.get('is_active', True) user.created_at = data.get('created_at') user.updated_at = data.get('updated_at') return user`, 'Dockerfile': `FROM python:3.11-slim WORKDIR /app # Install system dependencies RUN apt-get update && apt-get install -y \\ gcc \\ postgresql-client \\ && rm -rf /var/lib/apt/lists/* # Copy requirements COPY requirements.txt requirements-dev.txt ./ # Install Python dependencies RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . # Create non-root user RUN useradd -m -u 1000 sanic && chown -R sanic:sanic /app USER sanic # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c "import asyncio; import aiohttp; asyncio.run(aiohttp.ClientSession().get('http://localhost:8000/health').close())" # Run application CMD ["python", "main.py"]`, 'docker-compose.yml': `version: '3.8' services: sanic-app: build: . ports: - "8000:8000" environment: - DEBUG=true - HOST=0.0.0.0 - PORT=8000 - WORKERS=1 - DATABASE_URL=postgresql://postgres:password@postgres:5432/sanic_app - REDIS_URL=redis://redis:6379/0 - SECRET_KEY=your-secret-key-change-in-production - JWT_SECRET_KEY=jwt-secret-key-change-in-production depends_on: - postgres - redis volumes: - .:/app command: python main.py postgres: image: postgres:15-alpine environment: - POSTGRES_DB=sanic_app - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - "6379:6379" command: redis-server --appendonly yes volumes: - redis_data:/data volumes: postgres_data: redis_data:`, '.env.example': `# Server Configuration HOST=0.0.0.0 PORT=8000 DEBUG=true WORKERS=1 # Performance Settings KEEP_ALIVE_TIMEOUT=5 REQUEST_TIMEOUT=60 REQUEST_MAX_SIZE=100000000 # Security SECRET_KEY=your-secret-key-change-in-production JWT_SECRET_KEY=jwt-secret-key-change-in-production JWT_ALGORITHM=HS256 JWT_EXPIRATION_DELTA=3600 # Database DATABASE_URL=postgresql://user:password@localhost:5432/sanic_app DB_POOL_MIN_SIZE=1 DB_POOL_MAX_SIZE=10 # Redis REDIS_URL=redis://localhost:6379/0 REDIS_POOL_MIN_SIZE=1 REDIS_POOL_MAX_SIZE=10 # CORS CORS_ORIGINS=http://localhost:3000,http://localhost:8080 # Rate Limiting RATE_LIMIT_ENABLED=true RATE_LIMIT_REQUESTS=100 RATE_LIMIT_WINDOW=60 # Logging LOG_LEVEL=INFO`, 'pytest.ini': `[tool:pytest] asyncio_mode = auto testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = --verbose --tb=short --cov=app --cov-report=term-missing --cov-report=html:htmlcov markers = unit: Unit tests integration: Integration tests websocket: WebSocket tests performance: Performance tests`, 'tests/__init__.py': '', 'tests/conftest.py': `""" Pytest configuration and fixtures. """ import pytest import asyncio from sanic_testing import TestClient from app.core.app_factory import create_app @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 async def app(): """Create test application.""" test_config = { 'TESTING': True, 'DEBUG': True, 'SECRET_KEY': 'test-secret-key' } return create_app(test_config=test_config) @pytest.fixture async def client(app): """Create test client.""" return TestClient(app)`, 'tests/test_auth.py': `""" Authentication tests. """ import pytest import json @pytest.mark.asyncio async def test_register(client): """Test user registration.""" data = { "email": "test@example.com", "password": "testpassword123", "username": "testuser" } request, response = await client.post( '/api/v1/auth/register', json=data ) assert response.status == 201 response_data = response.json assert 'access_token' in response_data assert response_data['user']['email'] == 'test@example.com' @pytest.mark.asyncio async def test_login(client): """Test user login.""" data = { "email": "test@example.com", "password": "testpassword123" } request, response = await client.post( '/api/v1/auth/login', json=data ) assert response.status == 200 response_data = response.json assert 'access_token' in response_data assert response_data['user']['email'] == 'test@example.com' @pytest.mark.asyncio async def test_login_validation_error(client): """Test login with validation error.""" data = { "email": "invalid-email", "password": "short" } request, response = await client.post( '/api/v1/auth/login', json=data ) assert response.status == 400 response_data = response.json assert 'error' in response_data assert 'Validation error' in response_data['error'] @pytest.mark.asyncio async def test_logout_without_auth(client): """Test logout without authentication.""" request, response = await client.post('/api/v1/auth/logout') assert response.status == 401`, 'tests/test_users.py': `""" User management tests. """ import pytest @pytest.mark.asyncio async def test_list_users_without_auth(client): """Test listing users without authentication.""" request, response = await client.get('/api/v1/users/') assert response.status == 401 @pytest.mark.asyncio async def test_get_user_without_auth(client): """Test getting user without authentication.""" request, response = await client.get('/api/v1/users/1') assert response.status == 401 @pytest.mark.asyncio async def test_update_user_without_auth(client): """Test updating user without authentication.""" data = {"username": "newusername"} request, response = await client.put( '/api/v