Assert
Add simple boolean checks on your parameters or return-values.
Simple example
Below is the simple example of a constrained division-function.
The example
from typing_exe.annotations import Assert
from typing_exe.decorators import execute_annotations, cleanup_annotations
@cleanup_annotations
@execute_annotations
def divide(
a: Assert[lambda a: a >= 0],
b: Assert[float, lambda b: b != 0]
) -> Assert[lambda r, a, b: r < a if b > 1 else r >= a]:
return a / b
Explanation
What happens when divide
is called?
- The first
Assert
is checked. Ifa
is smaller than 0, aValueError
is raised - The second
Assert
is checked. Ifb
is equal to 0, aValueError
is raised - The function-body is executed and the result of
a / b
calculated - Its result is checked for plausibility by the third
Assert
- The result is returned
Description
As the two typehints in the example above show, the first entry can either be a typehint, or an assertion. All other entries are assertions (an arbitrary number of them).
The acceptable forms are:
from typing_exe.annotations import Assert
# 1. typehint and assertions
Assert[<typehint>, <assertion1>, <assertion2>, ...]
# 2. only assertions
Assert[<assertion1>, <assertion2>, ...]
The typehint will be ignored by Assert. Its purpose is twofold: Firstly, it helps readability. Secondly, when @execute_annotations is paired with @cleanup_annotations, only that typehint will be left in the function's annotations, so that the function can be used properly by other packages such as strongtyping.
The assertions are not in the form of assert
-statements but in the form of functions that
take the parameter and return a boolean value. If that boolean value is False
, a ValueError
will be raised (this only works if your function, divide
in the example above, is decorated with
@execute_annotations
).
It is also possible to make comparisons with other parameters by simply giving your assertion-function
more than one parameter, where the first parameter is assumed to be the one that is annotated,
while the others are the other parameters. It is important that those parameters are called the
same in both the assertion-function (the lambda in the return-annotation in the example) and
the annotated function (divide
in the example above). The name of the parameter itself in the
assertion-function is irrelevant but should, for readability, usually be the same as the parameter
that is annotated by this assertion-function.
For example, the following works:
from typing_exe.annotations import Assert
def foo(a, b: Assert[lambda whatever, a: whatever > a]):
...
But this doesn't:
from typing_exe.annotations import Assert
def foo(a, b: Assert[lambda b, whatever: b > whatever]):
...
Good form would be the following:
from typing_exe.annotations import Assert
def foo(a, b: Assert[lambda b, a: b > a]):
...
Of course, the assertion-functions don't have to be lambdas.