Flask Debugging Guide
A systematic approach to debugging Flask applications using established patterns and Flask-specific tooling.
Common Error Patterns
HTTP Status Code Errors
404 Not Found - Routing Issues
-
Route decorator not matching requested URL
-
Typos in route definitions or endpoint names
-
Blueprint not registered with application
-
Missing trailing slash mismatch (strict_slashes setting)
-
URL rules registered after first request
500 Internal Server Error
-
Unhandled exceptions in view functions
-
Template rendering errors
-
Database connection failures
-
Malformed JSON request data (missing Content-Type: application/json )
-
Circular imports preventing app initialization
405 Method Not Allowed
-
HTTP method not specified in @app.route() methods parameter
-
Form submission using wrong method (GET vs POST)
Jinja2 Template Errors
TemplateNotFound
-
Template not in templates/ directory
-
Custom template folder not configured
-
Blueprint template folder misconfigured
UndefinedError
-
Variable not passed to render_template()
-
Typo in template variable name
-
Accessing attribute on None object
TemplateSyntaxError
-
Unclosed blocks ({% if %} without {% endif %} )
-
Invalid filter usage
-
Incorrect macro definitions
Application Context Errors
RuntimeError: Working outside of application context
-
Accessing current_app outside request/CLI context
-
Database operations outside with app.app_context():
-
Celery tasks not properly configured with app context
RuntimeError: Working outside of request context
-
Accessing request , session , or g outside view function
-
Background thread without request context
SQLAlchemy Session Issues
DetachedInstanceError
-
Accessing lazy-loaded relationship outside session
-
Object expired after transaction commit
-
Session closed prematurely
InvalidRequestError: Object already attached to session
-
Adding object already in different session
-
Improper session management in tests
OperationalError: Connection pool exhausted
-
Connections not being returned to pool
-
Missing db.session.close() or db.session.remove()
-
Long-running transactions
Blueprint Registration Problems
Blueprint registration failures
-
Circular imports between blueprints
-
Duplicate blueprint names
-
Blueprint registered after first request
-
URL prefix conflicts
Import and Module Errors
ImportError: cannot import name 'Flask'
-
Circular dependency in modules
-
File named flask.py shadowing the package
-
Virtual environment not activated
ModuleNotFoundError: No module named 'flask'
-
Virtual environment not activated
-
Flask not installed in current environment
-
Wrong Python interpreter
Debugging Tools
Built-in Flask Debugger (Werkzeug)
Enable debug mode (DEVELOPMENT ONLY - NEVER IN PRODUCTION)
Method 1: Environment variable
export FLASK_DEBUG=1
Method 2: In code (not recommended)
app.run(debug=True)
Method 3: Flask CLI
flask run --debug
The Werkzeug debugger provides:
-
Interactive traceback with code context
-
In-browser Python REPL at each stack frame
-
Variable inspection at each level
-
PIN-protected access (check terminal for PIN)
Security Warning: Never enable debug mode in production. The debugger allows arbitrary code execution from the browser.
Python Debugger (pdb/breakpoint)
Insert breakpoint in code
def my_view(): data = get_data() breakpoint() # Python 3.7+ (or: import pdb; pdb.set_trace()) return process(data)
Common pdb commands:
n (next) - Execute next line
s (step) - Step into function
c (continue) - Continue to next breakpoint
p variable - Print variable value
pp variable - Pretty print variable
l (list) - Show current code context
w (where) - Show stack trace
q (quit) - Quit debugger
Flask Shell
Start interactive shell with app context
flask shell
In shell:
from app.models import User User.query.all() app.config['DEBUG'] app.url_map # View all registered routes
Logging Module
import logging from flask import Flask
app = Flask(name)
Configure logging
logging.basicConfig(level=logging.DEBUG) app.logger.setLevel(logging.DEBUG)
Add file handler for persistent logs
file_handler = logging.FileHandler('app.log') file_handler.setLevel(logging.WARNING) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) app.logger.addHandler(file_handler)
Usage in views
@app.route('/api/data') def get_data(): app.logger.debug('Processing request for /api/data') try: result = fetch_data() app.logger.info(f'Successfully fetched {len(result)} records') return jsonify(result) except Exception as e: app.logger.error(f'Error fetching data: {e}', exc_info=True) raise
Flask-DebugToolbar
Install: pip install flask-debugtoolbar
from flask import Flask from flask_debugtoolbar import DebugToolbarExtension
app = Flask(name) app.config['SECRET_KEY'] = 'dev-secret-key' app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
toolbar = DebugToolbarExtension(app)
Provides sidebar panels for:
-
Request/response headers
-
Template rendering details
-
SQLAlchemy queries (with query time)
-
Route matching information
-
Configuration values
-
Logging messages
SQLAlchemy Query Logging
Enable SQL query logging
import logging logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
Or in Flask config
app.config['SQLALCHEMY_ECHO'] = True
For query analysis with flask-debugtoolbar
app.config['DEBUG_TB_PANELS'] = [ 'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel', ]
The Four Phases of Flask Debugging
Phase 1: Reproduce and Isolate
Capture the exact error state:
Check Flask is running with debug enabled
flask run --debug
View registered routes
flask routes
Check configuration
flask shell
app.config
Minimal reproduction:
Create minimal test case
def test_failing_route(client): response = client.get('/api/broken-endpoint') print(f"Status: {response.status_code}") print(f"Data: {response.get_data(as_text=True)}") assert response.status_code == 200
Questions to answer:
-
Does error occur on every request or intermittently?
-
What is the exact HTTP status code?
-
What does the traceback show?
-
What was the request payload?
-
What environment variables are set?
Phase 2: Gather Information
Collect comprehensive context:
Add request logging middleware
@app.before_request def log_request_info(): app.logger.debug('Headers: %s', dict(request.headers)) app.logger.debug('Body: %s', request.get_data()) app.logger.debug('Args: %s', dict(request.args))
@app.after_request def log_response_info(response): app.logger.debug('Response Status: %s', response.status) return response
Check configuration:
flask shell
from flask import current_app current_app.config['SQLALCHEMY_DATABASE_URI'] current_app.url_map current_app.extensions
Database state:
In flask shell
from app import db db.session.execute(text("SELECT 1")).fetchone() # Test connection User.query.count() db.engine.pool.status() # Connection pool status
Phase 3: Hypothesize and Test
Form hypotheses based on error patterns:
Error Pattern Likely Cause Test
404 on valid route Blueprint not registered Check app.url_map
500 with no traceback Error before request context Check create_app()
DetachedInstanceError Lazy load outside session Add lazy='joined'
Connection refused DB not running Test connection string
Test hypotheses systematically:
Hypothesis: Route not registered
flask routes | grep expected_endpoint
Hypothesis: Database connection issue
flask shell
from app import db db.session.execute(text("SELECT 1"))
Hypothesis: Configuration not loaded
flask shell
import os os.environ.get('DATABASE_URL') app.config.get('DATABASE_URL')
Phase 4: Fix and Verify
Apply fix with minimal changes:
Before fixing, add test to capture expected behavior
def test_user_creation(client): response = client.post('/api/users', json={'name': 'Test'}) assert response.status_code == 201 assert 'id' in response.get_json()
After fix, verify test passes
pytest tests/test_users.py::test_user_creation -v
Verify fix doesn't introduce regressions:
Run full test suite
pytest
Run with coverage to ensure fix is tested
pytest --cov=app --cov-report=term-missing
Document the fix:
Add comment explaining the fix
@app.route('/api/users', methods=['POST']) def create_user(): # FIX: Must validate JSON before accessing request.json # See: https://github.com/org/repo/issues/123 if not request.is_json: return jsonify(error='Content-Type must be application/json'), 400 ...
Quick Reference Commands
View all registered routes
flask routes
Interactive shell with app context
flask shell
Database migration status (Flask-Migrate)
flask db status flask db current flask db history
Run with debug mode
flask run --debug
Run with specific host/port
flask run --host=0.0.0.0 --port=5000
Show Flask configuration
flask shell -c "print(app.config)"
Test database connection
flask shell -c "from app import db; print(db.session.execute(text('SELECT 1')).fetchone())"
Check environment variables
flask shell -c "import os; print(os.environ.get('FLASK_ENV'))"
List installed extensions
flask shell -c "print(list(app.extensions.keys()))"
Flask-Specific Debugging Patterns
Debug Application Factory Issues
Common issue: app not created properly
def create_app(config_name='default'): app = Flask(name)
# Debug: Print when config loads
print(f"Loading config: {config_name}")
app.config.from_object(config[config_name])
# Debug: Verify extensions initialized
db.init_app(app)
print(f"DB initialized: {db}")
# Debug: Verify blueprints registered
from app.api import api_bp
app.register_blueprint(api_bp, url_prefix='/api')
print(f"Registered routes: {[r.rule for r in app.url_map.iter_rules()]}")
return app
Debug Request/Response Cycle
from flask import g, request import time
@app.before_request def before_request(): g.start_time = time.time() g.request_id = request.headers.get('X-Request-ID', 'unknown') app.logger.info(f"[{g.request_id}] {request.method} {request.path}")
@app.after_request def after_request(response): duration = time.time() - g.start_time app.logger.info( f"[{g.request_id}] {response.status_code} ({duration:.3f}s)" ) return response
@app.teardown_request def teardown_request(exception): if exception: app.logger.error(f"[{g.request_id}] Exception: {exception}")
Debug SQLAlchemy N+1 Queries
from flask_sqlalchemy import SQLAlchemy from sqlalchemy import event
db = SQLAlchemy()
Log all queries in development
if app.debug: @event.listens_for(db.engine, "before_cursor_execute") def receive_before_cursor_execute(conn, cursor, statement, parameters, context, executemany): app.logger.debug(f"SQL: {statement}") app.logger.debug(f"Params: {parameters}")
Debug Template Rendering
Add context processor to expose debug info
@app.context_processor def debug_context(): if app.debug: return { 'debug_info': { 'endpoint': request.endpoint, 'view_args': request.view_args, 'url': request.url, } } return {}
<!-- In template, show debug info --> {% if config.DEBUG %} <pre> Endpoint: {{ debug_info.endpoint }} View Args: {{ debug_info.view_args }} URL: {{ debug_info.url }} </pre> {% endif %}
Debug Blueprint Registration
def register_blueprints(app): """Register all blueprints with debugging.""" from app.api import api_bp from app.auth import auth_bp
blueprints = [
(api_bp, '/api'),
(auth_bp, '/auth'),
]
for bp, prefix in blueprints:
app.logger.debug(f"Registering blueprint: {bp.name} at {prefix}")
try:
app.register_blueprint(bp, url_prefix=prefix)
app.logger.debug(f"Successfully registered {bp.name}")
except Exception as e:
app.logger.error(f"Failed to register {bp.name}: {e}")
raise
Error Handlers for Better Debugging
from flask import jsonify from werkzeug.exceptions import HTTPException
@app.errorhandler(Exception) def handle_exception(e): """Handle all unhandled exceptions.""" # Log the full exception with traceback app.logger.exception(f"Unhandled exception: {e}")
# Pass through HTTP errors
if isinstance(e, HTTPException):
return jsonify(error=e.description), e.code
# Return generic error in production, detailed in development
if app.debug:
return jsonify(
error=str(e),
type=type(e).__name__,
traceback=traceback.format_exc()
), 500
return jsonify(error="Internal server error"), 500
@app.errorhandler(404) def not_found(e): app.logger.warning(f"404: {request.url}") return jsonify(error="Resource not found", path=request.path), 404
@app.errorhandler(500) def internal_error(e): db.session.rollback() # Rollback any failed transactions app.logger.error(f"500 error: {e}") return jsonify(error="Internal server error"), 500
Production Debugging with Sentry
Install: pip install sentry-sdk[flask]
import sentry_sdk from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
sentry_sdk.init( dsn="your-sentry-dsn", integrations=[ FlaskIntegration(), SqlalchemyIntegration(), ], traces_sample_rate=0.1, # 10% of transactions for performance monitoring environment=os.environ.get('FLASK_ENV', 'production'), )
Environment-Specific Configuration
class Config: """Base configuration.""" SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-in-prod') SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config): """Development configuration with debugging enabled.""" DEBUG = True SQLALCHEMY_ECHO = True # Log SQL queries
class TestingConfig(Config): """Testing configuration.""" TESTING = True SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' WTF_CSRF_ENABLED = False
class ProductionConfig(Config): """Production configuration - no debug features.""" DEBUG = False SQLALCHEMY_ECHO = False
# Use proper secret key
SECRET_KEY = os.environ['SECRET_KEY'] # Will raise if not set
config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'default': DevelopmentConfig, }
Debugging Checklist
Before escalating or spending extensive time:
-
Is Flask running in debug mode? (FLASK_DEBUG=1 )
-
Are you in the correct virtual environment?
-
Is the database running and accessible?
-
Have you checked the full traceback in the terminal?
-
Did you run flask routes to verify route registration?
-
Is the request Content-Type correct for JSON endpoints?
-
Have you checked for circular imports?
-
Are all required environment variables set?
-
Is the SECRET_KEY configured?
-
Have you tried flask shell to test in isolation?
-
Did you check the browser's Network tab for request details?
-
Are database migrations up to date? (flask db upgrade )
References
-
Flask Official Debugging Documentation
-
Flask Error Handling Guide
-
Flask Mega-Tutorial: Error Handling
-
Flask Error Handling Patterns - Better Stack
-
DigitalOcean: Handle Errors in Flask