Skip to content

Commit

Permalink
feat(checkers): add a checker for callable objects (#29)
Browse files Browse the repository at this point in the history
Add a new checker, `sghi.utils.checkers.ensure_callable`, to assert that
the provided value is a callable object (some kind of function).
  • Loading branch information
kennedykori committed Mar 31, 2024
1 parent 3248a13 commit 3b70509
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/sghi/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Common utilities used throughout SGHI projects."""

from .checkers import (
ensure_callable,
ensure_greater_or_equal,
ensure_greater_than,
ensure_instance_of,
Expand All @@ -15,6 +16,7 @@
from .others import future_succeeded, type_fqn

__all__ = [
"ensure_callable",
"ensure_greater_or_equal",
"ensure_greater_than",
"ensure_instance_of",
Expand Down
23 changes: 23 additions & 0 deletions src/sghi/utils/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@
# =============================================================================


def ensure_callable(value: _T, message: str = "A callable is required.") -> _T:
"""Check that the given value is a callable object (some kind of function).
A callable should have the same semantics as those defined by the
``builtin.callable`` function to qualify. That is, it should be function or
method, a class or an instance of a class with a ``__call__`` method.
If ``value`` is NOT a callable, then a :exc:`ValueError` is raised; else
``value`` is returned as is.
:param value: The object to check if it is a callable.
:param message: An optional error message to be shown when ``value`` is NOT
a callable.
:return: ``value`` if it is a callable.
:raise ValueError: If the given ``value`` is NOT a callable.
"""
if not callable(value):
raise ValueError(message)
return value


def ensure_greater_or_equal(
value: _CT,
base_value: Comparable,
Expand Down
42 changes: 42 additions & 0 deletions test/sghi/utils_tests/checkers_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sghi.app
from sghi.config import Config, ConfigProxy
from sghi.utils import (
ensure_callable,
ensure_greater_or_equal,
ensure_greater_than,
ensure_instance_of,
Expand All @@ -23,6 +24,47 @@
from sghi.typing import Comparable


def test_ensure_callable_return_value_on_valid_input() -> None:
"""
:func:`ensure_callable` should return the input value if the given
``value`` is a callable.
"""

class _Callable:
def __call__(self, *args, **kwargs) -> None: ...

a_callable = _Callable()

assert ensure_callable(callable) is callable
assert ensure_callable(type_fqn) is type_fqn
assert ensure_callable(_Callable) is _Callable
assert ensure_callable(a_callable) is a_callable


def test_ensure_callable_fails_on_invalid_input() -> None:
"""
:func:`ensure_callable` should raise a ``ValueError`` when the given
``value`` is not a callable.
"""
inputs: Iterable = ("", 45, None, [])

# With default message
default_msg: str = "A callable is required."
for value in inputs:
with pytest.raises(ValueError, match=default_msg) as exp_info:
ensure_callable(value)

assert exp_info.value.args[0] == default_msg

# Test with a custom message
custom_msg: str = "Would you please provide a callable."
for value in inputs:
with pytest.raises(ValueError, match=custom_msg) as exp_info:
ensure_callable(value, message=custom_msg)

assert exp_info.value.args[0] == custom_msg


def test_ensure_greater_or_equal_return_value_on_valid_input() -> None:
"""
:func:`ensure_greater_or_equal` should return the input value if the given
Expand Down

0 comments on commit 3b70509

Please sign in to comment.