Skip to content

Instantly share code, notes, and snippets.

@malcolmgreaves
Last active November 28, 2023 20:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save malcolmgreaves/0f487fd07fdfe3461260e0832ee35beb to your computer and use it in GitHub Desktop.
Save malcolmgreaves/0f487fd07fdfe3461260e0832ee35beb to your computer and use it in GitHub Desktop.
Exploring patterns for validating function arguments in Python.
"""
$ python testing_args_easier_debug_messages.py.py
Hello world, I can't believe you've have 42 birthdays! I hope you find time for crafting soon!
Hello universe, I can't believe you've have 117.0 birthdays! I hope you find time for crafting soon!
ValueError: Need positive numbers, not: age=whoops
ValueError: Need positive numbers, not: age=-1
ValueError: Need non-empty strings, not: name=
ValueError: Need non-empty strings, not: hobby=None
"""
from typing import Callable, Generic, List, Tuple, Type, TypeVar, Union
T = TypeVar('T')
E = TypeVar('E', bound=Exception)
class conditional(Generic[T, E]):
def __init__(self, msg: str, catching: Callable[[T], bool], exception: Type[E] = ValueError) -> None:
self.msg = msg
self.catching = catching
self.exception = exception
def __call__(self, **kwargs) -> None:
"""Alias for `validate`."""
self.validate(**kwargs)
def catch(self, **kwargs: T) -> List[Tuple[str, T]]:
"""Returns each named argument where `catching` evaluates to true."""
passes_test = []
for arg, value in kwargs.items():
if self.catching(value):
passes_test.append((arg, value))
return passes_test
def validate(self, **kwargs: T) -> None:
"""Raises a new `exception` if any argument evaluates to true under the `catching` function."""
passes_test = self.catch(**kwargs)
if len(passes_test) > 0:
s = ','.join([f"{a}={v}" for a, v in passes_test])
raise self.exception(f"{self.msg}, not: {s}")
def require_non_empty_str(**kwargs: str) -> None:
conditional("Need non-empty strings", lambda v: not isinstance(v, str) or len(v) == 0)(**kwargs)
def require_positive(**kwargs: Union[int, float]) -> None:
conditional("Need positive numbers", lambda v: not isinstance(v, (float, int)) or v < 1)(**kwargs)
def example_func_for_validation(age: int, name: str, hobby: str):
require_non_empty_str(name=name, hobby=hobby)
require_positive(age=age)
print(f"Hello {name}, I can't believe you've have {age} birthdays! I hope you find time for {hobby} soon!")
if __name__ == "__main__":
def _test(**kwargs):
try:
example_func_for_validation(**kwargs)
except ValueError as e:
print(f"ValueError: {e}")
_test(age=42, name='world', hobby='crafting') # ok!
_test(age=117.0, name='universe', hobby='crafting') # ok!
_test(age='whoops', name='world', hobby='crafting') # ValueError: age=whoops
_test(age=-1, name='world', hobby='crafting') # ValueError: age=-1
_test(age=42, name='', hobby='crafting') # ValueError: name=""
_test(age=42, name='world', hobby=None) # ValueError: hobby=None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment