Python decorator error handling - python

I am creating bottle rest API and I want to use function decorator at some endpoints to authentificate user. Decorator code is:
def authenticating_decorator(func):
def wrapper():
try:
'''
auth user before execution of the required code
if user is not authenticated bottle.HTTPError is raised
'''
auth()
return func
except HTTPError as e:
return handle_auth_error
return wrapper()
return authenticating_decorator
Handle auth error function:
def handle_auth_error(error):
return {
"code": error.status_code,
"name": error.body.get('name'),
"description": error.body.get('description')
}
Everything is working fine except that I have bottle plugin installed to catch exceptions and convert them to required JSON and API response has content typeapplication/json
When exception occurs in auth method, API return error in known html format because it somehow skips my error plugin. (I mahbe do not fully understand application flow when using both plugins and decorators)
Call method of my error plugin:
def __call__(self, callback):
def wrapper(*a, **kw):
try:
rv = callback(*a, **kw)
return rv
except HTTPError as e:
response.status = e.status_code
return {
"code": e.status_code,
"name": e.body.get('name'),
"description": e.body.get('description')
}
return wrapper
My point is that I have to pass function to the plugin because of line rv = callback(*a, **kw)
and since I have multiple types of exception in auth() method in decorator I want to pass exception as argument to handle_auth_error in decorator
But if I type return handle_auth_error(e) the function returns dict, not the function and I am getting exception dict object is not callable at code line rv = callback(*a, **kw)
How can I return function with argument from decorator withouth calling it in decorator but calling it in plugin?
Or how can I pass exception as parameter to plugin?
The possible solution is creating own function to handle for every possible exception with 'switch' statement based on exception name, but I want to do it more programically:
return {
'HEADER_MISSING': handle_header_missing_exception,
'TOKEN_EXPIRED': handle_expired_token_exception,
etc ... : etc...
}.get(e.body.get('name'))

I think your decorator is not written correctly, shouldn't it be:
def authenticating_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kw):
try:
'''
auth user before execution of the required code
if user is not authenticated bottle.HTTPError is raised
'''
auth()
return func(*args, **kw) #calling func here
except HTTPError as e:
return handle_auth_error(e) #calling the handle_auto_error here
return wrapper #notice - no call here, just return the wrapper

Related

Python access function attributes with multiple decorators applied to it

Have created a fastapi endpoint which looks something like this
#v1.get("/users")
def fetchusers(token:str):
res = fetchdetailsfromdb()
return res
Now the API has 2 requirements, before running check if the token is valid and log an event by calling another api
To cover the authorization part I have created a decorator as such which runs before the function execution
def apply_auth(func):
#wraps(func)
def check_token(*args, **kwargs):
res = validate_user(kwargs["token"]) # Some function to validate is user is valid or not
if res["result"] != True:
raise Exception("Invalid token")
else:
check_token.authorization_data = res
return func(*args, **kwargs)
return check_token
The decorator sets an attribute named authorization_data which can be accessed during the function execution of the api endpoint
The authorization decorator works perfectly when its the only decorator applied
To cover the second use case I had created another decorator to go along with it
This decorator is supposed to run after function execution
def logdata(func):
#wraps(func)
def log_api(*args, **kwargs):
retval = func(*args, **kwargs)
log_data = {}
log_data['name'] = log_api.EVENT
log_data['user_id'] = log_api.INITIATOR_ID
requests.post(url = "DUMMY_URL", json = log_data, timeout=1)
return retval
return log_api
#v1.get("/users")
#apply_auth # Runs before function execution
#logdata # Runs after function execution
def fetchusers(token:str):
authorization_data = fetchusers.authorization_data # Access authorization related data such as username user email etc....
fetchusers.EVENT = "FETCH_USERS_LIST"
fetchusers.INITIATOR_ID = authorization_data["id"]
res = fetchdetailsfromdb()
return res
However using multiple decorators is giving me issues such as the function attributes EVENT and INITIATIOR_ID is not available for some reason in the logdata decorator
If #apply_auth is not added to the function #logdata can access those attributes
With both decorators added #apply_auth is able to access the function attributes but same is not visible for the #logdata decorator
Any ideas on where I am going wrong here ?

Include a function with render in another view function

I want to design an error handler function in view and use it in different main functions in view.
This error handler function will gather required error info, log the error to the system, and render those info to an error handler page.
Codes in view.py look like:
def error_manage(request, error_title, error_detail):
error_time = datetime.now()
info_table = {'error_title': error_title,
'error_detail': error_detail,
'error_time ': error_time ,
}
logger.error(str(error_detail))
return render(request,'home/error_manage.html', {'error_title': error_title,
'info_table ': display_table})
def my_page_1(request):
try:
var1 = my_calculation_func()
except Exception as ex:
error_manage(request, "Unexpected Error Happened.", ex)
var2 = my_calculation_func2()
var3 = my_calculation_func3()
return render(request,'home/my_page_1.html', {'var1': var1 ,'var2': var2 ,'var3': var3})
The problem here is that when the error occurs in var1, the program will jump into error_manage but will not stop in the return of this function and also will not successfully render into error_manage.html.
it will later jump out of this function and continue run var2 and var3 until it meets the final return in my_page_1().
I know I can simply put the return statement in error_manage to my_page_1 under Exception.
But this error handler will be used in many other functions in view.py and I don't want to repeat it over and over again.
Is there a good solution for this problem? Thank you.
Here's an example of the decorator approach:
class MyError(Exception):
pass
def error_handler(func):
def decorated(*args, **kwargs):
try:
return func(*args, **kwargs)
except MyError as e:
return f"Oh no! {e} happened!"
return decorated
#error_handler
def my_view(request):
if "error" in request:
raise MyError("request was rubbish")
return "Great success!"
for r in ["Fine", "no problem", "An error happened"]:
print(my_view(r))
So running this, we get
Great success!
Great success!
Oh no! request was rubbish happened!
So essentially we use the decorator to catch and handle MyError exceptions, other exceptions will not be handled and will bubble up out of the view as normal to be handled by django's [default] view error handling.
You should make a custom middleware to handle exceptions. See Writing your own middleware [Django docs]:
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_exception(request, exception):
error_time = datetime.now()
info_table = {
'error_detail': exception,
'error_time ': error_time ,
}
logger.error(str(exception))
return render(request,'home/error_manage.html', {'info_table ': display_table})
Now you need to add this middleware to the MIDDLEWARE list in the settings:
MIDDLEWARE = [
# Default middlewares
'path.to.MyMiddleware',
]
Now in your view you should simply not catch the Exception and let the middleware handle it.
Note: With this your approach of passing error_title won't work but you can try to re-raise a new exception from the old one if you
need some customization: raise Exception("Unexpected Error Happened.") from ex

Why python Flask decorator not logged?

I have a simple Flask app where i want to validate requests in json format with a decorator like this:
def validate_request(*expected_args):
"""
Validate requests decorator
"""
# print('=======================ENTER VALIDATE_REQUEST================')
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
json_obj = request.get_json()
for expected_arg in expected_args:
if expected_arg not in json_obj or json_obj.get(expected_arg) is None:
return api_response({'error': f"You must call with all request params: {', '.join(expected_args)}"})
return func(*args, **kwargs)
return wrapper
return decorator
And on the route side like this:
#user_api.route('/login', methods=['POST'])
#validate_request('email', 'password')
def login():
req_data = request.get_json()
......................
My question is why the PRINT statement from decorator is not shown when i call '/login' route?
The message is logged only when i start the server(flask run).
Thank you.
Python decorators replace the functions they are decorating. This replacement happens at the start of the program when the interpreter reads the code. This is when your print statement will be executed. After this time, the returned function is the only code that will run. If you want the print to run at every call, you need to put in the actual function that will be called:
def validate_request(*expected_args):
"""
Validate requests decorator
"""
# print('=======================Function Decoration Happening================')
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("==== ENTER VALIDATE_REQUEST ===")
json_obj = request.get_json()
for expected_arg in expected_args:
if expected_arg not in json_obj or json_obj.get(expected_arg) is None:
return api_response({'error': f"You must call with all request params: {', '.join(expected_args)}"})
return func(*args, **kwargs)
return wrapper
return decorator
Your print statement is in validate_request. Although validate_request returns a decorator, it is not a decorator itself. It is a decorator factory if you will, and is only executed once to create the decorator and decorate login when python loads your code, hence why your print statement only gets executed once when you boot up your server. Keep in mind that the decorator itself is also executed just once to replace login with whatever the decorator returns, in this case, wrapper. So, wrapper is the actual function that will be called on each request. If you put your print statement there, you'll see it on each request.
def validate_request(*expected_args):
def decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("=== Enter validate request ===")
return func(*args, **kwargs)
return wrapper
return decorator

Assign exception handler to methods of class

I have a class with multiple functions. These functions will handle similar kinds of exceptions. Can I have a handler function and assign this to the functions.
At the end, I would want that there should be no exception handling in functions but on exception the control should go to this handler function.
Class Foo:
def a():
try:
some_code
except Exception1 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
except Exception2 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
Similarly, other functions would have the same kind of exceptions. I wanted to have all these exception handling in a separate method. Just that the functions should hand over the control to exception handler function.
I can think of calling every function from the wrapper handler function. But that looked very weird to me.
Class Foo:
def process_func(func, *args, **kwargs):
try:
func(*args, **kwargs)
except Exception1 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
except Exception2 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
def a(*args, **kwargs):
some_code
Is there a better way for doing this?
You can define a function decorator:
def process_func(func):
def wrapped_func(*args, **kwargs):
try:
func(*args, **kwargs)
except ...
return wrapped_func
And use as:
#process_func
def func(...):
...
So that func(...) is equivalent to process_func(func)(...), and errors are handled inside wrapped_func.

wrapping class method in try / except using decorator

I have a general purpose function that sends info about exceptions to an application log.
I use the exception_handler function from within methods in classes. The app log handler that is passed into and called by the exception_handler creates a JSON string that is what actually gets sent to the logfile. This all works fine.
def exception_handler(log, terminate=False):
exc_type, exc_value, exc_tb = sys.exc_info()
filename, line_num, func_name, text = traceback.extract_tb(exc_tb)[-1]
log.error('{0} Thrown from module: {1} in {2} at line: {3} ({4})'.format(exc_value, filename, func_name, line_num, text))
del (filename, line_num, func_name, text)
if terminate:
sys.exit()
I use it as follows: (a hyper-simplified example)
from utils import exception_handler
class Demo1(object):
def __init__(self):
self.log = {a class that implements the application log}
def demo(self, name):
try:
print(name)
except Exception:
exception_handler(self.log, True)
I would like to alter exception_handler for use as a decorator for a large number of methods, i.e.:
#handle_exceptions
def func1(self, name)
{some code that gets wrapped in a try / except by the decorator}
I've looked at a number of articles about decorators, but I haven't yet figured out how to implement what I want to do. I need to pass a reference to the active log object and also pass 0 or more arguments to the wrapped function. I'd be happy to convert exception_handler to a method in a class if that makes things easier.
Such a decorator would simply be:
def handle_exceptions(f):
def wrapper(*args, **kw):
try:
return f(*args, **kw)
except Exception:
self = args[0]
exception_handler(self.log, True)
return wrapper
This decorator simply calls the wrapped function inside a try suite.
This can be applied to methods only, as it assumes the first argument is self.
Thanks to Martijn for pointing me in the right direction.
I couldn't get his suggested solution to work but after a little more searching based on his example the following works fine:
def handle_exceptions(fn):
from functools import wraps
#wraps(fn)
def wrapper(self, *args, **kw):
try:
return fn(self, *args, **kw)
except Exception:
exception_handler(self.log)
return wrapper

Categories