I have this pytest-powered test function that seems functional :
def test_settrace_timeout():
    TIMEOUT_DURATION = 3
    @settrace_timeout(TIMEOUT_DURATION)
    def function_to_timeout(n):
        sleep(n)
    with pytest.raises(TimeoutError):
        function_to_timeout(TIMEOUT_DURATION + 1)
    function_to_timeout(TIMEOUT_DURATION - 1)
It's meant to test whether the parametrized settrace_timeout decorator makes decorated functions raise a TimeoutError when it takes too long and doesn't make it raise it when it return soon enough.
In that test, I'm decorating a function that uses time.sleep.
It looks functional as a test. The tested decorator works and fails as expected and "sabotaging" it makes the test fail.
So I also tried this out of curiosity (and the test still works) :
As far as I know it should be nearly equivalent.
def test_settrace_timeout():
    TIMEOUT_DURATION = 3
    function_to_timeout = settrace_timeout(TIMEOUT_DURATION)(lambda n: sleep(n))
    with pytest.raises(TimeoutError):
        function_to_timeout(TIMEOUT_DURATION + 1)
    function_to_timeout(TIMEOUT_DURATION - 1)
And finally, I have this that should work (that's what I was expecting at least). It doesn't work though.
When I try to remove the lambda proxy function, and when I try to directly decorate the sleep function, it suddenly fails (doesn't raise the function when expected to).
def test_settrace_timeout():
    TIMEOUT_DURATION = 3
    function_to_timeout = settrace_timeout(TIMEOUT_DURATION)(sleep)  #  Syntax it suddenly fails with (no more raised exception).
    with pytest.raises(TimeoutError):
        function_to_timeout(TIMEOUT_DURATION + 1)
    function_to_timeout(TIMEOUT_DURATION - 1)
What is the reason why I have this different behaviour in this specific case? I'd like to understand the difference between the test functions.
Here is the decorator I'm trying to test :
def settrace_timeout(timeout_in_seconds):
    """
    sys.settrace() based timeout.
    Will raise TimeoutError.
    It's meant to be used with requests based methods.
    It's based on this SO answer :
    https://stackoverflow.com/a/71453648/3156085
    """
    def decorator(f):
        exception_class = TimeoutError
        f_name = f.__name__
        def function(*args, **kwargs):
            start = time.time()
            def trace_function(frame, event, arg):
                if time.time() - start > timeout_in_seconds:
                    log("[settrace_timeout] - Timeout occured in function {0}.".format(f_name))
                    raise exception_class("Timeout")
                else:
                    return trace_function
            try:
                sys.settrace(trace_function)
                log("[settrace_timeout] settrace enabled in function {0}.".format(f_name))
                return_value = f(*args, **kwargs)
            except exception_class as e:
                log("[settrace_timeout] exception {0} raised in function {1}.".format(e, f_name))
                raise
            finally:
                sys.settrace(None)
                log("[settrace_timeout] settrace disabled in function {0}.".format(f_name))
            return return_value
        return function
    return decorator