1def check_types(value, expected_types, error_message):
2 """Check the type and print error message."""
3 if not isinstance(value, expected_types):
4 types_str = ", ".join(str(t) for t in expected_types)
5 print(error_message.format(types_str))
6 return False
7 return True
8
9def check_input_types(args, kwargs, expected_types):
10 """Check the types of args and kwargs."""
11 valid = True
12 for arg in args:
13 if not check_types(arg, expected_types, "Invalid input arguments, expected {}!"):
14 valid = False
15
16 for key, value in kwargs.items():
17 if not check_types(value, expected_types, f"Invalid input argument '{key}', expected {{}}!"):
18 valid = False
19
20 return valid
21
22def type_check(mode):
23 def decorator(*expected_types):
24 def wrapper(func):
25 def inner(*args, **kwargs):
26 if mode == "in":
27 if not check_input_types(args, kwargs, expected_types):
28 return None
29 result = func(*args, **kwargs)
30
31 if mode == "out":
32 if not check_types(result, expected_types, "Invalid output value, expected {}!"):
33 return result
34
35 return result
36 return inner
37 return wrapper
38 return decorator
39
40
41@type_check("in")(int, float)
42@type_check("out")(int, float)
43def power(num, power):
44 return num ** power
45
46print(power(6j, 2))
47@type_check("in")(str)
48def concatenate(*strings, separator=' beep boop '):
49 return separator.join(strings)
50
51print(concatenate(5, '6'))
Invalid input arguments, expected <class 'int'>, <class 'float'>!
None
Invalid input arguments, expected <class 'str'>!
None
FFF.
======================================================================
FAIL: test_check_both (test.TestTypeCheck.test_check_both)
The decorator should report invalid "in" and "out" together.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.12/unittest/mock.py", line 1390, in patched
return func(*newargs, **newkeywargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/test.py", line 98, in test_check_both
mock_print.assert_has_calls(
File "/usr/lib/python3.12/unittest/mock.py", line 981, in assert_has_calls
raise AssertionError(
AssertionError: Calls not found.
Expected: [call("Invalid input arguments, expected <class 'float'>!"),
call("Invalid output value, expected <class 'int'>!")]
Actual: [call("Invalid input arguments, expected <class 'float'>!"),
call("Invalid input arguments, expected <class 'float'>!"),
call("Invalid input argument 'pirates', expected <class 'float'>!"),
call("Invalid output value, expected <class 'int'>!")]
======================================================================
FAIL: test_check_decorated_exception (test.TestTypeCheck.test_check_decorated_exception)
The decorator should not supress any exceptions raised.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.12/unittest/mock.py", line 1390, in patched
return func(*newargs, **newkeywargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/test.py", line 110, in test_check_decorated_exception
with self.assertRaises(ZeroDivisionError):
AssertionError: ZeroDivisionError not raised
======================================================================
FAIL: test_check_in (test.TestTypeCheck.test_check_in)
The decorator should report invalid "in" arguments.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.12/unittest/mock.py", line 1390, in patched
return func(*newargs, **newkeywargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/test.py", line 43, in test_check_in
self.assertEqual(mock_print.call_count, 1)
AssertionError: 2 != 1
----------------------------------------------------------------------
Ran 4 tests in 0.003s
FAILED (failures=3)