1

I am developing a REST API in Flask. Some endpoints need to perform additional duties after the response is handled, but I would like to avoid using external processing queue or threads. One very convenient solution seems to be with WSGI Middleware and ClosingIterator, as outlined in this answer. However the handler of each point needs to know which endpoint handled the request, in order to perform their post-mortem duties.

One idea is to decorate my endpoints like this:

@app.route('/api/status/info', methods=['GET'])
def get_status_info():
    @app.after_this_response('get_status_info')
    def say_hi():
        print('hi, unknown endpoint!')

    return 'ok', 200

Instead of unknown endpoint I would like to print get_status_info. Is this possible?

Even better, if I write a generic @app.after_response handler (as in another answer by the same author), can I determine in it which endpoint was used for handling the request?

EDIT: Trying to use flask.request.url_rule.endpoint throws an exception:

RuntimeError: Working outside of request context.
johndodo
  • 17,247
  • 15
  • 96
  • 113
  • The first thing that came to my mind are some daeomn-like threads. Have you considered this? – gonczor Feb 06 '19 at 21:35
  • 1
    does `flask.request.url_rule.endpoint` get you what you need? – Sir Neuman Feb 06 '19 at 21:41
  • @SirNeuman I have edited the answer - unfortunately it doesn't work outside of request context. – johndodo Feb 07 '19 at 06:45
  • @gonczor I would prefer it I could avoid threads (I have edited the answer to reflect that, thanks!). The solution with WSGI Middleware is actually perfect for what I need, I just need to be able to pass parameters to my handler (which I am almost sure can be done somehow, but my WSGI-foo is lacking :) ). – johndodo Feb 07 '19 at 11:45
  • 1
    @johndodo If it's outside of the request context could you add `@copy_current_request_context` decorator to your `say_hi` function? http://flask.pocoo.org/docs/1.0/api/#flask.copy_current_request_context – Sir Neuman Feb 07 '19 at 14:23
  • @SirNeuman Yes, thank you, that works! But only if I use `@after_this_response`. Do you maybe know how I could get the current request context in case of [`@after_response`](https://stackoverflow.com/a/51013358/593487) too? – johndodo Feb 07 '19 at 15:49
  • @SirNeuman If you care to post an answer, I would be happy to accept it. And thank you again! – johndodo Feb 08 '19 at 11:48

1 Answers1

3

To get the endpoint of the original request inside @app.after_this_response you can do the following:

@app.route('/api/status/info', methods=['GET'])
def get_status_info():

    @app.after_this_response('get_status_info')
    @flask.copy_current_request_context
    def say_hi():
        print('hi, %s', % (flask.request.url_rule.endpoint))

    return 'ok', 200

flask.request.url_rule.endpoint gives you the endpoint's name, however you lose the request context by default when it passes onto the say_hi function, which is why you need the @copy_current_request_context decorator to pass on the context. Reference to that decorator is here: http://flask.pocoo.org/docs/1.0/api/#flask.copy_current_request_context

Sir Neuman
  • 1,385
  • 1
  • 14
  • 23
  • `after_this_response` does not seem to exist -- only `after_this_request`, which adds a temporary `after_request` handler and still runs before returning to the browser. However, there is a `response.call_on_close` decorator that does the job and can be paired with `copy_current_request_context`. – Kiran Jonnalagadda Aug 07 '20 at 13:00
  • I should have read more carefully for where that method is coming from. In any case, I found a simpler way to do this without creating new middleware: https://stackoverflow.com/a/63080968/78903 – Kiran Jonnalagadda Aug 07 '20 at 13:05