Several finance functions start with the same lines of checks (if amount <= 0: raise ...), and the checks drift apart as the functions change. Move them into a decorator factory validate(**rules):
@validate(amount=lambda a: a > 0, years=int, rate=lambda r: 0 <= r < 1)
def monthly_payment(amount, years, rate=0.05):
...
Each keyword of validate names a parameter of the decorated function, and its value is a rule:
- a class (such as
intorstr): the argument must be an instance of it, otherwise the call raisesTypeError; - any other callable: a predicate; the argument is good when
rule(value)is true, otherwise the call raisesValueError.
The message of both errors is "<function name>: bad value for <parameter>: <repr of value>", for example "monthly_payment: bad value for amount: -500". Rules apply to arguments however they are passed (positionally or by keyword), but only to arguments the caller passed: defaults are not checked. When several arguments break their rules, the error names the one whose parameter comes first in the function's signature. The original function runs only if every check passes. A call that does not fit the signature (a missing or unexpected argument) raises TypeError, as a plain call would.
A rule naming a parameter the function does not have raises TypeError when the decorator is applied. The decorated function keeps its __name__ and __doc__.
Setup helpers, available with Run: loan_quotes(cases) (each case is (args, kwargs)), first_bad_in_order(), only_given_arguments(), unknown_rule(), call_mistakes(), many_calls(n), and outcome(call) and raises(fn, *args), which report what a call returned or raised.
Examples
Input: loan_quotes([((12000, 5), {}), ((-500, 2), {}), ((1000, 2.5), {}), ((1000,), {"years": 1, "rate": 1.2})])
Output: [226.45, "ValueError: monthly_payment: bad value for amount: -500",
"TypeError: monthly_payment: bad value for years: 2.5",
"ValueError: monthly_payment: bad value for rate: 1.2"]
Constraints
- Up to
10**4calls per test. Rules never raise exceptions themselves.
Goals
- Write a decorator factory whose settings name the parameters of the decorated function
- Match positional and keyword arguments to parameter names with `inspect.signature`
- Fail loudly and precisely on bad input, before the function body runs