Last active
November 28, 2023 20:02
-
-
Save malcolmgreaves/0f487fd07fdfe3461260e0832ee35beb to your computer and use it in GitHub Desktop.
Exploring patterns for validating function arguments in Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
$ 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