Note: I know that decorators with optional argument contain three nested function. But optional argument here is function itself. Please go through the complete post before you mark this as duplicate. I already tried all the tricks for decorators with optional argument, but I could not found any that takes function as argument.
I am having a decorator for wrapping error:
def wrap_error(func):
    from functools import wraps
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            import sys
            exc_msg = traceback.format_exception(*sys.exc_info())
            raise MyCustomError(exc_msg)
    return wrapper
If some function raises any exception, it wraps the error. This wrapper is used like:
@wrap_error
def foo():
    ...
Now I want to modify this wrapper with additional callback function which will be optional. And I want this wrapper to be used as:
@wrap_error
def foo():
    ...
@wrap_error(callback)
def foo():
    ...
I know how to write decorators with optional arguments (in case passed argument is not function, based on isfunction(func) check within wrapper). But I am not sure how to handle this case.
Note: I can not use @wrap_error() instead of @wrap_error. This wrapper is used in multiple number of packages, and it is not possible to update the change in all
Here is the blocker: Consider the wrapper as:
@wrap_error(callback)               --->       foo = wrap_error(callback)(foo)
def foo():
    ...
So, by the time wrap_error(foo) is executed, we do not know whether there will be any callback function for execution after that or not (in case we use just @wrap_error instead of @wrap_error(callback)).
If there is no (callback), wrapping function within wrap_error will return func(*args. **kwargs) so that I can raise exception. Else we have to return func so that it is called at next step, and if func() raises the exception, we call callback() in except block.
 
     
    