from contextlib import contextmanager
from functools import wraps
from werkzeug.local import LocalProxy, LocalStack
_override_ctx_stack = LocalStack()
__all__ = ("current_overrides", "Override", "OverrideManager")
@LocalProxy
def current_overrides():
"""
Proxy to the currently pushed override context.
"""
rv = _override_ctx_stack.top
if rv is None:
return None
return rv[1]
def _isinstance(f):
@wraps(f)
def check(self, other):
if not isinstance(other, Override):
return NotImplemented
return f(self, other)
return check
[docs]class Override(object):
"""
Container object that allows selectively disabling requirements.
Requirements can be disabled by passing them to the constructor or
by calling the ``add`` method. They can be re-enabled by calling the
``remove`` method. To check if a requirement is currently disabled, you
may call either ``is_overridden`` or use ``in``.
Override objects can be combined and compared to each other with the following
operators:
``+`` creates a new overide object by combining two others, the new
override overrides all requirements that both parents did.
``+=`` similar to ``+`` except it is an inplace update.
``-`` creates a new override instance by removing any overrides from
the first instance that are contained in the second instance.
``-=`` similar to ``-`` except it is an inplace update
``==`` compares two overrides and returns true if both have the same
disabled requirements.
``!=`` similar to ``==`` except returns true if both have different
disabled requirements.
"""
def __init__(self, *requirements):
self._requirements = set(requirements)
[docs] def add(self, requirement, *requirements):
"""
Adds one or more requirements to the override context.
"""
self._requirements.update((requirement,) + requirements)
[docs] def remove(self, requirement, *requirements):
"""
Removes one or more requirements from the override context.
"""
self._requirements.difference_update((requirement,) + requirements)
[docs] def is_overridden(self, requirement):
"""
Checks if a particular requirement is current overridden. Can also
be used as ``in``::
override = Override()
override.add(is_admin)
override.is_overridden(is_admin) # True
is_admin in override # True
"""
return requirement in self._requirements
def __contains__(self, other):
return self.is_overridden(other)
@_isinstance
def __add__(self, other):
requirements = self._requirements | other._requirements
return Override(*requirements)
@_isinstance
def __iadd__(self, other):
if len(other._requirements) > 0:
self.add(*other._requirements)
return self
@_isinstance
def __sub__(self, other):
requirements = self._requirements - other._requirements
return Override(*requirements)
@_isinstance
def __isub__(self, other):
if len(other._requirements) > 0:
self.remove(*other._requirements)
return self
@_isinstance
def __eq__(self, other):
return self._requirements == other._requirements
@_isinstance
def __ne__(self, other):
return not self == other
def __len__(self):
return len(self._requirements)
def __bool__(self):
return len(self) != 0
__nonzero__ = __bool__
def __repr__(self):
return "Override({!r})".format(self._requirements)
[docs]class OverrideManager(object):
"""
Used to manage the process of overriding and removing overrides.
This class shouldn't be used directly, instead use ``allows.overrides``
to access these controls.
"""
[docs] def push(self, override, use_parent=False):
"""
Binds an override to the current context, optionally use the
current overrides in conjunction with this override
If ``use_parent`` is true, a new override is created from the
parent and child overrides rather than manipulating either
directly.
"""
current = self.current
if use_parent and current:
override = current + override
_override_ctx_stack.push((self, override))
[docs] def pop(self):
"""
Pops the latest override context.
If the override context was pushed by a different override manager,
a ``RuntimeError`` is raised.
"""
rv = _override_ctx_stack.pop()
if rv is None or rv[0] is not self:
raise RuntimeError(
"popped wrong override context ({} instead of {})".format(rv, self)
)
@property
def current(self):
"""
Returns the current override context if set otherwise None
"""
try:
return _override_ctx_stack.top[1]
except TypeError:
return None
[docs] @contextmanager
def override(self, override, use_parent=False):
"""
Allows temporarily pushing an override context, yields the new context
into the following block.
"""
self.push(override, use_parent)
yield self.current
self.pop()