@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
JavaScript
"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