import operator
from abc import ABCMeta, abstractmethod
from functools import wraps
from flask import request
from flask._compat import with_metaclass
from .allows import _call_requirement
from .overrides import current_overrides
__all__ = (
"Requirement",
"ConditionalRequirement",
"wants_request",
"C",
"Or",
"And",
"Not",
)
[docs]class Requirement(with_metaclass(ABCMeta)):
"""
Base for object based Requirements in Flask-Allows. This is quite
useful for requirements that have complex logic that is too much to fit
inside of a single function.
"""
[docs] @abstractmethod
def fulfill(self, user, request=None):
"""
Abstract method called to verify the requirement against the current
user and request.
.. versionchanged:: 0.5.0
Passing request is now deprecated, pending removal in version 1.0.0
:param user: The current identity
:param request: The current request.
"""
return NotImplemented
def __call__(self, user, request):
return _call_requirement(self.fulfill, user, request)
def __repr__(self):
return "<{}()>".format(self.__class__.__name__)
[docs]class ConditionalRequirement(Requirement):
"""
Used to combine requirements together in ways other than all-or-nothing,
such as with an or-reducer (any requirement must be True)::
from flask_allows import Or
requires(Or(user_is_admin, user_is_moderator))
or negating a requirement::
from flask_allows import Not
requires(Not(user_logged_in))
Combinations may also nested::
Or(user_is_admin, And(user_is_moderator, HasPermission('view_admin')))
Custom combinators may be built by creating an instance of ConditionalRequirement
and supplying any combination of its keyword parameters
This class is also exported under the ``C`` alias.
:param requirements: Collection of requirements to combine into
one logical requirement
:param op: Optional, Keyword only. A binary operator that accepts two
booleans and returns a boolean.
:param until: Optional, Keyword only. A boolean to short circuit on (e.g.
if provided with True, then the first True evaluation to return from a
requirement ends verification)
:param negated: Optional, Keyword only. If true, then the
ConditionalRequirement will return the opposite of what it actually
evaluated to (e.g. ``ConditionalRequirement(user_logged_in, negated=True)``
returns False if the user is logged in)
"""
def __init__(self, *requirements, **kwargs):
self.requirements = requirements
self.op = kwargs.get("op", operator.and_)
self.until = kwargs.get("until")
self.negated = kwargs.get("negated")
[docs] @classmethod
def And(cls, *requirements):
"""
Short cut helper to construct a combinator that uses
:meth:`operator.and_` to reduce requirement results and stops
evaluating on the first False.
This is also exported at the module level as ``And``
"""
return cls(*requirements, op=operator.and_, until=False)
[docs] @classmethod
def Or(cls, *requirements):
"""
Short cut helper to construct a combinator that uses
:meth:`operator.or_` to reduce requirement results and stops evaluating
on the first True.
This is also exported at the module level as ``Or``
"""
return cls(*requirements, op=operator.or_, until=True)
[docs] @classmethod
def Not(cls, *requirements):
"""
Shortcut helper to negate a requirement or requirements.
This is also exported at the module as ``Not``
"""
return cls(*requirements, negated=True)
[docs] def fulfill(self, user, request):
reduced = None
requirements = self.requirements
# can't use is because is a proxy
if current_overrides != None: # noqa: E711
requirements = (r for r in requirements if r not in current_overrides)
for r in requirements:
result = _call_requirement(r, user, request)
if reduced is None:
reduced = result
else:
reduced = self.op(reduced, result)
if self.until == reduced:
break
if reduced is not None:
return not reduced if self.negated else reduced
return True
def __and__(self, require):
return self.And(self, require)
def __or__(self, require):
return self.Or(self, require)
def __invert__(self):
return self.Not(self)
def __repr__(self):
additional = []
for name in ["op", "negated", "until"]:
value = getattr(self, name)
if not value:
continue
additional.append("{0}={1!r}".format(name, value))
if additional:
additional = " {}".format(", ".join(additional))
else:
additional = ""
return "<{0} requirements={1!r}{2}>".format(
self.__class__.__name__, self.requirements, additional
)
def __eq__(self, other):
return (
isinstance(other, ConditionalRequirement)
and self.op == other.op
and self.until == other.until
and self.negated == other.negated
and self.requirements == other.requirements
)
def __hash__(self):
return hash((self.requirements, self.op, self.until, self.negated))
(C, And, Or, Not) = (
ConditionalRequirement,
ConditionalRequirement.And,
ConditionalRequirement.Or,
ConditionalRequirement.Not,
)
[docs]def wants_request(f):
"""
Helper decorator for transitioning to user-only requirements, this aids
in situations where the request may be marked optional and causes an
incorrect flow into user-only requirements.
This decorator causes the requirement to look like a user-only requirement
but passes the current request context internally to the requirement.
This decorator is intended only to assist during a transitionary phase
and will be removed in flask-allows 1.0
See: :issue:`20,27`
"""
@wraps(f)
def wrapper(user):
return f(user, request)
return wrapper