Asserts

Collection of utilities used to introspect code


Decorator detection

Note:
All of the listed decorators detection are not fully type-safe due to python non typed nature even if the library try to enforce it.

This should not pose a problem or have any real impact, as fooling the detection requires the developer to intentionally write misleading code. For details take a look at known limitations.

is_decorator()

Signature

def is_decorator(func: Callable, mode: str = "static") -> bool

Description

Returns True if the function is a decorator.

  • In 'static' mode, no execution is performed.
  • In 'hybrid' mode, dynamically inspect the callable, should not be used with side-effecting functions.

Parameters

  • func: The callable to check
  • mode: The inspection mode - 'static' (no execution) or 'hybrid' (dynamic inspection)

Raises

  • ValueError if mode is invalid

Returns

  • bool: True if the function is a decorator, False otherwise

Example use

def my_decorator(func): return func

>>> is_decorator(my_decorator)
True

def not_a_decorator(func): return None

>>> is_decorator(not_a_decorator)
False

is_decorator_factory()

Signature

def is_decorator_factory(func: Callable, mode: str = "static") -> bool

Description

Returns True if the function is a decorator factory.

  • In 'static' mode, no execution is performed.
  • In 'hybrid' mode, dynamically inspect the callable, should not be used with side-effecting functions.

Parameters

  • func: The callable to check
  • mode: The inspection mode - 'static' (no execution) or 'hybrid' (dynamic inspection)

Raises

  • ValueError if mode is invalid

Returns

  • bool: True if the function is a decorator factory, False otherwise

Example use

def my_decorator_factory(param):
    def inner(func):
        return func
    return inner

>>> is_decorator_factory(my_decorator_factory)
True

def my_decorator_factory_not_none(param) -> Callable:
    assert None != param
    def inner(func):
        return func
    return inner

>>> is_decorator_factory(my_decorator_factory_not_none)
True

def not_a_decorator_factory(func): return None

>>> is_decorator_factory(not_a_decorator)
False

Introspection

unwarp_function()

Signature

def unwarp_function(func: Callable) -> Callable

Description

Unwraps a function if it is a closure wrapping another function. If the function is not a closure, it returns the function itself.

Parameters

  • func: The callable to unwrap

Raises

  • TypeError if the provided argument is not callable

Returns

  • Callable: The unwrapped function or the original function if not wrapped

Example use

def bad_decorator_factory(param):
    def decorator(func):
        # Not using @wraps, which is a common mistake
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

@bad_decorator_factory("test")
def my_func(a, b : str, c : str | int):
    pass

print(my_func.__name__)
>>> wrapper

print(unwarp_function(my_func).__name__)
>>> my_func

get_signature_arg_types()

Signature

def get_signature_arg_types(func: Callable) -> dict[str, tuple[type, ...]]

Description

Produce a dictionary of parameter names and their types from the function's signature, even if wrapped or incorrectly warped. If a parameter has no type hint, it defaults to Any.

Parameters

  • func: The callable to analyze

Raises

  • TypeError if the provided argument is not callable

Returns

  • dict[str, tuple[type, ...]]: Dictionary mapping parameter names to their type annotations

Example use

def bad_decorator_factory(param):
    def decorator(func):
        # Not using @wraps, which is a common mistake
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

@bad_decorator_factory("test")
def my_func(a, b : str, c : str | int):
    pass

print(get_signature_arg_types(my_func))
>>> {'a': (typing.Any,), 'b': (<class 'str'>,), 'c': (<class 'str'>, <class 'int'>)}

Known limitations

Decorator detection: Static vs. Hybrid

The static mode of decorator detection relies solely on analyzing the function’s source code using AST parsing. This is inherently limited by the nature of Python’s static analysis capabilities: it cannot fully understand runtime behavior. As a result, it can be fooled by functions that look like decorators structurally but are not actually decorators.

In contrast, the hybrid mode dynamically invokes the function to verify its behavior at runtime. This makes it more accurate and resistant to false positives, since it observes actual function outputs. However, because hybrid mode executes user code, it risks triggering unwanted side effects if the functions perform I/O, mutate state, or depend on external conditions.

Due to this trade-off, the library defaults to static mode, prioritizing safety by avoiding code execution while accepting the possibility of false positives. Users who need stronger guarantees and understand the risks can explicitly enable hybrid mode with the following lines:

import decoguard.config
decoguard.config.DEFAULT_DECORATOR_CHECKS_MODE = "hybrid"

Summary:

Mode Calls function? Susceptible to false positives? Side effects risk?
Static No Yes No
Hybrid Yes No Yes