Whenever I need to apply some runtime constraints on a value while building an API, I
usually compare the value to an expected range and raise a `ValueError`

if it's not
within the range. For example, let's define a function that throttles some fictitious
operation. The `throttle`

function limits the number of times an operation can be
performed by specifying the `throttle_after`

parameter. This parameter defines the
number of iterations after which the operation will be halted. The `current_iter`

parameter tracks the current number of times the operation has been performed. Here's
the implementation:

```
# src.py
def throttle(current_iter: int, throttle_after: int = -1) -> None:
"""
The value of 'throttle_after' must be -1 or an integer
greater than 0. Here, -1 means no throttling, and 'n'
means that the function will throttle some operation
after 'n' iterations.
The `current_iter` parameter denotes the current iteration
of some operation. When 'current_iter > throttle_after' this
function will throttle the operation.
"""
# Return early if 'throttle_after=-1'.
if throttle_after == -1:
print("No throttling.")
return
# Ensure 'current_iter' is a positive integer.
if not (isinstance(current_iter, int) and current_iter >= 0):
raise ValueError(
"Value of 'current_iter' must be a" " positive integer."
)
# Ensure 'throttle_after' is a non-zero positive integer.
if not (isinstance(throttle_after, int) and throttle_after > 0):
raise ValueError(
"Value of 'throttle_after' must be either -1 or an"
" integer greater than 0."
)
# Do the throttling.
if current_iter > throttle_after:
print(f"Thottling after {throttle_after} iteration(s).")
return
if __name__ == "__main__":
# Prints 'Throttling after 1 iteration(s).'
throttle(current_iter=2, throttle_after=1)
```

We return early if the value of `throttle_after`

is -1. Otherwise, we check to see if
`current_iter`

is a positive integer and `throttle_after`

is a non-zero positive
integer. If not, we raise a `ValueError`

. When the parameters pass these checks then we
compare `current_iter`

with `throttle_after`

. If the value of `current_iter`

exceeds
that of the `throttle_after`

parameter, we throttle the operation.

While this works fine, recently, I've started to use `assert`

to replace the
*conditionals with ValueError* pattern. It works as follows:

```
# src.py
def throttle(current_iter: int, throttle_after: int = -1) -> None:
# Return early if 'throttle_after=-1'.
if throttle_after == -1:
print("No throttling.")
return
# Ensure 'current_iter' is a positive integer.
assert (
isinstance(current_iter, int) and current_iter >= 0
), "Value of 'current_iter' must be a positive integer."
# Ensure 'throttle_after' is a non-zero positive integer.
assert isinstance(throttle_after, int) and throttle_after > 0, (
"Value of 'throttle_after' must be either -1 or an "
" integer greater than 0."
)
# Do the throttling.
if current_iter > throttle_after:
print(f"Thottling after {throttle_after} iterations.")
return
if __name__ == "__main__":
# AssertionError: Value of 'current_iter' must be a positive
# integer.
throttle(current_iter=-2, throttle_after=1)
```

So, instead of using the `if not expression ... raise ValueError`

pattern, we can
leverage `assert expression, "Error message"`

pattern. In the latter case, `assert`

will
raise `AssertionError`

with the "Error message" if the expression evaluates to a falsy
value. Otherwise, the statement will remain silent and allow the execution to move
forward.

This is more succinct and makes the code flatter. I've no idea why I haven't started using it earlier and this piece of code in the Starlette repository jolted my brain. Eh bien, better late than never, I guess.

## Breadcrumbs¶

After this blog was published, several people mentioned on Twitter that the second
approach has a small caveat. Python has a flag that allows you to disable `assert`

statements in a script. You can disable the assertions in the snippet above by running
the script with the `-OO`

flag:

```
python -00 src.py
```

Removing assert statements will disable the constraints needed for the second `throttle`

function to work, which could lead to unexpected behavior or even subtle bugs. However,
I see this being used frequently in frameworks like Starlette and FastAPI. Also,
from my experience, using assertions is much more common than running production code
with the optimization flag.