Boilerplate in Flask Python

Boilerplate in Flask Python


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:

  • Clear separation of route definitions and their implementations
  • Easy application of middlewares to specific routes
  • Centralized route management
  • Scalability: easy to add new routes or modify existing ones

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.

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics