Flask - access the request in after_request or teardown_request - python

I want to be able to access the request object before I return the response of the HTTP call.
I want access to the request via "teardown_request" and "after_request":
from flask import Flask
...
app = Flask(__name__, instance_relative_config=True)
...
#app.before_request
def before_request():
# do something
#app.after_request
def after_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(request)
#app.teardown_request
def teardown_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(request)
I saw that I can add the request to g and do something like this:
g.curr_request = request
#app.after_request
def after_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(g.curr_request)
But the above seems a bit strange. I'm sure that there's a better way to access the request.
Thanks

The solution is simple -
from flask import request
#app.after_request
def after_request(response):
do_something_based_on_the_request_endpoint(request)
return response

Also try teardown_request(exception). This executes "regardless of whether there was an exception or not". Check the documentation: http://flask.pocoo.org/docs/0.12/api/#flask.Flask.teardown_request

Related

Flask Middleware with both Request and Response

I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:
The request's HTTP method (GET, POST, or PUT)
The request endpoint
The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.
Some options I've found (that don't quite work for me):
The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation
If a function raises an exception, any remaining after_request functions will not be called.
Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
#app.route('/')
def index():
#after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
Any suggestions?
My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:
The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.
This is how you would use it:
#app.before_request
def gather_request_data():
g.method = request.method
g.url = request.url
#app.after_request
def log_details(response: Response):
g.status = response.status
logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
return response
Gather whatever request information you want in the function decorated with #app.before_request and store it in the g object.
Access whatever you want from the response in the function decorated with #app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.
you can use flask-http-middleware for it link
from flask import Flask
from flask_http_middleware import MiddlewareManager, BaseHTTPMiddleware
app = Flask(__name__)
class MetricsMiddleware(BaseHTTPMiddleware):
def __init__(self):
super().__init__()
def dispatch(self, request, call_next):
url = request.url
response = call_next(request)
response.headers.add("x-url", url)
return response
app.wsgi_app = MiddlewareManager(app)
app.wsgi_app.add_middleware(MetricsMiddleware)
#app.get("/health")
def health():
return {"message":"I'm healthy"}
if __name__ == "__main__":
app.run()
Every time you make request, it will pass the middleware
Okay, so the answer was staring me in the face the whole time, on the page on Deferred Request Callbacks.
The trick is to register a function to run after the current request using after_this_request from inside the before_request callback. This is the code snippet of what worked for me:
#app.before_request
def log_details():
method = request.method
url = request.url
#after_this_request
def log_details_callback(response: Response):
logger.info(f'method: {method}\n url: {url}\n status: {response.status}')
These are the steps:
Get the required details from the response in the before_request callback and store them in some variables.
Then access what you want of the response in the function you decorate with after_this_request, along with the variables you stored the request details in earlier.

Flask - Pass back response of inner request

I looked at some of the similar suggested SO questions, but they weren't quite what I was looking for:
I have a Flask server with a POST route that calls another server. I want Flask to return the response from that request as-is.
import os
import requests
from flask import Flask, request, jsonify, make_response, Response
#app.route('/stuff', methods=['POST'])
def get_stuff():
resp = requests.post(...)
return resp
app.run(host='0.0.0.0', port=9999)
I've tried the following but each returns an error:
return jsonify(resp)
return ( resp.raw.read(), resp.status_code, resp.headers.items() )
return Response(json.dumps(resp), status=resp.status_code, mimetype='application/json')
I just want it to pass back what it got
Flask route functions should only return a string, you need a method to convert it and each of the attempts you made probably fell short in one way or another to do so.
Post the error messages, they may clue in how close you are to accomplishing the rerouting of the post response.

FastAPI - Set the url or path for the Request object

I have a function that looks like this:
#app.middleware("http")
async def process_api_event(request: Request, call_next):
url = request.url
path = request.url.path
# request.__setattr__('url', 'sample_url')
# request.url.__ setattr__('path', 'sample_path')
In the above function, depending on the situation I would like to change the request url, or path.
I tried request.__setattr__('url', 'sample_url') and request.url.__ setattr__('path', 'sample_path') as shown above but I wasn't able to do it due to AttributeError: can't set attribute error. I read through the FastAPI and Starlette documentation, but couldn't really find info that I needed in this case. Any help would be greatly appreciated!
request.url is a property that gets _url attribute, so you can set _url (but request.scope and request.base_url will not change)
from starlette.datastructures import URL
#app.middleware("http")
async def process_api_event(request: Request, call_next):
request._url = URL('sample_url')
print(request.url)
...

Flask teardown_request happening before request sent

I have an issue with the #app.teardown_request decorator in Flask.
My code is looking like this
#app.teardown_request
def teardown_request(exception):
print 'teardown'
#app.after_request
def after_request(response):
print 'after'
return response
#app.route('/entire', methods=['GET'])
def entire():
print 'entire'
return 'This is a text'
#app.route('/chunked', methods=['GET'])
def chunked():
text = 'This is a text'
def gen(t):
print 'chunked'
for a in t:
yield a
return gen(text)
And when I go to the /entire endpoint, I get
after
teardown
entire
When I go to the /chunked endpoint, I get
chunked
after
teardown
So when returning the data in a chunky way, the request teardown is actually happening before I am returning any data (nor executing any code generating this data).
The data coming from a sqlalchemy session, I find my self closing the session before doing anything with the query - the behaviour I get is then getting idle in transaction all over the place...
Flask destroys the context when the response is returned, not after the generator runs. Use stream_with_context to keep the context around for the generator.
from flask import Response, stream_with_context
return Response(stream_with_context(gen(text)))

Is it possible to make POST request in Flask?

There is a need to make POST request from server side in Flask.
Let's imagine that we have:
#app.route("/test", methods=["POST"])
def test():
test = request.form["test"]
return "TEST: %s" % test
#app.route("/index")
def index():
# Is there something_like_this method in Flask to perform the POST request?
return something_like_this("/test", { "test" : "My Test Data" })
I haven't found anything specific in Flask documentation. Some say urllib2.urlopen is the issue but I failed to combine Flask and urlopen. Is it really possible?
For the record, here's general code to make a POST request from Python:
#make a POST request
import requests
dictToSend = {'question':'what is the answer?'}
res = requests.post('http://localhost:5000/tests/endpoint', json=dictToSend)
print 'response from server:',res.text
dictFromServer = res.json()
Notice that we are passing in a Python dict using the json= option. This conveniently tells the requests library to do two things:
serialize the dict to JSON
write the correct MIME type ('application/json') in the HTTP header
And here's a Flask application that will receive and respond to that POST request:
#handle a POST request
from flask import Flask, render_template, request, url_for, jsonify
app = Flask(__name__)
#app.route('/tests/endpoint', methods=['POST'])
def my_test_endpoint():
input_json = request.get_json(force=True)
# force=True, above, is necessary if another developer
# forgot to set the MIME type to 'application/json'
print 'data from client:', input_json
dictToReturn = {'answer':42}
return jsonify(dictToReturn)
if __name__ == '__main__':
app.run(debug=True)
Yes, to make a POST request you can use urllib, see the documentation.
I would however recommend to use the requests module instead.
EDIT:
I suggest you refactor your code to extract the common functionality:
#app.route("/test", methods=["POST"])
def test():
return _test(request.form["test"])
#app.route("/index")
def index():
return _test("My Test Data")
def _test(argument):
return "TEST: %s" % argument

Categories