Controlling Failure¶
When dealing with permissioning, failure is an expected and desired outcome. And Flask-Allows provides several measures to deal with this failure.
Throwing an exception¶
The first measure is the ability to configure requirements runners to throw an
exception. By default this will be werkzeug’s Forbidden exception. However,
this can be set to be any exception type or specific instance. The easiest
way to set this is through the Allows
constructor:
class PermissionError(Exception):
def __init__(self):
super().__init__("I'm sorry Dave, I'm afraid I can't do that")
allows = Allows(throws=PermissionError)
# alternatively
allows = Allows(throws=PermissionError())
If a particular exception is desirable most of but not all of the time, an exception type or instance can be provided each requirements runner:
# to Permission helper
Permission(SomeRequirement(), throws=PermissionError)
# to decorators
@allows.requires(SomeRequirement(), throws=PermissionError)
@requires(SomeRequirement(), throws=PermissionError)
When an exception type or instance is provided to a requirements runner, it takes precedence over the type or instance registered on the extension object. If one is not supplied to a requirement runner, it uses the type or instance registered on the extension object.
Failure Callback¶
Another way to handle failure is providing an on_fail
argument that will be
invoked when failure happens. The value provided to on_fail
doesn’t have to
be a callable, so any value is appropriate. If the value provided is a callable
it should be prepared to accept any arbitrary arguments that were provided when
the requirement runner that was invoked.
To add a failure callback, it can be provided to the
Allows
constructor:
def flash_failure_message(*args, **kwargs):
flash("I'm sorry Dave, I'm afraid I can't do that", "error")
allows = Allows(on_fail=flash_failure_message)
If on_fails
return a non-None
value, that will be used as the return
value from the requirement runner. However, if a None
is returned from the
callback, then the configured exception is raised instead. In the above example,
since a None
is implicitly returned from the callback, a werkzeug Forbidden
exception would be raised from any requirements runners.
An example of returning a value from the callback would be returning a redirect to another page:
def redirect_to_home(*args, **kwargs):
flash("I'm sorry Dave, I'm afraid I can't do that", "error")
return redirect(url_for("index"))
However, any value can be returned from this wrapper.
Note
When used with the Permission
helper,
the callback will be invoked with no arguments and the return value isn’t
considered.
Danger
When using on_fail
with route decorators, be sure to return an
appropriate value for Flask to turn into a response.
Similar to exception configuration, on_fail
can be passed to any requirements
runner:
# to Permission helper
Permission(SomeRequirement(), on_fail=flash_failure_message)
# to decorators
@allow.requires(SomeRequirement(), on_fail=redirect_to_home)
@requires(SomeRequirement(), on_fail=redirect_to_home)
When on_fail
is passed to a requirements runner, it takes precedence over
the on_fail
registered on the extension object. If an on_fail
isn’t
provided then the one registered on the extension object is used.