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¶
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¶
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('/<int:user_id>')
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¶
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/<id>
# POST /users/
Blueprint Registration¶
Basic Registration¶
# 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¶
# 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:
# 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:
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:
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:
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:
# 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('/<int:user_id>')
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/<id>
# GET /api/posts/
Complex Nesting Example¶
# 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:
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:
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:
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:
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('/<int:user_id>') # 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:
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:
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¶
# 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/<int:post_id>')
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/<int:post_id>')
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/<int:post_id>')
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¶
# 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¶
# 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('/<int:user_id>')
async def delete_user(request, user_id: int):
await delete_user_from_db(user_id)
return JSONResponse({'success': True})
@users_admin_bp.post('/<int:user_id>/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¶
# 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/<id> # Blog: Get post
POST /blog/posts # Blog: Create post
PUT /blog/posts/<id> # Blog: Update post
DELETE /blog/posts/<id> # 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/<id> # Admin: Delete user
POST /admin/users/<id>/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¶
# ❌ 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:
# ✅ 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:
"""
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/<id> - Get user
- POST /users/ - Create user
- PUT /users/<id> - Update user
- DELETE /users/<id> - Delete user
"""
from gobstopper.core.blueprint import Blueprint
users_bp = Blueprint('users', url_prefix='/users')
5. Use Before Request for Common Logic¶
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¶
# 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¶
Async by default: All route handlers are async
RSGI protocol: Uses RSGI instead of WSGI
Template folders: Full namespacing not yet implemented
Blueprint factory: No
Blueprint.as_view()equivalent
Migration Example¶
Flask blueprint:
# 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/<int:user_id>')
def get_user(user_id):
return jsonify({'user': user_id})
Gobstopper blueprint:
# 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('/<int:user_id>')
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
requestparameter to handlers[ ] Change
jsonify()toJSONResponse()[ ] 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¶
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¶
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¶
# 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:
# 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:
# ✅ 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:
# ✅ 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!