# Logbook

# Source code for logbook._fallback

    """
        logbook._fallback
        ~~~~~~~~~~~~~~~~~

        Fallback implementations in case speedups is not around.

        :copyright: (c) 2010 by Armin Ronacher, Georg Brandl.
        :license: BSD, see LICENSE for more details.
    """
    from itertools import count

    from logbook.concurrency import (
        ContextVar,
        GreenletRLock,
        ThreadLock,
        context_get_ident,
        greenlet_get_ident,
        greenlet_local,
        is_context_enabled,
        is_gevent_enabled,
        thread_get_ident,
        thread_local,
    )
    from logbook.helpers import get_iterator_next_method

    _missing = object()
    _MAX_CONTEXT_OBJECT_CACHE = 256

    def group_reflected_property(name, default, fallback=_missing):
        """Returns a property for a given name that falls back to the
        value of the group if set.  If there is no such group, the
        provided default is used.
        """

        def _get(self):
            rv = getattr(self, "_" + name, _missing)
            if rv is not _missing and rv != fallback:
                return rv
            if self.group is None:
                return default
            return getattr(self.group, name)

        def _set(self, value):
            setattr(self, "_" + name, value)

        def _del(self):
            delattr(self, "_" + name)

        return property(_get, _set, _del)

    class _StackBound:
        def __init__(self, obj, push, pop):
            self.__obj = obj
            self.__push = push
            self.__pop = pop

        def __enter__(self):
            self.__push()
            return self.__obj

        def __exit__(self, exc_type, exc_value, tb):
            self.__pop()

    class StackedObject:
        """Baseclass for all objects that provide stack manipulation
        operations.
        """

        def push_greenlet(self):
            """Pushes the stacked object to the greenlet stack."""
            raise NotImplementedError()

        def pop_greenlet(self):
            """Pops the stacked object from the greenlet stack."""
            raise NotImplementedError()

        def push_context(self):
            """Pushes the stacked object to the context stack."""
            raise NotImplementedError()

        def pop_context(self):
            """Pops the stacked object from the context stack."""
            raise NotImplementedError()

        def push_thread(self):
            """Pushes the stacked object to the thread stack."""
            raise NotImplementedError()

        def pop_thread(self):
            """Pops the stacked object from the thread stack."""
            raise NotImplementedError()

        def push_application(self):
            """Pushes the stacked object to the application stack."""
            raise NotImplementedError()

        def pop_application(self):
            """Pops the stacked object from the application stack."""
            raise NotImplementedError()

        def __enter__(self):
            if is_gevent_enabled():
                self.push_greenlet()
            else:
                self.push_thread()
            return self

        def __exit__(self, exc_type, exc_value, tb):
            if is_gevent_enabled():
                self.pop_greenlet()
            else:
                self.pop_thread()

        def greenletbound(self, _cls=_StackBound):
            """Can be used in combination with the `with` statement to
            execute code while the object is bound to the greenlet.
            """
            return _cls(self, self.push_greenlet, self.pop_greenlet)

        def contextbound(self, _cls=_StackBound):
            """Can be used in combination with the `with` statement to
            execute code while the object is bound to the concurrent
            context.
            """
            return _cls(self, self.push_context, self.pop_context)

        def threadbound(self, _cls=_StackBound):
            """Can be used in combination with the `with` statement to
            execute code while the object is bound to the thread.
            """
            return _cls(self, self.push_thread, self.pop_thread)

        def applicationbound(self, _cls=_StackBound):
            """Can be used in combination with the `with` statement to
            execute code while the object is bound to the application.
            """
            return _cls(self, self.push_application, self.pop_application)

    class ContextStackManager:
        """Helper class for context objects that manages a stack of
        objects.
        """

        def __init__(self):
            self._global = []
            self._thread_context_lock = ThreadLock()
            self._thread_context = thread_local()
            self._greenlet_context_lock = GreenletRLock()
            self._greenlet_context = greenlet_local()
            self._context_stack = ContextVar("stack")
            self._cache = {}
            self._stackop = get_iterator_next_method(count())

        def iter_context_objects(self):
            """Returns an iterator over all objects for the combined
            application and context cache.
            """
            use_gevent = is_gevent_enabled()
            use_context = is_context_enabled()

            if use_context:
                tid = context_get_ident()
            elif use_gevent:
                tid = greenlet_get_ident()
            else:
                tid = thread_get_ident()

            objects = self._cache.get(tid)
            if objects is None:
                if len(self._cache) > _MAX_CONTEXT_OBJECT_CACHE:
                    self._cache.clear()
                objects = self._global[:]
                objects.extend(getattr(self._thread_context, "stack", ()))

                if use_gevent:
                    objects.extend(getattr(self._greenlet_context, "stack", ()))

                if use_context:
                    objects.extend(self._context_stack.get([]))

                objects.sort(reverse=True)
                objects = [x[1] for x in objects]
                self._cache[tid] = objects
            return iter(objects)

        def push_greenlet(self, obj):
            self._greenlet_context_lock.acquire()
            try:
                # remote chance to conflict with thread ids
                self._cache.pop(greenlet_get_ident(), None)
                item = (self._stackop(), obj)
                stack = getattr(self._greenlet_context, "stack", None)
                if stack is None:
                    self._greenlet_context.stack = [item]
                else:
                    stack.append(item)
            finally:
                self._greenlet_context_lock.release()

        def pop_greenlet(self):
            self._greenlet_context_lock.acquire()
            try:
                # remote chance to conflict with thread ids
                self._cache.pop(greenlet_get_ident(), None)
                stack = getattr(self._greenlet_context, "stack", None)
                assert stack, "no objects on stack"
                return stack.pop()[1]
            finally:
                self._greenlet_context_lock.release()

        def push_context(self, obj):
            self._cache.pop(context_get_ident(), None)
            item = (self._stackop(), obj)
            stack = self._context_stack.get(None)
            if stack is None:
                stack = [item]
                self._context_stack.set(stack)
            else:
                stack.append(item)

        def pop_context(self):
            self._cache.pop(context_get_ident(), None)
            stack = self._context_stack.get(None)
            assert stack, "no objects on stack"
            return stack.pop()[1]

        def push_thread(self, obj):
            self._thread_context_lock.acquire()
            try:
                self._cache.pop(thread_get_ident(), None)
                item = (self._stackop(), obj)
                stack = getattr(self._thread_context, "stack", None)
                if stack is None:
                    self._thread_context.stack = [item]
                else:
                    stack.append(item)
            finally:
                self._thread_context_lock.release()

        def pop_thread(self):
            self._thread_context_lock.acquire()
            try:
                self._cache.pop(thread_get_ident(), None)
                stack = getattr(self._thread_context, "stack", None)
                assert stack, "no objects on stack"
                return stack.pop()[1]
            finally:
                self._thread_context_lock.release()

        def push_application(self, obj):
            self._global.append((self._stackop(), obj))
            self._cache.clear()

        def pop_application(self):
            assert self._global, "no objects on application stack"
            popped = self._global.pop()[1]
            self._cache.clear()
            return popped