# Blueprint System Guide The Gobstopper Blueprint system provides a Flask-like modular structure for organizing routes, middleware, and application logic into reusable components. Blueprints help you build large applications with clean separation of concerns. ## Table of Contents - [Introduction](#introduction) - [Basic Usage](#basic-usage) - [Blueprint Registration](#blueprint-registration) - [URL Prefixes](#url-prefixes) - [Nested Blueprints](#nested-blueprints) - [Hooks and Middleware](#hooks-and-middleware) - [Static Files and Templates](#static-files-and-templates) - [Complete Examples](#complete-examples) - [Best Practices](#best-practices) - [Migration from Flask](#migration-from-flask) ## Introduction Blueprints allow you to: - **Organize routes** into logical modules - **Reuse components** across multiple applications - **Separate concerns** with clean boundaries - **Scale applications** without monolithic route files - **Team collaboration** with independent modules ### When to Use Blueprints Use blueprints when: - Your application has distinct feature areas (users, posts, admin) - You want to reuse functionality across projects - Multiple developers are working on different features - You need to organize routes for better maintainability ## Basic Usage ### Creating a Blueprint ```python from gobstopper.core.blueprint import Blueprint # Create a blueprint users_bp = Blueprint('users', url_prefix='/users') @users_bp.get('/') async def list_users(request): return JSONResponse({'users': []}) @users_bp.get('/') async def get_user(request, user_id: int): return JSONResponse({'user_id': user_id}) @users_bp.post('/') async def create_user(request): data = await request.json() return JSONResponse(data, status=201) ``` ### Registering with Application ```python from gobstopper import Gobstopper from gobstopper.http.response import JSONResponse app = Gobstopper(__name__) # Register blueprint app.register_blueprint(users_bp) # Routes are now available at: # GET /users/ # GET /users/ # POST /users/ ``` ## Blueprint Registration ### Basic Registration ```python # Create blueprint api_bp = Blueprint('api') @api_bp.get('/health') async def health_check(request): return JSONResponse({'status': 'healthy'}) # Register with app app = Gobstopper(__name__) app.register_blueprint(api_bp) ``` ### Registration with URL Prefix ```python # No prefix in blueprint definition auth_bp = Blueprint('auth') @auth_bp.post('/login') async def login(request): return JSONResponse({'token': 'abc123'}) # Add prefix during registration app.register_blueprint(auth_bp, url_prefix='/auth') # Route available at: POST /auth/login ``` ### Multiple Registrations You can register the same blueprint multiple times with different prefixes: ```python # API blueprint api_bp = Blueprint('api') @api_bp.get('/data') async def get_data(request): return JSONResponse({'data': []}) # Register for different API versions app.register_blueprint(api_bp, url_prefix='/v1/api') app.register_blueprint(api_bp, url_prefix='/v2/api') # Routes available at: # GET /v1/api/data # GET /v2/api/data ``` ## URL Prefixes ### Blueprint-Level Prefix Define prefix when creating blueprint: ```python admin_bp = Blueprint('admin', url_prefix='/admin') @admin_bp.get('/users') async def admin_users(request): return JSONResponse({'users': []}) app.register_blueprint(admin_bp) # Route: GET /admin/users ``` ### Registration-Level Prefix Override or add prefix during registration: ```python blog_bp = Blueprint('blog') @blog_bp.get('/posts') async def list_posts(request): return JSONResponse({'posts': []}) # Register with prefix app.register_blueprint(blog_bp, url_prefix='/blog') # Route: GET /blog/posts # Or register without prefix app.register_blueprint(blog_bp) # Route: GET /posts ``` ### Combined Prefixes Both blueprint and registration prefixes are combined: ```python api_bp = Blueprint('api', url_prefix='/api') @api_bp.get('/data') async def get_data(request): return JSONResponse({'data': []}) app.register_blueprint(api_bp, url_prefix='/v1') # Final route: GET /v1/api/data ``` ## Nested Blueprints Blueprints can contain other blueprints for hierarchical organization: ```python # Parent blueprint: API api_bp = Blueprint('api', url_prefix='/api') # Child blueprint: Users users_bp = Blueprint('users', url_prefix='/users') @users_bp.get('/') async def list_users(request): return JSONResponse({'users': []}) @users_bp.get('/') async def get_user(request, user_id: int): return JSONResponse({'user': user_id}) # Child blueprint: Posts posts_bp = Blueprint('posts', url_prefix='/posts') @posts_bp.get('/') async def list_posts(request): return JSONResponse({'posts': []}) # Register children with parent api_bp.register_blueprint(users_bp) api_bp.register_blueprint(posts_bp) # Register parent with app app = Gobstopper(__name__) app.register_blueprint(api_bp) # Final routes: # GET /api/users/ # GET /api/users/ # GET /api/posts/ ``` ### Complex Nesting Example ```python # Root: Admin section admin_bp = Blueprint('admin', url_prefix='/admin') # Level 1: Settings settings_bp = Blueprint('settings', url_prefix='/settings') @settings_bp.get('/') async def list_settings(request): return JSONResponse({'settings': []}) # Level 2: User settings user_settings_bp = Blueprint('user_settings', url_prefix='/user') @user_settings_bp.get('/') async def user_settings(request): return JSONResponse({'user_settings': {}}) # Build hierarchy settings_bp.register_blueprint(user_settings_bp) admin_bp.register_blueprint(settings_bp) app.register_blueprint(admin_bp) # Final route: GET /admin/settings/user/ ``` ## Hooks and Middleware ### Before Request Hooks Execute code before handling requests: ```python auth_bp = Blueprint('auth', url_prefix='/auth') @auth_bp.before_request async def check_auth(request): """Run before every request in this blueprint.""" token = request.headers.get('authorization') if not token: return JSONResponse({'error': 'Unauthorized'}, status=401) # Verify token... request.state.user_id = decode_token(token) @auth_bp.get('/profile') async def get_profile(request): user_id = request.state.user_id return JSONResponse({'user_id': user_id}) ``` ### After Request Hooks Modify responses after handling: ```python api_bp = Blueprint('api', url_prefix='/api') @api_bp.after_request async def add_headers(request, response): """Run after every request in this blueprint.""" response.headers['X-API-Version'] = '1.0' response.headers['X-Processed-By'] = 'Gobstopper' return response @api_bp.get('/data') async def get_data(request): return JSONResponse({'data': []}) ``` ### Blueprint Middleware Add middleware specific to a blueprint: ```python from gobstopper.middleware import CORSMiddleware api_bp = Blueprint('api', url_prefix='/api') # Add CORS only to API routes cors_middleware = CORSMiddleware( allow_origins=['https://example.com'], allow_methods=['GET', 'POST'] ) api_bp.add_middleware(cors_middleware, priority=100) @api_bp.get('/data') async def get_data(request): return JSONResponse({'data': []}) ``` ### Route-Level Middleware Apply middleware to specific routes within a blueprint: ```python from gobstopper.utils.rate_limiter import TokenBucketLimiter, rate_limit users_bp = Blueprint('users', url_prefix='/users') # Rate limiter for specific routes limiter = TokenBucketLimiter(rate=10, capacity=100) @users_bp.get('/') @rate_limit(limiter) # Only this route is rate limited async def list_users(request): return JSONResponse({'users': []}) @users_bp.get('/') # This route is NOT rate limited async def get_user(request, user_id: int): return JSONResponse({'user': user_id}) ``` ## Static Files and Templates ### Blueprint Static Files Serve static files specific to a blueprint: ```python admin_bp = Blueprint( 'admin', url_prefix='/admin', static_folder='admin/static', template_folder='admin/templates' ) # Static files served from: admin/static/ # Templates loaded from: admin/templates/ ``` **Directory structure:** ``` project/ ├── app.py └── admin/ ├── static/ │ ├── css/ │ │ └── admin.css │ └── js/ │ └── admin.js └── templates/ ├── dashboard.html └── users.html ``` ### Blueprint Templates Use templates from blueprint's template folder: ```python admin_bp = Blueprint( 'admin', url_prefix='/admin', template_folder='admin/templates' ) @admin_bp.get('/dashboard') async def dashboard(request): # Looks for template in: admin/templates/dashboard.html return await request.app.render_template('dashboard.html', data={}) ``` **Note:** Current implementation uses global template paths. Full blueprint template namespacing is planned for future releases. ## Complete Examples ### Blog Application ```python # blog.py from gobstopper.core.blueprint import Blueprint from gobstopper.http.response import JSONResponse blog_bp = Blueprint('blog', url_prefix='/blog') # List posts @blog_bp.get('/posts') async def list_posts(request): limit = int(request.args.get('limit', 10)) posts = await fetch_posts(limit) return JSONResponse({'posts': posts}) # Get single post @blog_bp.get('/posts/') async def get_post(request, post_id: int): post = await fetch_post(post_id) if not post: return JSONResponse({'error': 'Not found'}, status=404) return JSONResponse({'post': post}) # Create post @blog_bp.post('/posts') async def create_post(request): data = await request.json() post = await create_post_in_db(data) return JSONResponse({'post': post}, status=201) # Update post @blog_bp.put('/posts/') async def update_post(request, post_id: int): data = await request.json() post = await update_post_in_db(post_id, data) return JSONResponse({'post': post}) # Delete post @blog_bp.delete('/posts/') async def delete_post(request, post_id: int): await delete_post_from_db(post_id) return JSONResponse({}, status=204) # Comments sub-blueprint comments_bp = Blueprint('comments', url_prefix='/comments') @comments_bp.get('/') async def list_comments(request): post_id = request.args.get('post_id') comments = await fetch_comments(post_id) return JSONResponse({'comments': comments}) @comments_bp.post('/') async def create_comment(request): data = await request.json() comment = await create_comment_in_db(data) return JSONResponse({'comment': comment}, status=201) # Register comments with blog blog_bp.register_blueprint(comments_bp) ``` ### Authentication Module ```python # auth.py from gobstopper.core.blueprint import Blueprint from gobstopper.http.response import JSONResponse import jwt auth_bp = Blueprint('auth', url_prefix='/auth') @auth_bp.post('/register') async def register(request): data = await request.json() # Validate input if not data.get('email') or not data.get('password'): return JSONResponse( {'error': 'Email and password required'}, status=400 ) # Check if user exists if await user_exists(data['email']): return JSONResponse( {'error': 'User already exists'}, status=409 ) # Create user user = await create_user(data) return JSONResponse({'user': user}, status=201) @auth_bp.post('/login') async def login(request): data = await request.json() # Authenticate user = await authenticate(data['email'], data['password']) if not user: return JSONResponse( {'error': 'Invalid credentials'}, status=401 ) # Generate token token = generate_token(user) return JSONResponse({'token': token}) @auth_bp.post('/logout') async def logout(request): # Invalidate token (if using session) token = request.headers.get('authorization') await invalidate_token(token) return JSONResponse({'success': True}) @auth_bp.get('/me') async def get_current_user(request): # Requires authentication middleware user_id = request.state.user_id user = await fetch_user(user_id) return JSONResponse({'user': user}) ``` ### Admin Panel with Sub-Modules ```python # admin/__init__.py from gobstopper.core.blueprint import Blueprint admin_bp = Blueprint('admin', url_prefix='/admin') # Admin middleware @admin_bp.before_request async def require_admin(request): user_role = request.state.get('role') if user_role != 'admin': return JSONResponse({'error': 'Forbidden'}, status=403) # Dashboard @admin_bp.get('/') async def admin_dashboard(request): stats = await fetch_admin_stats() return JSONResponse({'stats': stats}) # admin/users.py users_admin_bp = Blueprint('users_admin', url_prefix='/users') @users_admin_bp.get('/') async def list_all_users(request): users = await fetch_all_users() return JSONResponse({'users': users}) @users_admin_bp.delete('/') async def delete_user(request, user_id: int): await delete_user_from_db(user_id) return JSONResponse({'success': True}) @users_admin_bp.post('//ban') async def ban_user(request, user_id: int): await ban_user_in_db(user_id) return JSONResponse({'success': True}) # admin/settings.py settings_admin_bp = Blueprint('settings_admin', url_prefix='/settings') @settings_admin_bp.get('/') async def get_settings(request): settings = await fetch_settings() return JSONResponse({'settings': settings}) @settings_admin_bp.put('/') async def update_settings(request): data = await request.json() await update_settings_in_db(data) return JSONResponse({'success': True}) # Register sub-blueprints admin_bp.register_blueprint(users_admin_bp) admin_bp.register_blueprint(settings_admin_bp) ``` ### Main Application Assembly ```python # app.py from gobstopper import Gobstopper from auth import auth_bp from blog import blog_bp from admin import admin_bp app = Gobstopper(__name__) # Register blueprints app.register_blueprint(auth_bp) app.register_blueprint(blog_bp) app.register_blueprint(admin_bp) # Main routes @app.get('/') async def index(request): return await app.render_template('index.html') if __name__ == '__main__': import granian granian.serve('app:app', interface='rsgi') ``` **Final route structure:** ``` GET / # Main index POST /auth/register # Auth: Register POST /auth/login # Auth: Login POST /auth/logout # Auth: Logout GET /auth/me # Auth: Current user GET /blog/posts # Blog: List posts GET /blog/posts/ # Blog: Get post POST /blog/posts # Blog: Create post PUT /blog/posts/ # Blog: Update post DELETE /blog/posts/ # Blog: Delete post GET /blog/comments # Blog: List comments POST /blog/comments # Blog: Create comment GET /admin/ # Admin: Dashboard GET /admin/users # Admin: List users DELETE /admin/users/ # Admin: Delete user POST /admin/users//ban # Admin: Ban user GET /admin/settings # Admin: Get settings PUT /admin/settings # Admin: Update settings ``` ## Best Practices ### 1. Organize by Feature, Not Type ``` # ❌ Poor organization (by type) project/ ├── routes/ │ ├── users.py │ └── posts.py ├── models/ │ ├── user.py │ └── post.py └── views/ ├── user_views.py └── post_views.py # ✅ Good organization (by feature) project/ ├── users/ │ ├── __init__.py # Blueprint definition │ ├── routes.py # User routes │ ├── models.py # User models │ └── templates/ # User templates ├── posts/ │ ├── __init__.py # Blueprint definition │ ├── routes.py # Post routes │ ├── models.py # Post models │ └── templates/ # Post templates └── app.py # Main application ``` ### 2. Use Meaningful Blueprint Names ```python # ❌ Poor naming bp = Blueprint('bp') routes = Blueprint('routes') # ✅ Good naming users_bp = Blueprint('users') admin_bp = Blueprint('admin') api_v1_bp = Blueprint('api_v1') ``` ### 3. Keep Blueprints Focused Each blueprint should have a single, clear responsibility: ```python # ✅ Focused blueprints auth_bp = Blueprint('auth') # Authentication only users_bp = Blueprint('users') # User management only posts_bp = Blueprint('posts') # Post management only # ❌ Unfocused blueprint everything_bp = Blueprint('everything') # Too broad ``` ### 4. Document Blueprint Structure Add docstrings to blueprint modules: ```python """ Users Blueprint Handles all user-related operations including: - User registration and authentication - Profile management - User settings - Password reset Routes: - GET /users/ - List users - GET /users/ - Get user - POST /users/ - Create user - PUT /users/ - Update user - DELETE /users/ - Delete user """ from gobstopper.core.blueprint import Blueprint users_bp = Blueprint('users', url_prefix='/users') ``` ### 5. Use Before Request for Common Logic ```python api_bp = Blueprint('api', url_prefix='/api') @api_bp.before_request async def api_before_request(request): """Common logic for all API routes.""" # Authenticate token = request.headers.get('authorization') if not token: return JSONResponse({'error': 'Unauthorized'}, status=401) # Rate limit if not await rate_limiter.allow(token): return JSONResponse({'error': 'Rate limit exceeded'}, status=429) # Set user context request.state.user = await get_user_from_token(token) ``` ### 6. Version Your APIs ```python # v1/users.py v1_users_bp = Blueprint('users_v1', url_prefix='/users') @v1_users_bp.get('/') async def list_users_v1(request): return JSONResponse({'users': []}) # v2/users.py v2_users_bp = Blueprint('users_v2', url_prefix='/users') @v2_users_bp.get('/') async def list_users_v2(request): # Enhanced response format return JSONResponse({'data': {'users': []}, 'meta': {}}) # app.py api_v1 = Blueprint('api_v1', url_prefix='/v1') api_v1.register_blueprint(v1_users_bp) api_v2 = Blueprint('api_v2', url_prefix='/v2') api_v2.register_blueprint(v2_users_bp) app.register_blueprint(api_v1) app.register_blueprint(api_v2) ``` ## Migration from Flask Gobstopper's Blueprint system is similar to Flask's but with some differences: ### Key Differences 1. **Async by default**: All route handlers are async 2. **RSGI protocol**: Uses RSGI instead of WSGI 3. **Template folders**: Full namespacing not yet implemented 4. **Blueprint factory**: No `Blueprint.as_view()` equivalent ### Migration Example **Flask blueprint:** ```python # Flask from flask import Blueprint, jsonify users_bp = Blueprint('users', __name__) @users_bp.route('/users') def list_users(): return jsonify({'users': []}) @users_bp.route('/users/') def get_user(user_id): return jsonify({'user': user_id}) ``` **Gobstopper blueprint:** ```python # Gobstopper from gobstopper.core.blueprint import Blueprint from gobstopper.http.response import JSONResponse users_bp = Blueprint('users', url_prefix='/users') @users_bp.get('/') # Route path relative to blueprint prefix async def list_users(request): # async and explicit request parameter return JSONResponse({'users': []}) @users_bp.get('/') async def get_user(request, user_id: int): # async and explicit request parameter return JSONResponse({'user': user_id}) ``` ### Migration Checklist - [ ] Convert all route handlers to async - [ ] Add explicit `request` parameter to handlers - [ ] Change `jsonify()` to `JSONResponse()` - [ ] Update route decorators (`@route` → `@get`, `@post`, etc.) - [ ] Adjust blueprint registration calls - [ ] Update template rendering calls - [ ] Migrate middleware to Gobstopper middleware system ## Advanced Topics ### Conditional Blueprint Registration ```python import os app = Gobstopper(__name__) # Register debug routes only in development if os.getenv('ENV') == 'development': app.register_blueprint(debug_bp) # Register admin routes only if admin feature is enabled if os.getenv('ENABLE_ADMIN') == 'true': app.register_blueprint(admin_bp) ``` ### Dynamic Blueprint Configuration ```python def create_api_blueprint(version: str, features: list[str]): """Create API blueprint with specific features.""" bp = Blueprint(f'api_{version}', url_prefix=f'/{version}') if 'users' in features: bp.register_blueprint(users_bp) if 'posts' in features: bp.register_blueprint(posts_bp) return bp # Create different API configurations basic_api = create_api_blueprint('v1', ['users']) full_api = create_api_blueprint('v2', ['users', 'posts']) app.register_blueprint(basic_api) app.register_blueprint(full_api) ``` ### Blueprint Testing ```python # test_users_blueprint.py import pytest from gobstopper import Gobstopper from users import users_bp @pytest.fixture def app(): app = Gobstopper(__name__) app.register_blueprint(users_bp) return app @pytest.fixture def client(app): # Create test client return TestClient(app) def test_list_users(client): response = client.get('/users/') assert response.status == 200 assert 'users' in response.json() ``` ## Troubleshooting ### Routes Not Found (404) Check URL prefix combination: ```python # If blueprint has prefix '/users' and route is '/list' users_bp = Blueprint('users', url_prefix='/users') @users_bp.get('/list') # Final URL: /users/list async def list_users(request): pass # To get /users/, use root path: @users_bp.get('/') # Final URL: /users/ async def list_users(request): pass ``` ### Middleware Not Applied Ensure middleware is registered before blueprint: ```python # ✅ Correct order bp = Blueprint('api') bp.add_middleware(auth_middleware) app.register_blueprint(bp) # ❌ Wrong - middleware added after registration app.register_blueprint(bp) bp.add_middleware(auth_middleware) # Won't work ``` ### Before Request Not Running Verify hook is defined before registration: ```python # ✅ Correct bp = Blueprint('api') @bp.before_request async def check_auth(request): pass app.register_blueprint(bp) # ❌ Hook added after registration won't work app.register_blueprint(bp) @bp.before_request # Too late async def check_auth(request): pass ``` ## Summary The Gobstopper Blueprint system provides: - ✅ Clean modular organization - ✅ Reusable components - ✅ Nested blueprint support - ✅ URL prefix management - ✅ Blueprint-scoped middleware and hooks - ✅ Flask-like familiar API Use blueprints to build scalable, maintainable Gobstopper applications!