"""
Module for defining functionality that marks and handle Lite-Function
creation.
"""
__all__ = ["bind_lite_func"]
import functools
import inspect
from collections.abc import Callable
class _LiteFuncDict(dict):
"""
Dictionary of Lite-Function functionality bound to the parent
function. The dictionary key is a string indicating the name
the functionality was bound with and the dictionary value is a
string containing the fully qualified path of the original
functionality.
"""
# This is only to give __bound_lite_func__ a docstring.
[docs]
def bind_lite_func(lite_func, attrs: dict[str, Callable] | None = None):
"""
Decorator to bind a lightweight "lite" version of a formulary
function to the full formulary function, as well as any supporting
attributes.
Parameters
----------
lite_func: Callable
The lightweight function to be bound as the ``lite`` attribute
to the function being decorated.
attrs: Dict[str, Callable]
A dictionary where the key is a string defining the bound name
and the associated value is the functionality to be bound.
Examples
--------
.. autolink-skip:: section
.. code-block:: python
def foo_lite(x):
return x
def bar():
print("Supporting function.")
@bind_lite_func(
foo_lite,
attrs=[
("bar", bar),
],
)
def foo(x):
if not isinstance(x, float):
raise TypeError("Argument x can only be a float.")
return x
.. code-block:: pycon
>>> foo(5) # doctest: +SKIP
5
>>> foo.lite(5) # doctest: +SKIP
5
>>> foo.bar() # doctest: +SKIP
Supporting function.
Notes
-----
In addition to binding the functionality defined by the inputs, a
``__bound_lite_func__`` dunder is bound. This dunder is a
dictionary where a key is a string representing the bound name of
the bound functionality and the associated value is a string
representing the fully qualified path of the original bound
functionality.
"""
if attrs is None:
attrs = {}
elif not isinstance(attrs, dict):
raise TypeError(
f"Argument 'attrs' is a type '{type(attrs)}', expected a dictionary."
)
elif "lite" in attrs:
raise ValueError(
"Argument 'attr' can NOT define key 'lite', this is reserved for"
" the 'lite_func' argument."
)
if inspect.isbuiltin(lite_func) or not inspect.isfunction(lite_func):
raise ValueError("The given lite-function is not a user-defined function.")
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
__bound_lite_func__ = _LiteFuncDict()
attrs["lite"] = lite_func
for bound_name, attr in attrs.items():
# only allow functions or jitted functions
if not inspect.isfunction(attr):
raise ValueError(
f"Can not bind obj '{attr}' to function '{wrapper.__name__}'."
f" Only functions are allowed to be bound. Skipping."
)
# build origin name
if hasattr(attr, "__module__"):
modname = attr.__module__
else:
# assume attr is defined in the module the function being
# decorated in
modname = wrapper.__module__
origin = f"{modname}.{attr.__name__}"
__bound_lite_func__[bound_name] = origin
# bind
setattr(wrapper, bound_name, attr)
wrapper.__bound_lite_func__ = __bound_lite_func__
return wrapper
return decorator