Asserts
Collection of utilities used to introspect code
Decorator detection
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 checkmode: The inspection mode - 'static' (no execution) or 'hybrid' (dynamic inspection)
Raises
ValueErrorifmodeis 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 checkmode: The inspection mode - 'static' (no execution) or 'hybrid' (dynamic inspection)
Raises
ValueErrorifmodeis 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
TypeErrorif 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
TypeErrorif 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 |