PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/flaskext/gae_mini_profiler/__init__.py

https://github.com/passy/flask-gae-mini-profiler
Python | 186 lines | 121 code | 34 blank | 31 comment | 9 complexity | e4aeaaf80f8e4dfb63bbb7a317832e73 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskext.gae_mini_profiler
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. A Google App Engine profiler for `Flask <http://flask.pocoo.org>` based
  6. on `gae_mini_profiler <https://github.com/kamens/gae_mini_profiler>` by
  7. `Ben Kamens <http://bjk5.com/>`.
  8. :copyright: (c) 2011 by Pascal Hartig.
  9. :license: MIT, see LICENSE for more details.
  10. """
  11. import os
  12. from flask.helpers import send_from_directory
  13. from flask import request, jsonify
  14. from flaskext.gae_mini_profiler import profiler
  15. from jinja2 import Environment, FileSystemLoader
  16. def replace_insensitive(string, target, replacement):
  17. """Similar to string.replace() but is case insensitive
  18. Code borrowed from: http://forums.devshed.com/python-programming-11/
  19. case-insensitive-string-replace-490921.html
  20. """
  21. no_case = string.lower()
  22. index = no_case.rfind(target.lower())
  23. if index >= 0:
  24. return string[:index] + replacement + string[index + len(target):]
  25. else:
  26. return string
  27. class GAEMiniProfilerWSGIMiddleware(profiler.ProfilerWSGIMiddleware):
  28. """Slightly adjusted WSGI middleware, using the flask app config rather
  29. than a stand-alone config file.
  30. """
  31. def __init__(self, flask_app, *args, **kwargs):
  32. self.flask_app = flask_app
  33. profiler.ProfilerWSGIMiddleware.__init__(self, *args, **kwargs)
  34. def should_profile(self, environ):
  35. """Check whether the currently processed page should be profiled or
  36. not.
  37. """
  38. from google.appengine.api import users
  39. # Short-circuit!
  40. if environ["PATH_INFO"].startswith("/_gae_mini_profiler/"):
  41. return False
  42. if self.flask_app.config['GAEMINIPROFILER_PROFILER_ADMINS'] and \
  43. users.is_current_user_admin():
  44. return True
  45. user = users.get_current_user()
  46. return user and user.email() in \
  47. self.flask_app.config['GAEMINIPROFILER_PROFILER_EMAILS']
  48. class GAEMiniProfiler(object):
  49. PROFILER_URL_PREFIX = "/_gae_mini_profiler/"
  50. def __init__(self, app, *args, **kwargs):
  51. self.app = app
  52. # Apply the middleware
  53. app.wsgi_app = GAEMiniProfilerWSGIMiddleware(app, app.wsgi_app)
  54. # Set up config defaults
  55. self.app.config.setdefault('GAEMINIPROFILER_PROFILER_EMAILS', [
  56. 'test@example.com'
  57. ])
  58. self.app.config.setdefault('GAEMINIPROFILER_PROFILER_ADMINS', True)
  59. # Build the static path based on our current directory
  60. base_dir = os.path.realpath(os.path.dirname(__file__))
  61. self._static_dir = os.path.join(base_dir, 'static')
  62. # Configure jinja for internal templates
  63. self.jinja_env = Environment(
  64. autoescape=True,
  65. extensions=['jinja2.ext.i18n'],
  66. loader=FileSystemLoader(
  67. os.path.join(base_dir, 'templates')
  68. )
  69. )
  70. # Install the response hook
  71. app.after_request(self._process_response)
  72. app.add_url_rule(self.PROFILER_URL_PREFIX + "static/<path:filename>",
  73. '_gae_mini_profiler.static', self._send_static_file)
  74. app.add_url_rule(self.PROFILER_URL_PREFIX + "request",
  75. '_gae_mini_profiler.request', self._request_view)
  76. app.add_url_rule(self.PROFILER_URL_PREFIX + "shared",
  77. '_gae_mini_profiler.share', self._share_view)
  78. def _send_static_file(self, filename):
  79. """Send an internal static file."""
  80. return send_from_directory(self._static_dir, filename)
  81. def _process_response(self, response):
  82. """Process response and append the profiler code if appropriate."""
  83. if response.status_code != 200 or response.mimetype != "text/html" or \
  84. not response.is_sequence:
  85. return response
  86. response_html = response.data.decode(response.charset)
  87. profiler_html = self._render_profiler()
  88. # Inject the profiler HTML snippet right before the </body>
  89. response.response = [
  90. replace_insensitive(
  91. response_html,
  92. '</body>',
  93. profiler_html + '</body>'
  94. )
  95. ]
  96. return response
  97. def _render_profiler(self):
  98. context = self._get_render_context()
  99. context['request_id'] = profiler.request_id
  100. return self._render("includes.html", context)
  101. def _get_render_context(self):
  102. return {
  103. 'js_path': "/_gae_mini_profiler/static/js/profiler.js",
  104. 'css_path': "/_gae_mini_profiler/static/css/profiler.css"
  105. }
  106. def _render(self, template_name, context):
  107. """Render a jinja2 template within the application's environment."""
  108. template = self.jinja_env.get_template(template_name)
  109. return template.render(**context)
  110. def _request_view(self):
  111. """Renders the request stats."""
  112. request_ids = request.args['request_ids']
  113. stats_list = []
  114. for request_id in request_ids.split(','):
  115. request_stats = profiler.RequestStats.get(request_id)
  116. if request_stats and not request_stats.disabled:
  117. dict_request_stats = {}
  118. for property in profiler.RequestStats.serialized_properties:
  119. dict_request_stats[property] = \
  120. request_stats.__getattribute__(property)
  121. stats_list.append(dict_request_stats)
  122. # Don't show temporary redirect profiles more than once
  123. # automatically, as they are tied to URL params and may be
  124. # copied around easily.
  125. if request_stats.temporary_redirect:
  126. request_stats.disabled = True
  127. request_stats.store()
  128. # For security reasons, we return an object instead of a list as it is
  129. # dont in the upstream module.
  130. return jsonify(stats=stats_list)
  131. def _share_view(self):
  132. """Renders the shared stats view."""
  133. request_id = request.args['request_id']
  134. if not profiler.RequestStats.get(request_id):
  135. return u"Profiler stats no longer available."
  136. context = self._get_render_context()
  137. context['request_id'] = request_id
  138. return self._render("shared.html", context)