PageRenderTime 52ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/packages/flaskext/gae_mini_profiler/__init__.py

https://github.com/jbochi/flask-appengine-template
Python | 172 lines | 108 code | 33 blank | 31 comment | 8 complexity | df6f94cff85d7a71365540e6a4be4018 MD5 | raw file
Possible License(s): BSD-3-Clause
  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 not response.is_sequence:
  84. return response
  85. response_html = response.data.decode(response.charset)
  86. profiler_html = self._render_profiler()
  87. # Inject the profiler HTML snippet right before the </body>
  88. response.response = [
  89. replace_insensitive(
  90. response_html,
  91. '</body>',
  92. profiler_html + '</body>'
  93. )
  94. ]
  95. return response
  96. def _render_profiler(self):
  97. context = self._get_render_context()
  98. context['request_id'] = profiler.request_id
  99. return self._render("includes.html", context)
  100. def _get_render_context(self):
  101. return {
  102. 'js_path': "/_gae_mini_profiler/static/js/profiler.js",
  103. 'css_path': "/_gae_mini_profiler/static/css/profiler.css"
  104. }
  105. def _render(self, template_name, context):
  106. """Render a jinja2 template within the application's environment."""
  107. template = self.jinja_env.get_template(template_name)
  108. return template.render(**context)
  109. def _request_view(self):
  110. """Renders the request stats."""
  111. request_id = request.args['request_id']
  112. stats = profiler.RequestStats.get(request_id)
  113. if not stats:
  114. return u''
  115. dict_request_stats = {}
  116. for property in profiler.RequestStats.serialized_properties:
  117. dict_request_stats[property] = \
  118. stats.__getattribute__(property)
  119. return jsonify(dict_request_stats)
  120. def _share_view(self):
  121. """Renders the shared stats view."""
  122. request_id = request.args['request_id']
  123. if not profiler.RequestStats.get(request_id):
  124. return u"Profiler stats no longer available."
  125. context = self._get_render_context()
  126. context['request_id'] = request_id
  127. return self._render("shared.html", context)