Boilerplate in Flask Python
Harshil Kothari
Solution Architect | Certified GCP Cloud Architect | Senior Full-Stack Developer | Expert in Node.js, Angular, React, Python, Java, Golang | 8+ Years Experience in System Design, Agile & Scalable Apps
Flask is a powerful micro-framework for Python that is both flexible and lightweight. As your application grows, understanding advanced concepts and design patterns becomes essential to ensure scalability, efficiency, and maintainability. This blog will cover advanced Flask concepts, including configuration management, application structuring, design patterns, dependency injection, rate limiting, logging, and implementing authentication and authorization middleware.
Configuration Management
Proper configuration management is crucial for handling different environments and settings efficiently.
Configuration Basics
You can configure your Flask application using a dictionary-like config object:
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'your-secret-key'
Loading Configuration from Files
For better separation of concerns, load configurations from external files:
app.config.from_pyfile('config.py')
Or from environment variables:
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
Application Structuring with Blueprints
As your Flask application grows, organizing your code becomes critical. Flask's Blueprints allow you to split your application into modular components.
Creating Blueprints
from flask import Blueprint
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login')
def login():
return "Login Page"
Registering Blueprints
def create_app():
app = Flask(__name__)
app.register_blueprint(auth_bp, url_prefix='/auth')
return app
Route File with Middleware
To keep your code organized and easily maintainable, you can separate your routes into a dedicated file and apply middleware efficiently. Here's an example of a routes.py file that demonstrates route mapping and middleware application:
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from functools import wraps
from .services import UserService
api = Blueprint('api', __name__)
def log_request(f):
@wraps(f)
def decorated_function(*args, **kwargs):
print(f"Request made to {request.path}")
return f(*args, **kwargs)
return decorated_function
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
route_mappings = {
'/users': {
'GET': ('get_users', [log_request, limiter.limit("100/day")]),
'POST': ('create_user', [log_request, jwt_required(), limiter.limit("10/hour")])
},
'/users/<int:user_id>': {
'GET': ('get_user', [log_request]),
'PUT': ('update_user', [log_request, jwt_required()]),
'DELETE': ('delete_user', [log_request, jwt_required()])
},
'/protected': {
'GET': ('protected_route', [log_request, jwt_required()])
}
}
def get_users():
# Implementation
return jsonify({"message": "List of users"}), 200
def create_user():
# Implementation
return jsonify({"message": "User created"}), 201
def get_user(user_id):
# Implementation
return jsonify({"message": f"User {user_id} details"}), 200
def update_user(user_id):
# Implementation
return jsonify({"message": f"User {user_id} updated"}), 200
def delete_user(user_id):
# Implementation
return jsonify({"message": f"User {user_id} deleted"}), 200
def protected_route():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
for route, methods in route_mappings.items():
for method, (func_name, middlewares) in methods.items():
view_func = globals()[func_name]
for middleware in reversed(middlewares):
view_func = middleware(view_func)
api.add_url_rule(route, func_name, view_func, methods=[method])
def register_routes(app):
app.register_blueprint(api, url_prefix='/api')
This structure offers several advantages:
To use this in your main application file:
from flask import Flask
from .routes import register_routes
def create_app():
app = Flask(__name__)
register_routes(app)
return app
Design Patterns in Flask
Design patterns help you structure your application in a scalable and maintainable way. Here are some common design patterns used in Flask applications:
Factory Pattern
The Factory Pattern is useful for creating instances of the Flask application. This is particularly helpful for testing and configuration management.
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
return app
Service Layer Pattern
The Service Layer Pattern helps you separate business logic from the Flask views.
class UserService:
def get_user(self, user_id):
# Business logic to get user
pass
@app.route('/user/<int:user_id>')
def get_user(user_id):
user_service = UserService()
user = user_service.get_user(user_id)
return jsonify(user)
Dependency Injection
Dependency Injection (DI) is a design pattern that helps you manage dependencies in a more modular and testable way.
Using Flask-Injector
Flask-Injector is a library that integrates the Injector dependency-injection framework with Flask.
Installing Dependencies
pip install Flask-Injector
Setting Up Dependency Injection
from flask import Flask
from flask_injector import FlaskInjector
from injector import inject, singleton
class Config:
DB_CONNECTION_STRING = 'sqlite:///example.db'
class Database:
def __init__(self, connection_string):
self.connection_string = connection_string
class UserService:
@inject
def __init__(self, db: Database):
self.db = db
def configure(binder):
binder.bind(
Database,
to=Database(Config.DB_CONNECTION_STRING),
scope=singleton
)
app = Flask(__name__)
@app.route('/user/<int:user_id>')
@inject
def get_user(user_id, user_service: UserService):
user = user_service.get_user(user_id)
return jsonify(user)
FlaskInjector(app=app, modules=[configure])
Rate Limiting
Rate limiting is essential to prevent abuse of your API. Flask-Limiter is a useful extension for this purpose.
Installing Dependencies
pip install Flask-Limiter
Setting Up Rate Limiting
In your app/__init__.py or wherever you initialize your Flask app:
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
get_remote_address,
app=app,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://"
)
Applying Rate Limits to Routes
You can apply rate limits to specific routes or blueprints:
@app.route("/api/limited")
@limiter.limit("10 per minute")
def limited_route():
return "This route is rate limited"
from flask import Blueprint
api = Blueprint('api', __name__)
limiter.limit("1000 per day")(api)
@api.route("/resource")
@limiter.limit("100 per hour")
def api_resource():
return "This resource is rate limited"
Dynamic Rate Limiting
You can also set rate limits dynamically based on the request or user:
def rate_limit_by_user(user):
if user.is_premium:
return "1000 per hour"
return "100 per hour"
@app.route("/user_resource")
@limiter.limit(rate_limit_by_user)
def user_resource():
return "Rate limited based on user type"
领英推荐
Logging
Proper logging is crucial for monitoring and debugging your application. Flask provides a built-in logging mechanism.
Setting Up Logging
import logging
from flask import Flask
app = Flask(__name__)
if not app.debug:
handler = logging.FileHandler('app.log')
handler.setLevel(logging.WARNING)
app.logger.addHandler(handler)
@app.route('/')
def index():
app.logger.warning('This is a warning')
return "Check the logs for a warning message"
Authentication and Authorization Middleware
Securing your application is paramount. Implementing authentication and authorization can be efficiently handled using middleware.
JWT Authentication Middleware
JSON Web Tokens (JWT) are a common method for securing APIs.
Installing Dependencies
pip install Flask-JWT-Extended
Setting Up JWT
from flask import Flask, jsonify
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-jwt-secret-key'
jwt = JWTManager(app)
@app.route('/login', methods=['POST'])
def login():
access_token = create_access_token(identity={'username': 'user1'})
return jsonify(access_token=access_token)
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user)
Folder Structure
Here's a recommended folder structure that incorporates all the concepts we've discussed:
myapp/
│
├── app/
│ ├── __init__.py
│ ├── config.py
│ ├── extensions.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── ...
│ ├── services/
│ │ ├── __init__.py
│ │ ├── user_service.py
│ │ └── ...
│ ├── api/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── auth.py
│ │ ├── users.py
│ │ └── ...
│ ├── web/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── auth.py
│ │ └── ...
│ ├── templates/
│ └── static/
│
├── tests/
│ ├── __init__.py
│ ├── test_api.py
│ ├── test_models.py
│ └── ...
│
├── migrations/
├── requirements.txt
├── run.py
└── .env
Explanation of the structure:
1. app/: Main application package
- __init__.py: Initializes the Flask app and brings together various components
- config.py: Configuration settings for different environments
- extensions.py: Initializes Flask extensions (e.g., SQLAlchemy, JWT)
- models/: Database models
- services/: Business logic layer
- api/: API routes and resources
- web/: Web routes (if you have a web interface)
- templates/ and static/: For web interface assets
2. tests/: Unit and integration tests
3. migrations/: Database migration scripts (if using Flask-Migrate)
4. run.py: Script to run the application
5. .env: Environment variables (not version controlled)
Key Files:
app/__init__.py
from flask import Flask
from .extensions import db, jwt, limiter
from .config import config
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
jwt.init_app(app)
limiter.init_app(app)
from .api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api')
from .web import web as web_blueprint
app.register_blueprint(web_blueprint)
return app
app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
db = SQLAlchemy()
jwt = JWTManager()
limiter = Limiter(key_func=get_remote_address)
app/api/routes.py
from flask import Blueprint
from .auth import auth_routes
from .users import user_routes
api = Blueprint('api', __name__)
api.register_blueprint(auth_routes)
api.register_blueprint(user_routes)
@api.before_request
def before_request():
# Common logic for all API routes
pass
app/api/users.py
from flask import Blueprint, jsonify
from ..services.user_service import UserService
from ..extensions import limiter
user_routes = Blueprint('users', __name__)
@user_routes.route('/users', methods=['GET'])
@limiter.limit("100 per minute")
def get_users():
users = UserService.get_all_users()
return jsonify(users), 200
run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run()
Scaling Your Flask Application
To make your Flask application scalable, consider the following strategies:
Using a Production-Ready Server
Flask's built-in server is not suitable for production. Use a WSGI server like Gunicorn:
pip install gunicorn
gunicorn -w 4 -b 127.0.0.1:4000 myapp:app
Database Optimization
Use efficient database access patterns and indexing. Consider using NoSQL databases like MongoDB for high read/write operations.
Caching
Implement caching to reduce the load on your server. Flask-Caching is a useful extension:
pip install Flask-Caching
from flask_caching import Cache
app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
@app.route('/expensive')
@cache.cached(timeout=60)
def expensive_view():
return "Expensive Data"
Load Balancing
Distribute your traffic across multiple servers using load balancers like Nginx or AWS Elastic Load Balancing.
Conclusion
By leveraging advanced Flask concepts such as proper configuration management, modular application structuring with Blueprints, implementing robust authentication and authorization, and employing effective scaling strategies, you can build a highly scalable and efficient Flask application. These practices will help you manage growing codebases and user demands, ensuring your application remains performant and maintainable.
For further reading and tutorials, consider exploring resources like the Flask documentation, advanced Flask courses on platforms like Udemy, and community discussions on forums like Reddit.