Overview
The request context keeps track of request-level data during a request. It provides access to request and session objects, and is automatically created and destroyed for each request.
What is Request Context?
Starting with Flask 3.2, request context has been merged into AppContext. The context now includes both application and request data when a request is being handled.
From ctx.py:260-298, the unified context provides:
Access to request and session proxies
Request-specific data and state
Automatic lifecycle management
class AppContext :
"""An app context contains information about an app, and about the request
when handling a request."""
def __init__ ( self , app , * , request = None , session = None ):
self .app = app
self .g = app.app_ctx_globals_class()
self ._request = request
self ._session = session
# ...
@ property
def has_request ( self ):
"""True if this context was created with request data."""
return self ._request is not None
When is it Active?
Request context is automatically active during:
from flask import Flask, request
app = Flask( __name__ )
@app.route ( '/' )
def index ():
# Request context is active
print (request.method) # Works!
print (request.url) # Works!
return 'Hello'
# Outside request - won't work
try :
print (request.method) # RuntimeError!
except RuntimeError as e:
print (e) # "Working outside of request context"
The request Proxy
From wrappers.py:18-220, access request data:
from flask import request
@app.route ( '/info' )
def request_info ():
# HTTP method
method = request.method # GET, POST, etc.
# URL information
url = request.url # Full URL
base_url = request.base_url # Without query string
path = request.path # /info
query_string = request.query_string # bytes
# Headers
user_agent = request.headers.get( 'User-Agent' )
content_type = request.content_type
# Client info
remote_addr = request.remote_addr
referrer = request.referrer
# Routing info
endpoint = request.endpoint # 'request_info'
view_args = request.view_args # URL parameters dict
url_rule = request.url_rule # Matched Rule object
# Blueprint info
blueprint = request.blueprint # Current blueprint or None
return jsonify({
'method' : method,
'path' : path,
'endpoint' : endpoint
})
The session Proxy
From ctx.py:381-403, access session data:
from flask import session
@app.route ( '/login' , methods = [ 'POST' ])
def login ():
# Session context is automatically available
session[ 'user_id' ] = 123
session[ 'logged_in' ] = True
return redirect( '/dashboard' )
@app.route ( '/dashboard' )
def dashboard ():
# Access session data
if 'user_id' not in session:
return redirect( '/login' )
user_id = session.get( 'user_id' )
return f 'User ID: { user_id } '
Creating Request Context
For Testing
def test_request ():
with app.test_request_context( '/' ):
# Request context is active
assert request.path == '/'
assert request.method == 'GET'
def test_post_request ():
with app.test_request_context( '/' , method = 'POST' , data = { 'key' : 'value' }):
assert request.method == 'POST'
assert request.form[ 'key' ] == 'value'
With Custom Environ
from werkzeug.test import EnvironBuilder
with app.request_context(EnvironBuilder( '/' , method = 'POST' ).get_environ()):
# Custom request context
assert request.method == 'POST'
Manual Push/Pop
ctx = app.test_request_context( '/' )
ctx.push()
try :
# Do work with request context
print (request.path)
finally :
ctx.pop()
Request Context Properties
From ctx.py:350-379:
@ property
def request ( self ):
"""The request object associated with this context."""
if self ._request is None :
raise RuntimeError ( "There is no request in this context." )
return self ._request
@ property
def session ( self ):
"""The session object associated with this context."""
session = self ._get_session()
session.accessed = True # Mark as accessed
return session
@ property
def has_request ( self ):
"""True if this context was created with request data."""
return self ._request is not None
Context Locals
Flask uses context-local variables that are isolated per request:
from flask import request, g
import threading
@app.route ( '/worker/<int:task_id>' )
def start_worker ( task_id ):
# Each thread gets its own request context
def worker ():
# This would fail - different thread
# print(request.path) # RuntimeError!
pass
thread = threading.Thread( target = worker)
thread.start()
return f 'Started task { task_id } '
Copying Request Context
From ctx.py:154-206, copy context for background tasks:
from flask import copy_current_request_context
import gevent
@app.route ( '/background' )
def background_task ():
@copy_current_request_context
def do_work ():
# Has access to request and session
print (request.method)
print (session.get( 'user_id' ))
gevent.spawn(do_work)
return 'Task started'
When copying request context for background tasks:
Read request.form, request.json, or request.data before spawning
Access session in parent to ensure Vary: Cookie header is set
Avoid modifying session in background task
Checking Context State
From ctx.py:209-232:
from flask import has_request_context, request
def get_user_ip ():
if has_request_context():
return request.remote_addr
return None
def log_request_info ():
if has_request_context():
print ( f ' { request.method } { request.path } ' )
else :
print ( 'No request context' )
@app.route ( '/' )
def index ():
ip = get_user_ip() # Works - returns IP
return f 'Your IP: { ip } '
# Outside request
ip = get_user_ip() # Returns None
Request Lifecycle
From ctx.py:416-517, the request processing flow:
# 1. Context is pushed
ctx.push()
# - Creates request object
# - Opens session
# - Matches URL to route
# - Sends appcontext_pushed signal
# 2. Request is processed
# - before_request functions run
# - View function executes
# - after_request functions run
# 3. Context is popped
ctx.pop()
# - teardown_request functions run
# - Request object is closed
# - teardown_appcontext functions run
# - Sends appcontext_popped signal
URL Matching
From ctx.py:405-414, URL is matched when context is pushed:
def match_request ( self ):
"""Apply routing to the current request."""
try :
result = self .url_adapter.match( return_rule = True )
except HTTPException as e:
self ._request.routing_exception = e
else :
self ._request.url_rule, self ._request.view_args = result
Access matched route information:
@app.route ( '/user/<int:user_id>' )
def user_profile ( user_id ):
print (request.url_rule) # /user/<int:user_id>
print (request.endpoint) # 'user_profile'
print (request.view_args) # {'user_id': 123}
return f 'User: { user_id } '
After This Request
From ctx.py:118-148, register one-time response processors:
from flask import after_this_request
@app.route ( '/download' )
def download ():
@after_this_request
def add_download_header ( response ):
response.headers[ 'Content-Disposition' ] = 'attachment; filename=data.csv'
return response
return generate_csv()
@app.route ( '/api/data' )
def api_data ():
@after_this_request
def add_timing_header ( response ):
elapsed = time.time() - g.start_time
response.headers[ 'X-Processing-Time' ] = f ' { elapsed :.3f} s'
return response
return jsonify(data)
Request Hooks
Before Request
Run before each request:
@app.before_request
def load_user ():
g.user = None
if 'user_id' in session:
g.user = User.query.get(session[ 'user_id' ])
@app.before_request
def check_maintenance ():
if app.config.get( 'MAINTENANCE_MODE' ):
return render_template( 'maintenance.html' ), 503
After Request
Modify response after each request:
@app.after_request
def add_security_headers ( response ):
response.headers[ 'X-Content-Type-Options' ] = 'nosniff'
response.headers[ 'X-Frame-Options' ] = 'SAMEORIGIN'
return response
@app.after_request
def add_cache_header ( response ):
if request.endpoint == 'static' :
response.cache_control.max_age = 3600
return response
Teardown Request
Cleanup after each request:
@app.teardown_request
def close_db_connection ( exception ):
db = g.pop( 'db' , None )
if db is not None :
if exception is not None :
db.rollback()
db.close()
@app.teardown_request
def log_exception ( exception ):
if exception is not None :
app.logger.error( f 'Request failed: { exception } ' )
Common Patterns
Database Connection Per Request
from flask import g
def get_db ():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config[ 'DATABASE' ],
detect_types = sqlite3. PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
@app.teardown_request
def close_db ( error ):
db = g.pop( 'db' , None )
if db is not None :
db.close()
@app.route ( '/users' )
def list_users ():
db = get_db()
users = db.execute( 'SELECT * FROM users' ).fetchall()
return jsonify([ dict (user) for user in users])
Request Timing
import time
@app.before_request
def start_timer ():
g.start_time = time.time()
@app.after_request
def log_request_time ( response ):
if hasattr (g, 'start_time' ):
elapsed = time.time() - g.start_time
if elapsed > 1.0 : # Log slow requests
current_app.logger.warning(
f 'Slow request: { request.method } { request.path } - { elapsed :.2f} s'
)
return response
User Authentication
@app.before_request
def load_logged_in_user ():
user_id = session.get( 'user_id' )
if user_id is None :
g.user = None
else :
g.user = db.execute(
'SELECT * FROM user WHERE id = ?' , (user_id,)
).fetchone()
def login_required ( view ):
@functools.wraps (view)
def wrapped_view ( ** kwargs ):
if g.user is None :
return redirect(url_for( 'login' ))
return view( ** kwargs)
return wrapped_view
@app.route ( '/profile' )
@login_required
def profile ():
return render_template( 'profile.html' , user = g.user)
Context Preservation
For debugging, Flask preserves context on exceptions:
app.config[ 'PRESERVE_CONTEXT_ON_EXCEPTION' ] = True
@app.route ( '/debug' )
def debug ():
raise Exception ( 'Debug this!' )
# Request context remains active in debugger
Best Practices
Use Context Checks Always check has_request_context() in reusable code
Clean Up Resources Use teardown_request to close connections and files
Be Careful with Threads Use copy_current_request_context for background tasks
Test with Context Always use test_request_context in unit tests
Request Context vs Application Context
In Flask 3.2+, both contexts are unified:
Available In Context Objects Application Context current_app, gRequest Context current_app, g, request, session
# Application context only
with app.app_context():
print (current_app.name) # Works
print (g.value) # Works
# print(request.path) # RuntimeError!
# Request context (includes app context)
with app.test_request_context( '/' ):
print (current_app.name) # Works
print (g.value) # Works
print (request.path) # Works
print (session[ 'key' ]) # Works