Source code for carpet_concentrations.attrs_utils
"""
Tools for helping with :mod:`attrs`, particularly validators
"""
from __future__ import annotations
from functools import wraps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Callable, TypeVar
import attr
T = TypeVar("T")
[docs]def add_attrs_context(
original: Callable[[Any, attr.Attribute[Any], T], None]
) -> Callable[[Any, attr.Attribute[Any], T], None]:
"""
Decorate function with a ``try...except`` to add the :mod:`attrs` context
This means that the information about what attribute was being set and
what value it was passed is also shown to the user
Parameters
----------
original
Function to decorate
Returns
-------
Decorated function
Notes
-----
Only works with Python 3.11 and above. For other Python versions, the raw
error is simply shown instead
"""
@wraps(original)
def with_attrs_context(
instance: Any,
attribute: attr.Attribute[Any],
value: T,
) -> None:
try:
original(instance, attribute, value)
except Exception as exc:
if hasattr(exc, "add_note"):
exc.add_note(
"\nError raised while initialising attribute "
f"``{attribute.name}`` of ``{type(instance)}``. "
f"\nValue provided: {value}"
)
raise
return with_attrs_context
[docs]def make_attrs_validator_compatible_single_input(
func_to_wrap: Callable[[T], None],
) -> Callable[[Any, attr.Attribute[Any], T], None]:
"""
Create a function that is compatible with validation via :func:`attrs.field`
This assumes that the function you're wrapping only takes a single input.
Parameters
----------
func_to_wrap
Function to wrap
Returns
-------
Wrapped function, which can be used as a validator with
:func:`attrs.field`
"""
@add_attrs_context
@wraps(func_to_wrap)
def attrs_compatible(
instance: Any,
attribute: attr.Attribute[Any],
value: T,
) -> None:
func_to_wrap(value)
return attrs_compatible
[docs]def make_attrs_validator_compatible_value_instance_input(
func_to_wrap: Callable[[Any, T], None],
) -> Callable[[Any, attr.Attribute[Any], T], None]:
"""
Create a function that is compatible with validation via :func:`attrs.field`
This assumes that the function you're wrapping takes the instance and the
values as inputs.
Parameters
----------
func_to_wrap
Function to wrap
Returns
-------
Wrapped function, which can be used as a validator with
:func:`attrs.field`
"""
@add_attrs_context
@wraps(func_to_wrap)
def attrs_compatible(
instance: Any,
attribute: attr.Attribute[Any],
value: T,
) -> None:
func_to_wrap(instance, value)
return attrs_compatible