#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""gaetk2.handlers.base - default Request Handler for gaetk2.

Created by <NAME> on 2010-10-03.
Copyright (c) 2010-2019 HUDORA. All rights reserved.
"""
from __future__ import absolute_import
from __future__ import unicode_literals

import inspect
import logging
import os
import os.path
import time
import urlparse
import warnings

from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.api.app_identity import get_application_id

import jinja2
import webapp2

from gaetk2 import exc
from gaetk2 import jinja_filters
from gaetk2.config import gaetkconfig
from gaetk2.config import get_release
from gaetk2.config import is_development
from gaetk2.config import is_production
from gaetk2.tools import hujson2
from gaetk2.tools import introspection
from gaetk2.tools.sentry import sentry_client


try:
    # if mixing gaetk1 and gaetk2 we need to use the same module
    # to get the rifght thread local storage
    from gaetk import gaesessions
except:
    from gaetk2 import _gaesessions as gaesessions

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)
_jinja_env_cache = None

# Your app usually will extend a `BasicHandler` or `DefaultHandler`
# (for added authentication). These based on
# [webapp2.RequestHandler](http://webapp2.readthedocs.io/en/latest/api/webapp2.html#webapp2.RequestHandler)
# See [The webapp2 Guide](http://webapp2.readthedocs.io/en/latest/guide/handlers.html)
# for an introduction.


class BasicHandler(webapp2.RequestHandler):
    """Generic Handler functionality.

    You usually overwrite :meth:`get()` or :meth:`post()` and call
    :meth:`render()` in there. See :ref:`gaetk2.handlers` for examples.

    For Returning Data to the user you can access the `self.response` object
    or use :meth:`return_text` and :meth:`render`. See :meth:`get_jinja2env`
    to understand the jinja2 context being used.

    Helper functions are :meth:`abs_url()` and :meth:`is_production`.

    See Also:
        :meth:`is_sysadmin`, :meth:`is_staff` and :meth:`has_permission`
        are meant to work with
        :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`
        for ``self.request`` see `webapp2 Documentation
        <http://webapp2.readthedocs.io/en/latest/guide/request.html
        #common-request-attributes>`_

    Attributes:
        request: See webapp2 documentation
        credential: authenticated user, see :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`
        session: current session which is based on https://github.com/dound/gae-sessions.
        default_cachingtime (None or int): Class Variable. Which cache headers to generate,
            see :meth:`_set_cache_headers`.

    Note:
        gaetk2 adds various variables to the template context. Other mixins provide
        additional template variables. See the Index :ref:`genindex` under "Template Context"
        to get an overview.

        These :index:`Template Variables <Template Context>` are provided:

        * :index:`request <Template Context; request>`
        * :index:`credential <Template Context; credential>`
        * :index:`is_staff <Template Context; is_staff>` (self.is_staff())
        * :index:`is_sysadmin <Template Context; is_sysadmin>` (self.is_sysadmin())
        * :index:`gaetk_production <Template Context; gaetk_production>` (is_production())
        * :index:`gaetk_development <Template Context; gaetk_development>` (is_development())
        * :index:`gaetk_release <Template Context; gaetk_development>` (get_release())
        * :index:`gaetk_app_name <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)
        * :index:`gaetk_gae_version <Template Context; gaetk_gae_version>` (CURRENT_VERSION_ID)
        * :index:`gaetk_sentry_dsn <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)
        * :index:`gaetk_logout_url <Template Context; gaetk_logout_url>`
        * :index:`gaetk_path <Template Context; gaetk_path>` (self.request.path)


    .. _handler-hook-mechanism:

    Warning:
        :class:`BasicHandler` implements a rather unusual way to implement
        Multi-Inherance/Mix-Ins. Instead of insisting that every parent class
        and everty Mix-In implements all possible methods and calls super on them
        :class:`BasicHandler` uses a custom dispatch mechanism which calls all
        methods in all parent and sibling classes.

        The following functions are called on all parent and sibling classes:

        * :meth:`pre_authentication_hook`.
        * :meth:`authentication_preflight_hook`.
        * :meth:`authentication_hook`.
        * :meth:`authorisation_hook`.
        * :meth:`method_preperation_hook`.
        * :meth:`finished_hook` - called even if a :exc:`exc.HTTPException` < 500 occurs.

        :meth:`build_context` is special because the output is "chained".
        So the rendering is done with something like the output of
        ``Child.build_context(Parent.build_context(MixIn.build_context({})))``

    :meth:`response_overwrite` and :meth:`finished_overwrite` can be overwritten
    to provide special functionality like in :class:`JsonBasicHandler`.

    You are encouraged to study the source code of :class:`BasicHandler`!

    """

    default_cachingtime = None

    def __init__(self, *args, **kwargs):
        """Initialize RequestHandler."""
        self.credential = None
        self.session = {}
        self._last_template_exception = None
        # Careful! `webapp2.RequestHandler` does not call super()!
        super(BasicHandler, self).__init__(*args, **kwargs)
        # ... so we route arround that
        super(webapp2.RequestHandler, self).__init__()

    # helper methods

    def abs_url(self, url):
        """Converts an relative into an qualified absolute URL.

        Args:
            url (str): an path to a web resource.

        Returns:
            str: A fully qualified url.

        Example:
                >>> BasicHandler().abs_url('/foo')
                'http://server.example.com/foo'

        """
        if self.request:
            return urlparse.urljoin(self.request.uri, url)
        return urlparse.urljoin(os.environ.get('HTTP_ORIGIN', ''), url)

    def is_sysadmin(self):
        """Checks if the current user is logged in as a SysOp/SystemAdministrator.

        We use various souces to deterine the Status of the user.
        Returns `True` if:

        * google.appengine.api.users.is_current_user_admin()
        * the request came from `127.0.0.1` local address
        * `self.credential.sysadmin == True`

        Returns:
            boolean: the status of the currently logged in user.

        """
        # Google App Engine Administrators
        if users.is_current_user_admin():
            return True
        # Requests from localhost (on dev_appserver) are always admin
        if self.request.remote_addr == '127.0.0.1':
            return True
        # User with Admin permissions via Credential entities
        if not hasattr(self, 'credential'):
            return False
        elif self.credential is None:
            return False
        return getattr(self.credential, 'sysadmin', False)

    def is_staff(self):
        """Returns if the current user is considered internal.

        This means he has access to not only his own but to all
        settings pages, etc.

        * :meth:`is_sysadmin`
        * `self.credential.staff == True`

        Returns:
            boolean: the status of the currently logged in user is considered internal.

        """
        if self.is_sysadmin():
            return True
        elif self.credential is None:
            return False
        return getattr(self.credential, 'staff', False)

    def has_permission(self, permission):
        """Checks if user has a given permission."""
        if self.credential:
            return permission in self.credential.permissions
        return False

    def render(self, values, template_name):
        """Render a Jinja2 Template and write it to the client.

        If rendering takes an unusual long time this is logged.

        Parameters:
            values (dict): variables for template context.
            template_name (str): name of the template to render.

        See Also:
            :meth:`build_context()`also provides data to the template
            context and is often extended by plugins. See
            :class:`BasicHandler` docsting for standard template variables.

        """
        start = time.time()
        self._render_to_fd(values, template_name, self.response.out)
        delta = time.time() - start
        if delta > 500:
            LOGGER.warn('rendering took %d ms', (delta * 1000.0))

    def return_text(
        self, text, status=200, content_type='text/plain', encoding='utf-8'
    ):
        """Quick and dirty sending of some plaintext to the client.

        Parameters:
            text (str or unicode): Data to be sent to the cliend. A NEWLINE is appended.
            status (int): status code to be sent to the client. Defaults to 200.
            content_type: to be sent to the client in respective header.
            encoding: to be used when sending to the client.

        """
        self.response.set_status(status)
        self.response.headers[b'Content-Type'] = str(content_type)
        if isinstance(text, unicode):
            text = text.encode(encoding)
        self.response.out.write(text)
        self.response.out.write('\n')

    # to be overwritten / extended

    def build_context(self, values):
        """Helper to provide additional request-specific values to HTML Templates.

        Will be called on all Parents and MixIns, no `super()` needed.

        def build_context(self, values):
            myvalues = dict(navsection='kunden', ...)
            myvalues.update(values)
            return myvalues
        """
        ret = {
            'request': self.request,
            'credential': self.credential,
            'gaetk_logout_url': '/gaetk2/auth/logout',
            'gaetk_path': self.request.path,
            'is_staff': self.is_staff(),
            'is_sysadmin': self.is_sysadmin(),
        }
        ret.update(values)
        return ret

    def _add_jinja2env_globals(self, env):
        """Helper to provide additional Globals to Jinja2 Environment.

        This should be considered one time initialisation.

        Example::
            env.globals['bottommenuurl'] = '/admin/'
            env.globals['bottommenuaddon'] = '<i class="fa fa-area-chart"></i> Admin'
            env.globals['profiler_includes'] = gae_mini_profiler.templatetags.profiler_includes

        """
        if not gaetkconfig.APP_NAME:
            gaetkconfig.APP_NAME = get_application_id().capitalize()
        env.globals.update(
            dict(
                gaetk_production=is_production(),
                gaetk_development=is_development(),
                gaetk_release=get_release(),
                gaetk_app_name=gaetkconfig.APP_NAME,
                gaetk_sentry_dsn=gaetkconfig.SENTRY_PUBLIC_DSN,
                gaetk_gae_version=os.environ.get('CURRENT_VERSION_ID', 'dev'),
            )
        )
        return env

    def debug(self, message, *args):
        """Detailed logging for development.

        This logging will only happen, if :class:`WSGIApplication` was
        initialized with ``debug=True``. Is meant for local inspection
        of the stack during development.
        Messages are prefixed with the method name from where they are called.
        """
        if self.app.debug:
            curframe = inspect.currentframe()
            calframe = inspect.getouterframes(curframe, 2)
            callfile = calframe[1][1].split('/')[-1]
            callfunc = calframe[1][3]
            message = '{} {}(): {}'.format(callfile, callfunc, message)
            LOGGER.debug(message, *args)
            # filename, lineno, function, code_context, index).

    # For MixIns:

    def pre_authentication_hook(self, method_name, *args, **kwargs):
        """Might do redirects before even authentication data is loaded.

        Called on all parent and sibling classes.
        """
        return

    def authentication_preflight_hook(self, method_name, *args, **kwargs):
        """Might load Authentication data from Headers.

        Called on all parent and sibling classes.
        """
        return

    def authentication_hook(self, method_name, *args, **kwargs):
        """Might verify Authentication data.

        Called on all parent and sibling classes.
        """
        return

    def authorisation_hook(self, method_name, *args, **kwargs):
        """Might check if authenticated user is authorized.

        Called on all parent and sibling classes.
        """
        return

    def method_preperation_hook(self, method_name, *args, **kwargs):
        """Is Called just before GEP, POST, PUT, DELETE etc. is called.

        Used to provide common data in child classes.
        E.g. to set up variables, load Date etc.
        """
        return

    def response_overwrite(self, response, method, *args, **kwargs):
        """Function to transform response. To be overwritten."""
        if hasattr(self, 'response_overwrite_overwrite'):  # hack for JsonMixin
            return self.response_overwrite_overwrite(response, method, *args, **kwargs)
        return response

    def finished_hook(self, method, *args, **kwargs):
        """To be called at the end of an request."""

    def finished_overwrite(self, response, method, *args, **kwargs):
        """Function to allow logging etc. To be overwritten."""
        # not called when exceptions are raised

    def clear_session(self):
        """Terminate the session reliably."""
        LOGGER.info('clearing session')
        self.session['uid'] = None
        if self.session and self.session.is_active():
            self.session.terminate()
        self.session.regenerate_id()

    # internal stuff

    def get_jinja2env(self):
        """Initialise and return a jinja2 Environment instance."""
        global _jinja_env_cache

        if not _jinja_env_cache:
            env = jinja2.Environment(
                loader=jinja2.FileSystemLoader(gaetkconfig.TEMPLATE_DIRS),
                auto_reload=False,  # unneeded on App Engine production
                trim_blocks=True,  # first newline after a block is removed
                # lstrip_blocks=True,
                bytecode_cache=jinja2.MemcachedBytecodeCache(memcache, timeout=3600),
                # This needs jinja2 > Version 2.8
                autoescape=jinja2.select_autoescape(['html', 'xml']),
            )

            env.exception_handler = self._jinja2_exception_handler

            jinja_filters.register_custom_filters(env)
            env.policies['json.dumps_function'] = hujson2.htmlsafe_json_dumps
            env = self._add_jinja2env_globals(env)
            _jinja_env_cache = env

        return _jinja_env_cache

    # internal stuff

    def _jinja2_exception_handler(self, traceback):
        """Is called during Jinja2 Exception processing to provide logging."""
        global _jinja_env_cache
        # see http://jinja.pocoo.org/docs/2.10/faq/#my-tracebacks-look-weird-what-s-happening
        # see http://flask.pocoo.org/snippets/74/
        # here we still get the correct traceback information, it will be discarded later on
        LOGGER.info('Template globals = %r', getattr(_jinja_env_cache, 'globals', None))
        LOGGER.exception('Template Exception %r', traceback.render_as_text())
        # this logs the correct exception to Sentry
        # but results in a duplocate incorrect one.
        sentry_client.captureException(exc_info=traceback.exc_info)
        self._last_template_exception = traceback.exc_info[1]

    def _render_to_fd(self, values, template_name, fd):
        """Sends the rendered content of a Jinja2 Template to Output.

        Per default the template is provided with output of ``build_context(values)``.
        """
        env = self.get_jinja2env()
        try:
            template = env.get_template(template_name)
        except jinja2.TemplateNotFound:
            # better error reporting - we want to see the name of the base template
            raise jinja2.TemplateNotFound(template_name)
        except (jinja2.TemplateAssertionError, jinja2.TemplateRuntimeError):
            # better logging
            LOGGER.debug('values=%r', values)
            LOGGER.debug('env.globals=%r', env.globals.keys())
            LOGGER.debug('env.filters=%r', env.filters.keys())
            if self._last_template_exception:
                # at his point the treceback is damaged
                # but we send it earlier on to Sentry.
                self._last_template_exception._hide_from_sentry = True
                raise self._last_template_exception
            raise

        # to collect template variables from all Parent-Classes and MisIns.
        # this keeps parents from having all to implement the function and
        # use `super()`
        values = self._reduce_all_inherited('build_context', values)

        # for debugging provide access to all variables if `_gaetk_dump` is
        # given in the URL
        if self.is_sysadmin() and self.request.get('_gaetk_dump'):
            self.body = ''
            self.response.headers['Content-Type'] = 'application/json'
            fd.write(
                hujson2.htmlsafe_json_dumps(
                    {
                        'gaetk_globalcontext_json': env.globals,
                        'gaetk_localcontext_json': values,
                    }
                )
            )
            return None

        try:
            template.stream(values).dump(fd, encoding='utf-8')
            # we do not want to rely on webob.Response magically transforming unicode
        except jinja2.TemplateNotFound:  # can happen for includes etc.
            # better error reporting
            # TODO: https://docs.sentry.io/clientdev/interfaces/template/
            LOGGER.info('jinja2 environment: %s', vars(env))
            LOGGER.info('template dirs: %s', gaetkconfig.TEMPLATE_DIRS)
            raise

        # TODO: warn about undeclared variables
        # http://jinja.pocoo.org/docs/dev/api/#the-meta-api
        # from jinja2 import Environment, PackageLoader, meta
        # env = Environment(loader=PackageLoader('gummi', 'templates'))
        # template_source = env.loader.get_source(env,   'page_content.html')[0]
        # parsed_content = env.parse(template_source)
        # meta.find_undeclared_variables(parsed_content)

    def _set_cache_headers(self, caching_time=None):
        """Set Cache Headers.

        Parameters:
            caching_time (None or int): the number of seconds, the result should
                be  cachetd at frontend caches. ``None`` means no Caching-Headers.
                See also :any:`default_cachingtime`. `0` or negative Values generate
                an comand to disable all caching.

        """
        ct = self.default_cachingtime
        if caching_time is not None:
            ct = caching_time

        if ct is not None:
            if ct > 0:
                self.response.headers[b'Cache-Control'] = b'max-age=%d public' % ct
            elif ct <= 0:
                self.response.headers[b'Cache-Control'] = b'no-cache public'

    def _call_all_inherited(self, funcname, *args, **kwargs):
        """In all SuperClasses call `funcname` - if it exists."""
        # We don't want to burden all mixins with implementing
        # the required methods and calling `super().meth()`
        # so we don't use a call chain provided by the
        # Parents and MixIns but instead work through them as a list.
        # it also reverses the call order

        self._debug_callstack = []
        # This code is based in ideas from <NAME>, see
        # https://www.python.org/download/releases/2.2/descrintro/#cooperation
        for cls in reversed(self.__class__.__mro__):
            if funcname in cls.__dict__:
                x = cls.__dict__[funcname]
                if hasattr(x, '__get__'):
                    x = x.__get__(self)
                if callable(x):
                    self._debug_callstack.append(x)
                    LOGGER.debug(
                        'calling %s.%s(*%r, **%r)', cls, funcname, args, kwargs
                    )
                    try:
                        x(*args, **kwargs)
                    except TypeError as e:
                        e.message = '{} while calling {}.{}(*{!r}, **{!r})'.format(
                            str(e), cls, funcname, args, kwargs
                        )
                        LOGGER.exception(
                            'failure calling %s.%s(*%r, **%r)',
                            cls,
                            funcname,
                            args,
                            kwargs,
                        )
                        raise e
                    except BaseException as e:
                        if not isinstance(e, exc.HTTPException):
                            LOGGER.exception(
                                'failure calling %s.%s(*%r, **%r)',
                                cls,
                                funcname,
                                args,
                                kwargs,
                            )
                        raise
                else:
                    LOGGER.warn('not clallable: %r', x)

    def _reduce_all_inherited(self, funcname, initial):
        """In all SuperClasses call `funcname` with the output of the previus call."""
        # We don't want to burden all mixins with mplementing
        # the required methods and calling `super().meth()`
        # so we don't use a call chaon provided by the
        # Parents and MixIns but instead work through them as a list.
        # it also reverses the call order

        ret = initial
        # This code is based in ideas from <NAME>, see
        # https://www.python.org/download/releases/2.2/descrintro/#cooperation
        for cls in reversed(self.__class__.__mro__):
            if funcname in cls.__dict__:
                x = cls.__dict__[funcname]
                if hasattr(x, '__get__'):
                    x = x.__get__(self)
                if callable(x):
                    LOGGER.debug('reducing %s.%s(%r)', cls, funcname, ret)
                    try:
                        ret = x(ret)
                    except:
                        LOGGER.debug('error reducing %s.%s(%r)', cls, funcname, ret)
                        raise
                else:
                    LOGGER.warn('not callable: %r', x)
                if ret is None:
                    raise RuntimeError(
                        '{}.{} did not provide a return value'.format(cls, funcname)
                    )
        return ret

    def dispatch(self):
        """Dispatches the requested method fom the WSGI App.

        Meant for internal use by the stack.
        """
        request = self.request
        method_name = request.route.handler_method
        if not method_name:
            method_name = webapp2._normalize_handler_method(request.method)

        method = getattr(self, method_name, None)
        if hasattr(self, '__class__'):
            sentry_client.tags_context(
                {'handler': self.__class__.__name__, 'method': method_name}
            )

        if method is None:
            # 405 Method Not Allowed.
            valid = b', '.join(webapp2._get_handler_methods(self))
            raise exc.HTTP405_HTTPMethodNotAllowed(
                'Method not allowed in {}'.format(self.__class__.__name__),
                headers=[(b'Allow', valid)],
            )

        # The handler only receives *args if no named variables are set.
        args, kwargs = request.route_args, request.route_kwargs
        if kwargs:
            args = ()

        # bind session on dispatch (not in __init__)
        try:
            self.session = gaesessions.get_current_session()
        except AttributeError:
            # probably session middleware not loaded
            self.session = {}

        if str(self.session) != 'uninitialized session':
            sentry_client.note(
                'storage', 'Session loaded', data=dict(session=self.session)
            )

        try:
            self._call_all_inherited(
                'pre_authentication_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited(
                'authentication_preflight_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited(
                'authentication_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited('authorisation_hook', method_name, *args, **kwargs)
            self._call_all_inherited(
                'method_preperation_hook', method_name, *args, **kwargs
            )
            try:
                response = method(*args, **kwargs)
            except TypeError:
                # parameter missmatch is the error we see most often
                # so help to pin down where it happens
                klass = introspection.get_class_that_defined_method(method)
                methname = method.__name__
                sourcepos = '{}:{}'.format(
                    os.path.basename(method.__func__.__code__.co_filename),
                    method.__func__.__code__.co_firstlineno,
                )
                LOGGER.debug(
                    'method called: %s.%s(%r) from %s',
                    klass.__name__,
                    methname,
                    (args, kwargs),
                    sourcepos,
                )
                LOGGER.debug('defined at: %s %s', klass, sourcepos)
                raise
            response = self.response_overwrite(response, method, *args, **kwargs)
        except exc.HTTPException as e:
            # for HTTP exceptions execute `finished_hooks`
            if e.code < 500:
                self._call_all_inherited('finished_hook', method_name, *args, **kwargs)
            return self.handle_exception(e, self.app.debug)
        except BaseException as e:
            return self.handle_exception(e, self.app.debug)

        if response and not getattr(self, '_gaetk2_allow_strange_responses', False):
            assert isinstance(response, webapp2.Response)

        self._set_cache_headers()
        self._call_all_inherited('finished_hook', method_name, *args, **kwargs)
        self.finished_overwrite(response, method, *args, **kwargs)
        return response

    def handle_exception(self, exception, debug):
        """Called if this handler throws an exception during execution.

        The default behavior is to re-raise the exception to be handled by
        :meth:`WSGIApplication.handle_exception`.

        Parameters:
            exception: The exception that was thrown.
            debug_mode: True if the web application is running in debug mode.

        Returns:
            response to be sent to the client.

        """
        raise


class JsonBasicHandler(BasicHandler):
    """Handler which is specialized for returning JSON.

    Excepts the method to return

    * dict(), e.g. `{'foo': bar}`

    Dict is converted to JSON. `status` is used as HTTP status code. `cachingtime`
    is used to generate a `Cache-Control` header. If `cachingtime is None`, no header
    is generated. `cachingtime` defaults to 60 seconds.
    """

    # Our default caching is 60s
    default_cachingtime = 60

    def serialize(self, content):
        """convert content to JSON."""
        warnings.warn(
            'use `class Handler(BasicHandler, JsonMixin)`',
            DeprecationWarning,
            stacklevel=2,
        )
        return hujson2.dumps(content)

    def response_overwrite(self, response, method, *args, **kwargs):
        """Function to transform response. To be overwritten."""
        # do serialisation bef ore generating Content-Type Header so Errors will display nicely
        content = self.serialize(response) + '\n'

        # If we have gotten a `callback` parameter, we expect that this is a
        # [JSONP](http://en.wikipedia.org/wiki/JSONP#JSONP) can and therefore add the padding
        if self.request.get('callback', None):
            response = '{} ({})'.format(self.request.get('callback', None), response)
            self.response.headers['Content-Type'] = 'text/javascript'
        else:
            self.response.headers['Content-Type'] = 'application/json'
        return webapp2.Response(content)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""gaetk2.handlers.base - default Request Handler for gaetk2.

Created by <NAME> on 2010-10-03.
Copyright (c) 2010-2019 HUDORA. All rights reserved.
"""
from __future__ import absolute_import
from __future__ import unicode_literals

import inspect
import logging
import os
import os.path
import time
import urlparse
import warnings

from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.api.app_identity import get_application_id

import jinja2
import webapp2

from gaetk2 import exc
from gaetk2 import jinja_filters
from gaetk2.config import gaetkconfig
from gaetk2.config import get_release
from gaetk2.config import is_development
from gaetk2.config import is_production
from gaetk2.tools import hujson2
from gaetk2.tools import introspection
from gaetk2.tools.sentry import sentry_client


try:
    # if mixing gaetk1 and gaetk2 we need to use the same module
    # to get the rifght thread local storage
    from gaetk import gaesessions
except:
    from gaetk2 import _gaesessions as gaesessions

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.INFO)
_jinja_env_cache = None

# Your app usually will extend a `BasicHandler` or `DefaultHandler`
# (for added authentication). These based on
# [webapp2.RequestHandler](http://webapp2.readthedocs.io/en/latest/api/webapp2.html#webapp2.RequestHandler)
# See [The webapp2 Guide](http://webapp2.readthedocs.io/en/latest/guide/handlers.html)
# for an introduction.


class BasicHandler(webapp2.RequestHandler):
    """Generic Handler functionality.

    You usually overwrite :meth:`get()` or :meth:`post()` and call
    :meth:`render()` in there. See :ref:`gaetk2.handlers` for examples.

    For Returning Data to the user you can access the `self.response` object
    or use :meth:`return_text` and :meth:`render`. See :meth:`get_jinja2env`
    to understand the jinja2 context being used.

    Helper functions are :meth:`abs_url()` and :meth:`is_production`.

    See Also:
        :meth:`is_sysadmin`, :meth:`is_staff` and :meth:`has_permission`
        are meant to work with
        :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`
        for ``self.request`` see `webapp2 Documentation
        <http://webapp2.readthedocs.io/en/latest/guide/request.html
        #common-request-attributes>`_

    Attributes:
        request: See webapp2 documentation
        credential: authenticated user, see :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`
        session: current session which is based on https://github.com/dound/gae-sessions.
        default_cachingtime (None or int): Class Variable. Which cache headers to generate,
            see :meth:`_set_cache_headers`.

    Note:
        gaetk2 adds various variables to the template context. Other mixins provide
        additional template variables. See the Index :ref:`genindex` under "Template Context"
        to get an overview.

        These :index:`Template Variables <Template Context>` are provided:

        * :index:`request <Template Context; request>`
        * :index:`credential <Template Context; credential>`
        * :index:`is_staff <Template Context; is_staff>` (self.is_staff())
        * :index:`is_sysadmin <Template Context; is_sysadmin>` (self.is_sysadmin())
        * :index:`gaetk_production <Template Context; gaetk_production>` (is_production())
        * :index:`gaetk_development <Template Context; gaetk_development>` (is_development())
        * :index:`gaetk_release <Template Context; gaetk_development>` (get_release())
        * :index:`gaetk_app_name <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)
        * :index:`gaetk_gae_version <Template Context; gaetk_gae_version>` (CURRENT_VERSION_ID)
        * :index:`gaetk_sentry_dsn <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)
        * :index:`gaetk_logout_url <Template Context; gaetk_logout_url>`
        * :index:`gaetk_path <Template Context; gaetk_path>` (self.request.path)


    .. _handler-hook-mechanism:

    Warning:
        :class:`BasicHandler` implements a rather unusual way to implement
        Multi-Inherance/Mix-Ins. Instead of insisting that every parent class
        and everty Mix-In implements all possible methods and calls super on them
        :class:`BasicHandler` uses a custom dispatch mechanism which calls all
        methods in all parent and sibling classes.

        The following functions are called on all parent and sibling classes:

        * :meth:`pre_authentication_hook`.
        * :meth:`authentication_preflight_hook`.
        * :meth:`authentication_hook`.
        * :meth:`authorisation_hook`.
        * :meth:`method_preperation_hook`.
        * :meth:`finished_hook` - called even if a :exc:`exc.HTTPException` < 500 occurs.

        :meth:`build_context` is special because the output is "chained".
        So the rendering is done with something like the output of
        ``Child.build_context(Parent.build_context(MixIn.build_context({})))``

    :meth:`response_overwrite` and :meth:`finished_overwrite` can be overwritten
    to provide special functionality like in :class:`JsonBasicHandler`.

    You are encouraged to study the source code of :class:`BasicHandler`!

    """

    default_cachingtime = None

    def __init__(self, *args, **kwargs):
        """Initialize RequestHandler."""
        self.credential = None
        self.session = {}
        self._last_template_exception = None
        # Careful! `webapp2.RequestHandler` does not call super()!
        super(BasicHandler, self).__init__(*args, **kwargs)
        # ... so we route arround that
        super(webapp2.RequestHandler, self).__init__()

    # helper methods

    def abs_url(self, url):
        """Converts an relative into an qualified absolute URL.

        Args:
            url (str): an path to a web resource.

        Returns:
            str: A fully qualified url.

        Example:
                >>> BasicHandler().abs_url('/foo')
                'http://server.example.com/foo'

        """
        if self.request:
            return urlparse.urljoin(self.request.uri, url)
        return urlparse.urljoin(os.environ.get('HTTP_ORIGIN', ''), url)

    def is_sysadmin(self):
        """Checks if the current user is logged in as a SysOp/SystemAdministrator.

        We use various souces to deterine the Status of the user.
        Returns `True` if:

        * google.appengine.api.users.is_current_user_admin()
        * the request came from `127.0.0.1` local address
        * `self.credential.sysadmin == True`

        Returns:
            boolean: the status of the currently logged in user.

        """
        # Google App Engine Administrators
        if users.is_current_user_admin():
            return True
        # Requests from localhost (on dev_appserver) are always admin
        if self.request.remote_addr == '127.0.0.1':
            return True
        # User with Admin permissions via Credential entities
        if not hasattr(self, 'credential'):
            return False
        elif self.credential is None:
            return False
        return getattr(self.credential, 'sysadmin', False)

    def is_staff(self):
        """Returns if the current user is considered internal.

        This means he has access to not only his own but to all
        settings pages, etc.

        * :meth:`is_sysadmin`
        * `self.credential.staff == True`

        Returns:
            boolean: the status of the currently logged in user is considered internal.

        """
        if self.is_sysadmin():
            return True
        elif self.credential is None:
            return False
        return getattr(self.credential, 'staff', False)

    def has_permission(self, permission):
        """Checks if user has a given permission."""
        if self.credential:
            return permission in self.credential.permissions
        return False

    def render(self, values, template_name):
        """Render a Jinja2 Template and write it to the client.

        If rendering takes an unusual long time this is logged.

        Parameters:
            values (dict): variables for template context.
            template_name (str): name of the template to render.

        See Also:
            :meth:`build_context()`also provides data to the template
            context and is often extended by plugins. See
            :class:`BasicHandler` docsting for standard template variables.

        """
        start = time.time()
        self._render_to_fd(values, template_name, self.response.out)
        delta = time.time() - start
        if delta > 500:
            LOGGER.warn('rendering took %d ms', (delta * 1000.0))

    def return_text(
        self, text, status=200, content_type='text/plain', encoding='utf-8'
    ):
        """Quick and dirty sending of some plaintext to the client.

        Parameters:
            text (str or unicode): Data to be sent to the cliend. A NEWLINE is appended.
            status (int): status code to be sent to the client. Defaults to 200.
            content_type: to be sent to the client in respective header.
            encoding: to be used when sending to the client.

        """
        self.response.set_status(status)
        self.response.headers[b'Content-Type'] = str(content_type)
        if isinstance(text, unicode):
            text = text.encode(encoding)
        self.response.out.write(text)
        self.response.out.write('\n')

    # to be overwritten / extended

    def build_context(self, values):
        """Helper to provide additional request-specific values to HTML Templates.

        Will be called on all Parents and MixIns, no `super()` needed.

        def build_context(self, values):
            myvalues = dict(navsection='kunden', ...)
            myvalues.update(values)
            return myvalues
        """
        ret = {
            'request': self.request,
            'credential': self.credential,
            'gaetk_logout_url': '/gaetk2/auth/logout',
            'gaetk_path': self.request.path,
            'is_staff': self.is_staff(),
            'is_sysadmin': self.is_sysadmin(),
        }
        ret.update(values)
        return ret

    def _add_jinja2env_globals(self, env):
        """Helper to provide additional Globals to Jinja2 Environment.

        This should be considered one time initialisation.

        Example::
            env.globals['bottommenuurl'] = '/admin/'
            env.globals['bottommenuaddon'] = '<i class="fa fa-area-chart"></i> Admin'
            env.globals['profiler_includes'] = gae_mini_profiler.templatetags.profiler_includes

        """
        if not gaetkconfig.APP_NAME:
            gaetkconfig.APP_NAME = get_application_id().capitalize()
        env.globals.update(
            dict(
                gaetk_production=is_production(),
                gaetk_development=is_development(),
                gaetk_release=get_release(),
                gaetk_app_name=gaetkconfig.APP_NAME,
                gaetk_sentry_dsn=gaetkconfig.SENTRY_PUBLIC_DSN,
                gaetk_gae_version=os.environ.get('CURRENT_VERSION_ID', 'dev'),
            )
        )
        return env

    def debug(self, message, *args):
        """Detailed logging for development.

        This logging will only happen, if :class:`WSGIApplication` was
        initialized with ``debug=True``. Is meant for local inspection
        of the stack during development.
        Messages are prefixed with the method name from where they are called.
        """
        if self.app.debug:
            curframe = inspect.currentframe()
            calframe = inspect.getouterframes(curframe, 2)
            callfile = calframe[1][1].split('/')[-1]
            callfunc = calframe[1][3]
            message = '{} {}(): {}'.format(callfile, callfunc, message)
            LOGGER.debug(message, *args)
            # filename, lineno, function, code_context, index).

    # For MixIns:

    def pre_authentication_hook(self, method_name, *args, **kwargs):
        """Might do redirects before even authentication data is loaded.

        Called on all parent and sibling classes.
        """
        return

    def authentication_preflight_hook(self, method_name, *args, **kwargs):
        """Might load Authentication data from Headers.

        Called on all parent and sibling classes.
        """
        return

    def authentication_hook(self, method_name, *args, **kwargs):
        """Might verify Authentication data.

        Called on all parent and sibling classes.
        """
        return

    def authorisation_hook(self, method_name, *args, **kwargs):
        """Might check if authenticated user is authorized.

        Called on all parent and sibling classes.
        """
        return

    def method_preperation_hook(self, method_name, *args, **kwargs):
        """Is Called just before GEP, POST, PUT, DELETE etc. is called.

        Used to provide common data in child classes.
        E.g. to set up variables, load Date etc.
        """
        return

    def response_overwrite(self, response, method, *args, **kwargs):
        """Function to transform response. To be overwritten."""
        if hasattr(self, 'response_overwrite_overwrite'):  # hack for JsonMixin
            return self.response_overwrite_overwrite(response, method, *args, **kwargs)
        return response

    def finished_hook(self, method, *args, **kwargs):
        """To be called at the end of an request."""

    def finished_overwrite(self, response, method, *args, **kwargs):
        """Function to allow logging etc. To be overwritten."""
        # not called when exceptions are raised

    def clear_session(self):
        """Terminate the session reliably."""
        LOGGER.info('clearing session')
        self.session['uid'] = None
        if self.session and self.session.is_active():
            self.session.terminate()
        self.session.regenerate_id()

    # internal stuff

    def get_jinja2env(self):
        """Initialise and return a jinja2 Environment instance."""
        global _jinja_env_cache

        if not _jinja_env_cache:
            env = jinja2.Environment(
                loader=jinja2.FileSystemLoader(gaetkconfig.TEMPLATE_DIRS),
                auto_reload=False,  # unneeded on App Engine production
                trim_blocks=True,  # first newline after a block is removed
                # lstrip_blocks=True,
                bytecode_cache=jinja2.MemcachedBytecodeCache(memcache, timeout=3600),
                # This needs jinja2 > Version 2.8
                autoescape=jinja2.select_autoescape(['html', 'xml']),
            )

            env.exception_handler = self._jinja2_exception_handler

            jinja_filters.register_custom_filters(env)
            env.policies['json.dumps_function'] = hujson2.htmlsafe_json_dumps
            env = self._add_jinja2env_globals(env)
            _jinja_env_cache = env

        return _jinja_env_cache

    # internal stuff

    def _jinja2_exception_handler(self, traceback):
        """Is called during Jinja2 Exception processing to provide logging."""
        global _jinja_env_cache
        # see http://jinja.pocoo.org/docs/2.10/faq/#my-tracebacks-look-weird-what-s-happening
        # see http://flask.pocoo.org/snippets/74/
        # here we still get the correct traceback information, it will be discarded later on
        LOGGER.info('Template globals = %r', getattr(_jinja_env_cache, 'globals', None))
        LOGGER.exception('Template Exception %r', traceback.render_as_text())
        # this logs the correct exception to Sentry
        # but results in a duplocate incorrect one.
        sentry_client.captureException(exc_info=traceback.exc_info)
        self._last_template_exception = traceback.exc_info[1]

    def _render_to_fd(self, values, template_name, fd):
        """Sends the rendered content of a Jinja2 Template to Output.

        Per default the template is provided with output of ``build_context(values)``.
        """
        env = self.get_jinja2env()
        try:
            template = env.get_template(template_name)
        except jinja2.TemplateNotFound:
            # better error reporting - we want to see the name of the base template
            raise jinja2.TemplateNotFound(template_name)
        except (jinja2.TemplateAssertionError, jinja2.TemplateRuntimeError):
            # better logging
            LOGGER.debug('values=%r', values)
            LOGGER.debug('env.globals=%r', env.globals.keys())
            LOGGER.debug('env.filters=%r', env.filters.keys())
            if self._last_template_exception:
                # at his point the treceback is damaged
                # but we send it earlier on to Sentry.
                self._last_template_exception._hide_from_sentry = True
                raise self._last_template_exception
            raise

        # to collect template variables from all Parent-Classes and MisIns.
        # this keeps parents from having all to implement the function and
        # use `super()`
        values = self._reduce_all_inherited('build_context', values)

        # for debugging provide access to all variables if `_gaetk_dump` is
        # given in the URL
        if self.is_sysadmin() and self.request.get('_gaetk_dump'):
            self.body = ''
            self.response.headers['Content-Type'] = 'application/json'
            fd.write(
                hujson2.htmlsafe_json_dumps(
                    {
                        'gaetk_globalcontext_json': env.globals,
                        'gaetk_localcontext_json': values,
                    }
                )
            )
            return None

        try:
            template.stream(values).dump(fd, encoding='utf-8')
            # we do not want to rely on webob.Response magically transforming unicode
        except jinja2.TemplateNotFound:  # can happen for includes etc.
            # better error reporting
            # TODO: https://docs.sentry.io/clientdev/interfaces/template/
            LOGGER.info('jinja2 environment: %s', vars(env))
            LOGGER.info('template dirs: %s', gaetkconfig.TEMPLATE_DIRS)
            raise

        # TODO: warn about undeclared variables
        # http://jinja.pocoo.org/docs/dev/api/#the-meta-api
        # from jinja2 import Environment, PackageLoader, meta
        # env = Environment(loader=PackageLoader('gummi', 'templates'))
        # template_source = env.loader.get_source(env,   'page_content.html')[0]
        # parsed_content = env.parse(template_source)
        # meta.find_undeclared_variables(parsed_content)

    def _set_cache_headers(self, caching_time=None):
        """Set Cache Headers.

        Parameters:
            caching_time (None or int): the number of seconds, the result should
                be  cachetd at frontend caches. ``None`` means no Caching-Headers.
                See also :any:`default_cachingtime`. `0` or negative Values generate
                an comand to disable all caching.

        """
        ct = self.default_cachingtime
        if caching_time is not None:
            ct = caching_time

        if ct is not None:
            if ct > 0:
                self.response.headers[b'Cache-Control'] = b'max-age=%d public' % ct
            elif ct <= 0:
                self.response.headers[b'Cache-Control'] = b'no-cache public'

    def _call_all_inherited(self, funcname, *args, **kwargs):
        """In all SuperClasses call `funcname` - if it exists."""
        # We don't want to burden all mixins with implementing
        # the required methods and calling `super().meth()`
        # so we don't use a call chain provided by the
        # Parents and MixIns but instead work through them as a list.
        # it also reverses the call order

        self._debug_callstack = []
        # This code is based in ideas from <NAME>, see
        # https://www.python.org/download/releases/2.2/descrintro/#cooperation
        for cls in reversed(self.__class__.__mro__):
            if funcname in cls.__dict__:
                x = cls.__dict__[funcname]
                if hasattr(x, '__get__'):
                    x = x.__get__(self)
                if callable(x):
                    self._debug_callstack.append(x)
                    LOGGER.debug(
                        'calling %s.%s(*%r, **%r)', cls, funcname, args, kwargs
                    )
                    try:
                        x(*args, **kwargs)
                    except TypeError as e:
                        e.message = '{} while calling {}.{}(*{!r}, **{!r})'.format(
                            str(e), cls, funcname, args, kwargs
                        )
                        LOGGER.exception(
                            'failure calling %s.%s(*%r, **%r)',
                            cls,
                            funcname,
                            args,
                            kwargs,
                        )
                        raise e
                    except BaseException as e:
                        if not isinstance(e, exc.HTTPException):
                            LOGGER.exception(
                                'failure calling %s.%s(*%r, **%r)',
                                cls,
                                funcname,
                                args,
                                kwargs,
                            )
                        raise
                else:
                    LOGGER.warn('not clallable: %r', x)

    def _reduce_all_inherited(self, funcname, initial):
        """In all SuperClasses call `funcname` with the output of the previus call."""
        # We don't want to burden all mixins with mplementing
        # the required methods and calling `super().meth()`
        # so we don't use a call chaon provided by the
        # Parents and MixIns but instead work through them as a list.
        # it also reverses the call order

        ret = initial
        # This code is based in ideas from <NAME>, see
        # https://www.python.org/download/releases/2.2/descrintro/#cooperation
        for cls in reversed(self.__class__.__mro__):
            if funcname in cls.__dict__:
                x = cls.__dict__[funcname]
                if hasattr(x, '__get__'):
                    x = x.__get__(self)
                if callable(x):
                    LOGGER.debug('reducing %s.%s(%r)', cls, funcname, ret)
                    try:
                        ret = x(ret)
                    except:
                        LOGGER.debug('error reducing %s.%s(%r)', cls, funcname, ret)
                        raise
                else:
                    LOGGER.warn('not callable: %r', x)
                if ret is None:
                    raise RuntimeError(
                        '{}.{} did not provide a return value'.format(cls, funcname)
                    )
        return ret

    def dispatch(self):
        """Dispatches the requested method fom the WSGI App.

        Meant for internal use by the stack.
        """
        request = self.request
        method_name = request.route.handler_method
        if not method_name:
            method_name = webapp2._normalize_handler_method(request.method)

        method = getattr(self, method_name, None)
        if hasattr(self, '__class__'):
            sentry_client.tags_context(
                {'handler': self.__class__.__name__, 'method': method_name}
            )

        if method is None:
            # 405 Method Not Allowed.
            valid = b', '.join(webapp2._get_handler_methods(self))
            raise exc.HTTP405_HTTPMethodNotAllowed(
                'Method not allowed in {}'.format(self.__class__.__name__),
                headers=[(b'Allow', valid)],
            )

        # The handler only receives *args if no named variables are set.
        args, kwargs = request.route_args, request.route_kwargs
        if kwargs:
            args = ()

        # bind session on dispatch (not in __init__)
        try:
            self.session = gaesessions.get_current_session()
        except AttributeError:
            # probably session middleware not loaded
            self.session = {}

        if str(self.session) != 'uninitialized session':
            sentry_client.note(
                'storage', 'Session loaded', data=dict(session=self.session)
            )

        try:
            self._call_all_inherited(
                'pre_authentication_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited(
                'authentication_preflight_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited(
                'authentication_hook', method_name, *args, **kwargs
            )
            self._call_all_inherited('authorisation_hook', method_name, *args, **kwargs)
            self._call_all_inherited(
                'method_preperation_hook', method_name, *args, **kwargs
            )
            try:
                response = method(*args, **kwargs)
            except TypeError:
                # parameter missmatch is the error we see most often
                # so help to pin down where it happens
                klass = introspection.get_class_that_defined_method(method)
                methname = method.__name__
                sourcepos = '{}:{}'.format(
                    os.path.basename(method.__func__.__code__.co_filename),
                    method.__func__.__code__.co_firstlineno,
                )
                LOGGER.debug(
                    'method called: %s.%s(%r) from %s',
                    klass.__name__,
                    methname,
                    (args, kwargs),
                    sourcepos,
                )
                LOGGER.debug('defined at: %s %s', klass, sourcepos)
                raise
            response = self.response_overwrite(response, method, *args, **kwargs)
        except exc.HTTPException as e:
            # for HTTP exceptions execute `finished_hooks`
            if e.code < 500:
                self._call_all_inherited('finished_hook', method_name, *args, **kwargs)
            return self.handle_exception(e, self.app.debug)
        except BaseException as e:
            return self.handle_exception(e, self.app.debug)

        if response and not getattr(self, '_gaetk2_allow_strange_responses', False):
            assert isinstance(response, webapp2.Response)

        self._set_cache_headers()
        self._call_all_inherited('finished_hook', method_name, *args, **kwargs)
        self.finished_overwrite(response, method, *args, **kwargs)
        return response

    def handle_exception(self, exception, debug):
        """Called if this handler throws an exception during execution.

        The default behavior is to re-raise the exception to be handled by
        :meth:`WSGIApplication.handle_exception`.

        Parameters:
            exception: The exception that was thrown.
            debug_mode: True if the web application is running in debug mode.

        Returns:
            response to be sent to the client.

        """
        raise


class JsonBasicHandler(BasicHandler):
    """Handler which is specialized for returning JSON.

    Excepts the method to return

    * dict(), e.g. `{'foo': bar}`

    Dict is converted to JSON. `status` is used as HTTP status code. `cachingtime`
    is used to generate a `Cache-Control` header. If `cachingtime is None`, no header
    is generated. `cachingtime` defaults to 60 seconds.
    """

    # Our default caching is 60s
    default_cachingtime = 60

    def serialize(self, content):
        """convert content to JSON."""
        warnings.warn(
            'use `class Handler(BasicHandler, JsonMixin)`',
            DeprecationWarning,
            stacklevel=2,
        )
        return hujson2.dumps(content)

    def response_overwrite(self, response, method, *args, **kwargs):
        """Function to transform response. To be overwritten."""
        # do serialisation bef ore generating Content-Type Header so Errors will display nicely
        content = self.serialize(response) + '\n'

        # If we have gotten a `callback` parameter, we expect that this is a
        # [JSONP](http://en.wikipedia.org/wiki/JSONP#JSONP) can and therefore add the padding
        if self.request.get('callback', None):
            response = '{} ({})'.format(self.request.get('callback', None), response)
            self.response.headers['Content-Type'] = 'text/javascript'
        else:
            self.response.headers['Content-Type'] = 'application/json'
        return webapp2.Response(content)

#!/usr/bin/env python # -*- coding: utf-8 -*- gaetk2.handlers.base - default Request Handler for gaetk2.  Created by <NAME> on 2010-10-03. Copyright (c) 2010-2019 HUDORA. All rights reserved. # if mixing gaetk1 and gaetk2 we need to use the same module # to get the rifght thread local storage # Your app usually will extend a `BasicHandler` or `DefaultHandler` # (for added authentication). These based on # [webapp2.RequestHandler](http://webapp2.readthedocs.io/en/latest/api/webapp2.html#webapp2.RequestHandler) # See [The webapp2 Guide](http://webapp2.readthedocs.io/en/latest/guide/handlers.html) # for an introduction. Generic Handler functionality.      You usually overwrite :meth:`get()` or :meth:`post()` and call     :meth:`render()` in there. See :ref:`gaetk2.handlers` for examples.      For Returning Data to the user you can access the `self.response` object     or use :meth:`return_text` and :meth:`render`. See :meth:`get_jinja2env`     to understand the jinja2 context being used.      Helper functions are :meth:`abs_url()` and :meth:`is_production`.      See Also:         :meth:`is_sysadmin`, :meth:`is_staff` and :meth:`has_permission`         are meant to work with         :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`         for ``self.request`` see `webapp2 Documentation         <http://webapp2.readthedocs.io/en/latest/guide/request.html         #common-request-attributes>`_      Attributes:         request: See webapp2 documentation         credential: authenticated user, see :class:`~gaetk2.handlers.authentication.AuthenticationReaderMixin`         session: current session which is based on https://github.com/dound/gae-sessions.         default_cachingtime (None or int): Class Variable. Which cache headers to generate,             see :meth:`_set_cache_headers`.      Note:         gaetk2 adds various variables to the template context. Other mixins provide         additional template variables. See the Index :ref:`genindex` under "Template Context"         to get an overview.          These :index:`Template Variables <Template Context>` are provided:          * :index:`request <Template Context; request>`         * :index:`credential <Template Context; credential>`         * :index:`is_staff <Template Context; is_staff>` (self.is_staff())         * :index:`is_sysadmin <Template Context; is_sysadmin>` (self.is_sysadmin())         * :index:`gaetk_production <Template Context; gaetk_production>` (is_production())         * :index:`gaetk_development <Template Context; gaetk_development>` (is_development())         * :index:`gaetk_release <Template Context; gaetk_development>` (get_release())         * :index:`gaetk_app_name <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)         * :index:`gaetk_gae_version <Template Context; gaetk_gae_version>` (CURRENT_VERSION_ID)         * :index:`gaetk_sentry_dsn <Template Context; gaetk_app_name>` (gaetkconfig.APP_NAME)         * :index:`gaetk_logout_url <Template Context; gaetk_logout_url>`         * :index:`gaetk_path <Template Context; gaetk_path>` (self.request.path)       .. _handler-hook-mechanism:      Warning:         :class:`BasicHandler` implements a rather unusual way to implement         Multi-Inherance/Mix-Ins. Instead of insisting that every parent class         and everty Mix-In implements all possible methods and calls super on them         :class:`BasicHandler` uses a custom dispatch mechanism which calls all         methods in all parent and sibling classes.          The following functions are called on all parent and sibling classes:          * :meth:`pre_authentication_hook`.         * :meth:`authentication_preflight_hook`.         * :meth:`authentication_hook`.         * :meth:`authorisation_hook`.         * :meth:`method_preperation_hook`.         * :meth:`finished_hook` - called even if a :exc:`exc.HTTPException` < 500 occurs.          :meth:`build_context` is special because the output is "chained".         So the rendering is done with something like the output of         ``Child.build_context(Parent.build_context(MixIn.build_context({})))``      :meth:`response_overwrite` and :meth:`finished_overwrite` can be overwritten     to provide special functionality like in :class:`JsonBasicHandler`.      You are encouraged to study the source code of :class:`BasicHandler`! Initialize RequestHandler. # Careful! `webapp2.RequestHandler` does not call super()! # ... so we route arround that # helper methods Converts an relative into an qualified absolute URL.          Args:             url (str): an path to a web resource.          Returns:             str: A fully qualified url.          Example:                 >>> BasicHandler().abs_url('/foo')                 'http://server.example.com/foo' Checks if the current user is logged in as a SysOp/SystemAdministrator.          We use various souces to deterine the Status of the user.         Returns `True` if:          * google.appengine.api.users.is_current_user_admin()         * the request came from `127.0.0.1` local address         * `self.credential.sysadmin == True`          Returns:             boolean: the status of the currently logged in user. # Google App Engine Administrators # Requests from localhost (on dev_appserver) are always admin # User with Admin permissions via Credential entities Returns if the current user is considered internal.          This means he has access to not only his own but to all         settings pages, etc.          * :meth:`is_sysadmin`         * `self.credential.staff == True`          Returns:             boolean: the status of the currently logged in user is considered internal. Checks if user has a given permission. Render a Jinja2 Template and write it to the client.          If rendering takes an unusual long time this is logged.          Parameters:             values (dict): variables for template context.             template_name (str): name of the template to render.          See Also:             :meth:`build_context()`also provides data to the template             context and is often extended by plugins. See             :class:`BasicHandler` docsting for standard template variables. Quick and dirty sending of some plaintext to the client.          Parameters:             text (str or unicode): Data to be sent to the cliend. A NEWLINE is appended.             status (int): status code to be sent to the client. Defaults to 200.             content_type: to be sent to the client in respective header.             encoding: to be used when sending to the client. # to be overwritten / extended Helper to provide additional request-specific values to HTML Templates.          Will be called on all Parents and MixIns, no `super()` needed.          def build_context(self, values):             myvalues = dict(navsection='kunden', ...)             myvalues.update(values)             return myvalues Helper to provide additional Globals to Jinja2 Environment.          This should be considered one time initialisation.          Example::             env.globals['bottommenuurl'] = '/admin/'             env.globals['bottommenuaddon'] = '<i class="fa fa-area-chart"></i> Admin'             env.globals['profiler_includes'] = gae_mini_profiler.templatetags.profiler_includes Detailed logging for development.          This logging will only happen, if :class:`WSGIApplication` was         initialized with ``debug=True``. Is meant for local inspection         of the stack during development.         Messages are prefixed with the method name from where they are called. # filename, lineno, function, code_context, index). # For MixIns: Might do redirects before even authentication data is loaded.          Called on all parent and sibling classes. Might load Authentication data from Headers.          Called on all parent and sibling classes. Might verify Authentication data.          Called on all parent and sibling classes. Might check if authenticated user is authorized.          Called on all parent and sibling classes. Is Called just before GEP, POST, PUT, DELETE etc. is called.          Used to provide common data in child classes.         E.g. to set up variables, load Date etc. Function to transform response. To be overwritten. # hack for JsonMixin To be called at the end of an request. Function to allow logging etc. To be overwritten. # not called when exceptions are raised Terminate the session reliably. # internal stuff Initialise and return a jinja2 Environment instance. # unneeded on App Engine production # first newline after a block is removed # lstrip_blocks=True, # This needs jinja2 > Version 2.8 # internal stuff Is called during Jinja2 Exception processing to provide logging. # see http://jinja.pocoo.org/docs/2.10/faq/#my-tracebacks-look-weird-what-s-happening # see http://flask.pocoo.org/snippets/74/ # here we still get the correct traceback information, it will be discarded later on # this logs the correct exception to Sentry # but results in a duplocate incorrect one. Sends the rendered content of a Jinja2 Template to Output.          Per default the template is provided with output of ``build_context(values)``. # better error reporting - we want to see the name of the base template # better logging # at his point the treceback is damaged # but we send it earlier on to Sentry. # to collect template variables from all Parent-Classes and MisIns. # this keeps parents from having all to implement the function and # use `super()` # for debugging provide access to all variables if `_gaetk_dump` is # given in the URL # we do not want to rely on webob.Response magically transforming unicode # can happen for includes etc. # better error reporting # TODO: https://docs.sentry.io/clientdev/interfaces/template/ # TODO: warn about undeclared variables # http://jinja.pocoo.org/docs/dev/api/#the-meta-api # from jinja2 import Environment, PackageLoader, meta # env = Environment(loader=PackageLoader('gummi', 'templates')) # template_source = env.loader.get_source(env,   'page_content.html')[0] # parsed_content = env.parse(template_source) # meta.find_undeclared_variables(parsed_content) Set Cache Headers.          Parameters:             caching_time (None or int): the number of seconds, the result should                 be  cachetd at frontend caches. ``None`` means no Caching-Headers.                 See also :any:`default_cachingtime`. `0` or negative Values generate                 an comand to disable all caching. In all SuperClasses call `funcname` - if it exists. # We don't want to burden all mixins with implementing # the required methods and calling `super().meth()` # so we don't use a call chain provided by the # Parents and MixIns but instead work through them as a list. # it also reverses the call order # This code is based in ideas from <NAME>, see # https://www.python.org/download/releases/2.2/descrintro/#cooperation In all SuperClasses call `funcname` with the output of the previus call. # We don't want to burden all mixins with mplementing # the required methods and calling `super().meth()` # so we don't use a call chaon provided by the # Parents and MixIns but instead work through them as a list. # it also reverses the call order # This code is based in ideas from <NAME>, see # https://www.python.org/download/releases/2.2/descrintro/#cooperation Dispatches the requested method fom the WSGI App.          Meant for internal use by the stack. # 405 Method Not Allowed. # The handler only receives *args if no named variables are set. # bind session on dispatch (not in __init__) # probably session middleware not loaded # parameter missmatch is the error we see most often # so help to pin down where it happens # for HTTP exceptions execute `finished_hooks` Called if this handler throws an exception during execution.          The default behavior is to re-raise the exception to be handled by         :meth:`WSGIApplication.handle_exception`.          Parameters:             exception: The exception that was thrown.             debug_mode: True if the web application is running in debug mode.          Returns:             response to be sent to the client. Handler which is specialized for returning JSON.      Excepts the method to return      * dict(), e.g. `{'foo': bar}`      Dict is converted to JSON. `status` is used as HTTP status code. `cachingtime`     is used to generate a `Cache-Control` header. If `cachingtime is None`, no header     is generated. `cachingtime` defaults to 60 seconds. # Our default caching is 60s convert content to JSON. Function to transform response. To be overwritten. # do serialisation bef ore generating Content-Type Header so Errors will display nicely # If we have gotten a `callback` parameter, we expect that this is a # [JSONP](http://en.wikipedia.org/wiki/JSONP#JSONP) can and therefore add the padding