Search code examples
pythonflaskflask-restful

flask-restful - resource class for current request


THE QUESTION

All of my app's routes are defined via flask-restful Resources. How can I find the resource object/class that is processing current request?

WHY I WANT THIS

I wanted to log all exceptions raised while processing requests. I connect to flask.got_request_exception, as described in http://flask.pocoo.org/docs/1.0/api/#signals and something like this works well:

from flask import got_request_exception, request

def log_exception(sender, exception, **extra):
    logger.info("URL: {}, Exception: {}".format(request.url, type(exception).__name__))

got_request_exception.connect(log_exception, app)

The only problem is that I want to log some of the request data, but not all the data - e.g. I'd like to hide passwords. I think it would be a good idea to have logging-data-logic together with request processing logic, like this:

from flask import request
import flask_restful

class SomeResource(flask_restful.Resource):
    def get(self):
        # ... GET processing
    def log_data(self):
        # log all body params
        return request.get_json()

class Login(flask_restful.Resource):
   def post(self):
       # ... authentication
   def log_data(self):
       # log selected body params
       return {'login': request.get_json()['login'], 'password': 'HIDDEN!'}

and than use it in my log_exception:

from flask import got_request_exception, request

def log_exception(sender, exception, **extra):
    resource_class = # THIS IS THE THING I'M MISSING
    logger.info("URL: {}, Exception: {}, Data: {}".format(request.url, type(exception).__name__), 
                resource_class.log_data())

got_request_exception.connect(log_exception, app) 

But maybe this should be done other way?


Solution

  • Instead of inheriting from flask_restful.Resource your want to inherit all from a custom resource

    class MyResource(flask_restful.Resource):
        def dispatch_request(self, *args, **kwargs):
            try:
                return super(MyResource,self).dispatch_request(*args, **kwargs)
            except Exception as ex:
                setattr(ex, "_raised_by", self)
                raise ex
    

    and then you can use the exception handler

    def log_exception(sender, exception, **extra):
        _raised_by = getattr(exception, "_raised_by", None)
        if _raised_by:
            print(_raised_by)
        property("URL: {}, Exception: {}".format(request.url, type(exception).__name__))
    

    Here is complete code I tried

    from flask import request, Flask
    import flask_restful
    
    app = Flask(__name__)
    
    api = flask_restful.Api(app)
    
    
    class MyResource(flask_restful.Resource):
        def dispatch_request(self, *args, **kwargs):
            try:
                return super(MyResource,self).dispatch_request(*args, **kwargs)
            except Exception as ex:
                setattr(ex, "_raised_by", self)
                raise ex
    
    # MyResource = flask_restful.Resource
    
    class SomeResource(MyResource):
        def get(self):
            raise Exception("Not implemented")
    
        def log_data(self):
            # log all body params
            return request.get_json()
    
    
    class Login(MyResource):
        def post(self):
            raise Exception("Not implemented")
    
        def log_data(self):
            # log selected body params
            return {'login': request.get_json()['login'], 'password': 'HIDDEN!'}
    
    
    from flask import got_request_exception, request
    
    api.add_resource(Login, '/login')
    api.add_resource(SomeResource, '/some')
    
    
    def log_exception(sender, exception, **extra):
        _raised_by = getattr(exception, "_raised_by", None)
        if _raised_by:
            print(_raised_by)
        property("URL: {}, Exception: {}".format(request.url, type(exception).__name__))
    
    
    got_request_exception.connect(log_exception, app)
    
    if __name__ == '__main__':
        app.run(debug=True)
    

    Edit-1: 5th Aug

    As commented by @jbet, in case one wants to get the processing class always a cleaner option would be to use the MyResource as below

    from flask import g
    
    class MyResource(flask_restful.Resource):
        def dispatch_request(self, *args, **kwargs):
            g.processed_by = self
            return super(MyResource,self).dispatch_request(*args, **kwargs)