Source code for flask_allows.requirements

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