In [1]:
import os
import time
from functools import wraps
from typing import Any, Callable
In [2]:
def runtime_calculator_without_wraps(func: Callable) -> Callable:
    """Calculate runtime of a function without @wraps"""

    def wrapper(*args, **kwargs):
        """wrapper without @wraps"""
        
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Function `{func.__name__}` executed in {execution_time:.4f} seconds \n")
        
        return result

    return wrapper


@runtime_calculator_without_wraps
def print_anything_without_wraps(anything: Any) -> None:
    """Print anything without @wraps

    Args:
        anything (Any): anything to print
    """
    
    time.sleep(1)
    
    print(anything)

# This returns the `wrapper` function in the decorator, there is no problem with the result
print(print_anything_without_wraps.__name__)
print(print_anything_without_wraps.__doc__)
print_anything_without_wraps("\nHello World")
wrapper
wrapper without @wraps

Hello World
Function `print_anything_without_wraps` executed in 1.0060 seconds 

In [3]:
def runtime_calculator_with_wraps(func: Callable) -> Callable:
    """Calculate runtime of a function with @wraps"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        """wrapper with @wraps"""
        
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Function `{func.__name__}` executed in {execution_time:.4f} seconds \n")
        
        return result

    return wrapper


@runtime_calculator_with_wraps
def print_anything_with_wraps(anything: Any) -> None:
    """Print anything with @wraps

    Args:
        anything (Any): anything to print
    """
    
    time.sleep(1)
    
    print(anything)

# This returns the original function, the result is the same as the one without @wraps
print(print_anything_with_wraps.__name__)
print(print_anything_with_wraps.__doc__)
print_anything_with_wraps("\nHello World")
print_anything_with_wraps
Print anything with @wraps

    Args:
        anything (Any): anything to print
    

Hello World
Function `print_anything_with_wraps` executed in 1.0116 seconds 

In [4]:
def set_and_remove_envvar(varname: str, value: str) -> Callable:
    """Set and remove an environment variable"""

    def decorator(func: Callable) -> Callable:
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            """wrapper"""
            
            os.environ[varname] = value
            result = func(*args, **kwargs)
            del os.environ[varname]
            return result

        return wrapper

    return decorator


def foobar_without_decorator():    
    foo = os.getenv("foo")
    assert foo is None
    print(f"foo is {foo}...")


@set_and_remove_envvar(varname="foo", value="bar")
def foobar_with_decorator():
    foo = os.getenv("foo")
    assert foo is not None
    print(f"foo is {foo}!")
        
foobar_without_decorator()
foobar_with_decorator()

# After terminating the function, "foo" for the environment variable is removed
print(os.getenv("foo"))
assert os.getenv("foo") is None
foo is None...
foo is bar!
None