From 35738415223c47f70468eb6b1ffa7ef5bd0e03c6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 30 Apr 2024 08:28:03 -0700 Subject: [PATCH] apply formatting and linting rules --- example/app.py | 3 +- src/flask_debugtoolbar/__init__.py | 161 ++++++++++-------- src/flask_debugtoolbar/panels/__init__.py | 19 +-- src/flask_debugtoolbar/panels/config_vars.py | 27 ++- src/flask_debugtoolbar/panels/g.py | 22 +-- src/flask_debugtoolbar/panels/headers.py | 70 ++++---- src/flask_debugtoolbar/panels/logger.py | 54 +++--- src/flask_debugtoolbar/panels/profiler.py | 68 ++++---- src/flask_debugtoolbar/panels/request_vars.py | 47 ++--- src/flask_debugtoolbar/panels/route_list.py | 30 ++-- src/flask_debugtoolbar/panels/sqlalchemy.py | 123 +++++++------ src/flask_debugtoolbar/panels/template.py | 108 ++++++------ src/flask_debugtoolbar/panels/timer.py | 119 ++++++------- src/flask_debugtoolbar/panels/versions.py | 33 ++-- src/flask_debugtoolbar/toolbar.py | 30 ++-- src/flask_debugtoolbar/utils.py | 48 +++--- tests/basic_app.py | 23 +-- tests/test_toolbar.py | 11 +- tests/test_utils.py | 133 +++++++-------- tox.ini | 1 + 20 files changed, 587 insertions(+), 543 deletions(-) diff --git a/example/app.py b/example/app.py index b94e1bc..a466be6 100644 --- a/example/app.py +++ b/example/app.py @@ -19,7 +19,8 @@ app.config["SECRET_KEY"] = "asd" app.config["SQLALCHEMY_RECORD_QUERIES"] = True app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////tmp/test.db" -# This is no longer needed for Flask-SQLAlchemy 3.0+, if you're using 2.X you'll want to define this: +# This is no longer needed for Flask-SQLAlchemy 3.0+, if you're using 2.X you'll +# want to define this: # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) diff --git a/src/flask_debugtoolbar/__init__.py b/src/flask_debugtoolbar/__init__.py index 232599e..4151ab6 100644 --- a/src/flask_debugtoolbar/__init__.py +++ b/src/flask_debugtoolbar/__init__.py @@ -4,18 +4,25 @@ import urllib.parse import warnings -from flask import Blueprint, current_app, request, g, send_from_directory, url_for +from flask import Blueprint +from flask import current_app +from flask import g +from flask import request +from flask import send_from_directory +from flask import url_for from flask.globals import request_ctx - from jinja2 import __version__ as __jinja_version__ -from jinja2 import Environment, PackageLoader +from jinja2 import Environment +from jinja2 import PackageLoader -from flask_debugtoolbar.toolbar import DebugToolbar -from flask_debugtoolbar.utils import decode_text, gzip_compress, gzip_decompress +from .toolbar import DebugToolbar +from .utils import decode_text +from .utils import gzip_compress +from .utils import gzip_decompress __version__ = importlib.metadata.version("flask-debugtoolbar") -module = Blueprint('debugtoolbar', __name__) +module = Blueprint("debugtoolbar", __name__) def replace_insensitive(string, target, replacement): @@ -25,8 +32,9 @@ def replace_insensitive(string, target, replacement): """ no_case = string.lower() index = no_case.rfind(target.lower()) + if index >= 0: - return string[:index] + replacement + string[index + len(target):] + return string[:index] + replacement + string[index + len(target) :] else: # no results so return the original string return string @@ -35,13 +43,11 @@ def _printable(value): try: return decode_text(repr(value)) except Exception as e: - return '' % ( - object.__repr__(value), type(e).__name__, e) + return f"" -class DebugToolbarExtension(object): - _static_dir = os.path.realpath( - os.path.join(os.path.dirname(__file__), 'static')) +class DebugToolbarExtension: + _static_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "static")) _toolbar_codes = [200, 201, 400, 401, 403, 404, 405, 500, 501, 502, 503, 504] _redirect_codes = [301, 302, 303, 304] @@ -50,21 +56,22 @@ def __init__(self, app=None): self.app = app # Support threads running `flask.copy_current_request_context` without # poping toolbar during `teardown_request` - self.debug_toolbars_var = contextvars.ContextVar('debug_toolbars') - jinja_extensions = ['jinja2.ext.i18n'] + self.debug_toolbars_var = contextvars.ContextVar("debug_toolbars") + jinja_extensions = ["jinja2.ext.i18n"] - if __jinja_version__[0] == '2': - jinja_extensions.append('jinja2.ext.with_') + if __jinja_version__[0] == "2": + jinja_extensions.append("jinja2.ext.with_") # Configure jinja for the internal templates and add url rules # for static data self.jinja_env = Environment( autoescape=True, extensions=jinja_extensions, - loader=PackageLoader(__name__, 'templates')) - self.jinja_env.filters['urlencode'] = urllib.parse.quote_plus - self.jinja_env.filters['printable'] = _printable - self.jinja_env.globals['url_for'] = url_for + loader=PackageLoader(__name__, "templates"), + ) + self.jinja_env.filters["urlencode"] = urllib.parse.quote_plus + self.jinja_env.filters["printable"] = _printable + self.jinja_env.globals["url_for"] = url_for if app is not None: self.init_app(app) @@ -73,13 +80,14 @@ def init_app(self, app): for k, v in self._default_config(app).items(): app.config.setdefault(k, v) - if not app.config['DEBUG_TB_ENABLED']: + if not app.config["DEBUG_TB_ENABLED"]: return - if not app.config.get('SECRET_KEY'): + if not app.config.get("SECRET_KEY"): raise RuntimeError( "The Flask-DebugToolbar requires the 'SECRET_KEY' config " - "var to be set") + "var to be set" + ) DebugToolbar.load_panels(app) @@ -90,30 +98,33 @@ def init_app(self, app): # Monkey-patch the Flask.dispatch_request method app.dispatch_request = self.dispatch_request - app.add_url_rule('/_debug_toolbar/static/', - '_debug_toolbar.static', self.send_static_file) + app.add_url_rule( + "/_debug_toolbar/static/", + "_debug_toolbar.static", + self.send_static_file, + ) - app.register_blueprint(module, url_prefix='/_debug_toolbar/views') + app.register_blueprint(module, url_prefix="/_debug_toolbar/views") def _default_config(self, app): return { - 'DEBUG_TB_ENABLED': app.debug, - 'DEBUG_TB_HOSTS': (), - 'DEBUG_TB_INTERCEPT_REDIRECTS': True, - 'DEBUG_TB_PANELS': ( - 'flask_debugtoolbar.panels.versions.VersionDebugPanel', - 'flask_debugtoolbar.panels.timer.TimerDebugPanel', - 'flask_debugtoolbar.panels.headers.HeaderDebugPanel', - 'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel', - 'flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel', - 'flask_debugtoolbar.panels.template.TemplateDebugPanel', - 'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel', - 'flask_debugtoolbar.panels.logger.LoggingPanel', - 'flask_debugtoolbar.panels.route_list.RouteListDebugPanel', - 'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel', - 'flask_debugtoolbar.panels.g.GDebugPanel', + "DEBUG_TB_ENABLED": app.debug, + "DEBUG_TB_HOSTS": (), + "DEBUG_TB_INTERCEPT_REDIRECTS": True, + "DEBUG_TB_PANELS": ( + "flask_debugtoolbar.panels.versions.VersionDebugPanel", + "flask_debugtoolbar.panels.timer.TimerDebugPanel", + "flask_debugtoolbar.panels.headers.HeaderDebugPanel", + "flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel", + "flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel", + "flask_debugtoolbar.panels.template.TemplateDebugPanel", + "flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel", + "flask_debugtoolbar.panels.logger.LoggingPanel", + "flask_debugtoolbar.panels.route_list.RouteListDebugPanel", + "flask_debugtoolbar.panels.profiler.ProfilerDebugPanel", + "flask_debugtoolbar.panels.g.GDebugPanel", ), - 'SQLALCHEMY_RECORD_QUERIES': app.debug, + "SQLALCHEMY_RECORD_QUERIES": app.debug, } def dispatch_request(self): @@ -128,22 +139,24 @@ def dispatch_request(self): # if we provide automatic options for this URL and the # request came with the OPTIONS method, reply automatically - if getattr(rule, 'provide_automatic_options', False) \ - and req.method == 'OPTIONS': + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): return app.make_default_options_response() # otherwise dispatch to the handler for that endpoint view_func = app.view_functions[rule.endpoint] view_func = self.process_view(app, view_func, req.view_args) - return view_func(**req.view_args) def _show_toolbar(self): """Return a boolean to indicate if we need to show the toolbar.""" - if request.blueprint == 'debugtoolbar': + if request.blueprint == "debugtoolbar": return False - hosts = current_app.config['DEBUG_TB_HOSTS'] + hosts = current_app.config["DEBUG_TB_HOSTS"] + if hosts and request.remote_addr not in hosts: return False @@ -161,17 +174,19 @@ def process_request(self): real_request = request._get_current_object() self.debug_toolbars_var.set({}) - self.debug_toolbars_var.get()[real_request] = ( - DebugToolbar(real_request, self.jinja_env)) + self.debug_toolbars_var.get()[real_request] = DebugToolbar( + real_request, self.jinja_env + ) for panel in self.debug_toolbars_var.get()[real_request].panels: panel.process_request(real_request) def process_view(self, app, view_func, view_kwargs): - """ This method is called just before the flask view is called. + """This method is called just before the flask view is called. This is done by the dispatch_request method. """ real_request = request._get_current_object() + try: toolbar = self.debug_toolbars_var.get({})[real_request] except KeyError: @@ -179,6 +194,7 @@ def process_view(self, app, view_func, view_kwargs): for panel in toolbar.panels: new_view = panel.process_view(real_request, view_func, view_kwargs) + if new_view: view_func = new_view @@ -186,20 +202,22 @@ def process_view(self, app, view_func, view_kwargs): def process_response(self, response): real_request = request._get_current_object() + if real_request not in self.debug_toolbars_var.get({}): return response # Intercept http redirect codes and display an html page with a # link to the target. - if current_app.config['DEBUG_TB_INTERCEPT_REDIRECTS']: + if current_app.config["DEBUG_TB_INTERCEPT_REDIRECTS"]: if response.status_code in self._redirect_codes: redirect_to = response.location redirect_code = response.status_code + if redirect_to: - content = self.render('redirect.html', { - 'redirect_to': redirect_to, - 'redirect_code': redirect_code - }) + content = self.render( + "redirect.html", + {"redirect_to": redirect_to, "redirect_code": redirect_code}, + ) response.content_length = len(content) response.location = None response.response = [content] @@ -207,29 +225,34 @@ def process_response(self, response): # If the http response code is an allowed code then we process to add the # toolbar to the returned html response. - if not (response.status_code in self._toolbar_codes and - response.is_sequence and - response.headers['content-type'].startswith('text/html')): + if not ( + response.status_code in self._toolbar_codes + and response.is_sequence + and response.headers["content-type"].startswith("text/html") + ): return response - content_encoding = response.headers.get('Content-Encoding') - if content_encoding and 'gzip' in content_encoding: + content_encoding = response.headers.get("Content-Encoding") + + if content_encoding and "gzip" in content_encoding: response_html = gzip_decompress(response.data).decode() else: response_html = response.get_data(as_text=True) no_case = response_html.lower() - body_end = no_case.rfind('') + body_end = no_case.rfind("") if body_end >= 0: before = response_html[:body_end] after = response_html[body_end:] - elif no_case.startswith(''): + elif no_case.startswith(""): before = response_html - after = '' + after = "" else: - warnings.warn('Could not insert debug toolbar.' - ' tag not found in response.') + warnings.warn( + "Could not insert debug toolbar." " tag not found in response.", + stacklevel=1, + ) return response toolbar = self.debug_toolbars_var.get()[real_request] @@ -239,10 +262,12 @@ def process_response(self, response): toolbar_html = toolbar.render_toolbar() - content = ''.join((before, toolbar_html, after)) - content = content.encode('utf-8') - if content_encoding and 'gzip' in content_encoding: + content = "".join((before, toolbar_html, after)) + content = content.encode("utf-8") + + if content_encoding and "gzip" in content_encoding: content = gzip_compress(content) + response.response = [content] response.content_length = len(content) diff --git a/src/flask_debugtoolbar/panels/__init__.py b/src/flask_debugtoolbar/panels/__init__.py index 6dab5bd..3d34966 100644 --- a/src/flask_debugtoolbar/panels/__init__.py +++ b/src/flask_debugtoolbar/panels/__init__.py @@ -1,10 +1,6 @@ -"""Base DebugPanel class""" +class DebugPanel: + """Base class for debug panels.""" - -class DebugPanel(object): - """ - Base class for debug panels. - """ # name = Base # If content returns something, set to true in subclass @@ -18,10 +14,11 @@ class DebugPanel(object): context = {} # Panel methods - def __init__(self, jinja_env, context={}): - self.context.update(context) - self.jinja_env = jinja_env + def __init__(self, jinja_env, context=None): + if context is not None: + self.context.update(context) + self.jinja_env = jinja_env # If the client enabled the panel self.is_active = False @@ -53,7 +50,7 @@ def render(self, template_name, context): return template.render(**context) def dom_id(self): - return 'flDebug%sPanel' % (self.name.replace(' ', '')) + return f"flDebug{self.name.replace(' ', '')}Panel" def nav_title(self): """Title showing in toolbar""" @@ -61,7 +58,7 @@ def nav_title(self): def nav_subtitle(self): """Subtitle showing until title in toolbar""" - return '' + return "" def title(self): """Title showing in panel""" diff --git a/src/flask_debugtoolbar/panels/config_vars.py b/src/flask_debugtoolbar/panels/config_vars.py index 7f8d11e..c2ab464 100644 --- a/src/flask_debugtoolbar/panels/config_vars.py +++ b/src/flask_debugtoolbar/panels/config_vars.py @@ -1,29 +1,28 @@ from flask import current_app -from flask_debugtoolbar.panels import DebugPanel -_ = lambda x: x +from . import DebugPanel class ConfigVarsDebugPanel(DebugPanel): - """ - A panel to display all variables from Flask configuration - """ - name = 'ConfigVars' + """A panel to display all variables from Flask configuration.""" + + name = "ConfigVars" has_content = True def nav_title(self): - return _('Config') + return "Config" def title(self): - return _('Config') + return "Config" def url(self): - return '' + return "" def content(self): context = self.context.copy() - context.update({ - 'config': current_app.config, - }) - - return self.render('panels/config_vars.html', context) + context.update( + { + "config": current_app.config, + } + ) + return self.render("panels/config_vars.html", context) diff --git a/src/flask_debugtoolbar/panels/g.py b/src/flask_debugtoolbar/panels/g.py index 0c1172f..da7dc14 100644 --- a/src/flask_debugtoolbar/panels/g.py +++ b/src/flask_debugtoolbar/panels/g.py @@ -1,28 +1,24 @@ from flask import g -from flask_debugtoolbar.panels import DebugPanel -_ = lambda x: x +from . import DebugPanel class GDebugPanel(DebugPanel): - """ - A panel to display flask.g content. - """ - name = 'g' + """A panel to display ``flask.g`` content.""" + + name = "g" has_content = True def nav_title(self): - return _('flask.g') + return "flask.g" def title(self): - return _('flask.g content') + return "flask.g content" def url(self): - return '' + return "" def content(self): context = self.context.copy() - context.update({ - 'g_content': g.__dict__ - }) - return self.render('panels/g.html', context) + context.update({"g_content": g.__dict__}) + return self.render("panels/g.html", context) diff --git a/src/flask_debugtoolbar/panels/headers.py b/src/flask_debugtoolbar/panels/headers.py index 0b37acd..b0e6717 100644 --- a/src/flask_debugtoolbar/panels/headers.py +++ b/src/flask_debugtoolbar/panels/headers.py @@ -1,56 +1,54 @@ -from flask_debugtoolbar.panels import DebugPanel - -_ = lambda x: x +from . import DebugPanel class HeaderDebugPanel(DebugPanel): - """ - A panel to display HTTP headers. - """ - name = 'Header' + """A panel to display HTTP headers.""" + + name = "Header" has_content = True # List of headers we want to display header_filter = ( - 'CONTENT_TYPE', - 'HTTP_ACCEPT', - 'HTTP_ACCEPT_CHARSET', - 'HTTP_ACCEPT_ENCODING', - 'HTTP_ACCEPT_LANGUAGE', - 'HTTP_CACHE_CONTROL', - 'HTTP_CONNECTION', - 'HTTP_HOST', - 'HTTP_KEEP_ALIVE', - 'HTTP_REFERER', - 'HTTP_USER_AGENT', - 'QUERY_STRING', - 'REMOTE_ADDR', - 'REMOTE_HOST', - 'REQUEST_METHOD', - 'SCRIPT_NAME', - 'SERVER_NAME', - 'SERVER_PORT', - 'SERVER_PROTOCOL', - 'SERVER_SOFTWARE', + "CONTENT_TYPE", + "HTTP_ACCEPT", + "HTTP_ACCEPT_CHARSET", + "HTTP_ACCEPT_ENCODING", + "HTTP_ACCEPT_LANGUAGE", + "HTTP_CACHE_CONTROL", + "HTTP_CONNECTION", + "HTTP_HOST", + "HTTP_KEEP_ALIVE", + "HTTP_REFERER", + "HTTP_USER_AGENT", + "QUERY_STRING", + "REMOTE_ADDR", + "REMOTE_HOST", + "REQUEST_METHOD", + "SCRIPT_NAME", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", + "SERVER_SOFTWARE", ) def nav_title(self): - return _('HTTP Headers') + return "HTTP Headers" def title(self): - return _('HTTP Headers') + return "HTTP Headers" def url(self): - return '' + return "" def process_request(self, request): self.headers = dict( - [(k, request.environ[k]) - for k in self.header_filter if k in request.environ] + [ + (k, request.environ[k]) + for k in self.header_filter + if k in request.environ + ] ) def content(self): context = self.context.copy() - context.update({ - 'headers': self.headers - }) - return self.render('panels/headers.html', context) + context.update({"headers": self.headers}) + return self.render("panels/headers.html", context) diff --git a/src/flask_debugtoolbar/panels/logger.py b/src/flask_debugtoolbar/panels/logger.py index 0408292..b8528e1 100644 --- a/src/flask_debugtoolbar/panels/logger.py +++ b/src/flask_debugtoolbar/panels/logger.py @@ -1,18 +1,14 @@ -from __future__ import with_statement - import datetime import logging import threading -from flask_debugtoolbar.panels import DebugPanel -from flask_debugtoolbar.utils import format_fname - -_ = lambda x: x +from ..utils import format_fname +from . import DebugPanel class ThreadTrackingHandler(logging.Handler): def __init__(self): - logging.Handler.__init__(self) + super().__init__() self.records = {} # a dictionary that maps threads to log records def emit(self, record): @@ -25,13 +21,16 @@ def get_records(self, thread=None): """ if thread is None: thread = threading.current_thread() + if thread not in self.records: self.records[thread] = [] + return self.records[thread] def clear_records(self, thread=None): if thread is None: thread = threading.current_thread() + if thread in self.records: del self.records[thread] @@ -42,8 +41,10 @@ def clear_records(self, thread=None): def _init_once(): global handler + if handler is not None: return + with _init_lock: if handler is not None: return @@ -55,14 +56,13 @@ def _init_once(): # be seen. from werkzeug._internal import _log - _log('debug', 'Initializing Flask-DebugToolbar log handler') - + _log("debug", "Initializing Flask-DebugToolbar log handler") handler = ThreadTrackingHandler() logging.root.addHandler(handler) class LoggingPanel(DebugPanel): - name = 'Logging' + name = "Logging" has_content = True def process_request(self, request): @@ -75,32 +75,34 @@ def get_and_delete(self): return records def nav_title(self): - return _("Logging") + return "Logging" def nav_subtitle(self): - # FIXME l10n: use ngettext num_records = len(handler.get_records()) - return '%s message%s' % (num_records, '' if num_records == 1 else 's') + plural = "message" if num_records == 1 else "messages" + return f"{num_records} {plural}" def title(self): - return _('Log Messages') + return "Log Messages" def url(self): - return '' + return "" def content(self): records = [] + for record in self.get_and_delete(): - records.append({ - 'message': record.getMessage(), - 'time': datetime.datetime.fromtimestamp(record.created), - 'level': record.levelname, - 'file': format_fname(record.pathname), - 'file_long': record.pathname, - 'line': record.lineno, - }) + records.append( + { + "message": record.getMessage(), + "time": datetime.datetime.fromtimestamp(record.created), + "level": record.levelname, + "file": format_fname(record.pathname), + "file_long": record.pathname, + "line": record.lineno, + } + ) context = self.context.copy() - context.update({'records': records}) - - return self.render('panels/logger.html', context) + context.update({"records": records}) + return self.render("panels/logger.html", context) diff --git a/src/flask_debugtoolbar/panels/profiler.py b/src/flask_debugtoolbar/panels/profiler.py index f1ca9da..014f0ba 100644 --- a/src/flask_debugtoolbar/panels/profiler.py +++ b/src/flask_debugtoolbar/panels/profiler.py @@ -1,27 +1,27 @@ -try: - import cProfile as profile -except ImportError: - import profile import functools import pstats from flask import current_app -from flask_debugtoolbar.panels import DebugPanel -from flask_debugtoolbar.utils import format_fname +from ..utils import format_fname +from . import DebugPanel -class ProfilerDebugPanel(DebugPanel): - """ - Panel that displays the time a response took with cProfile output. - """ +try: + import cProfile as profile +except ImportError: + import profile - name = 'Profiler' +class ProfilerDebugPanel(DebugPanel): + """Panel that displays the time a response took with cProfile output.""" + + name = "Profiler" user_activate = True - def __init__(self, jinja_env, context={}): - DebugPanel.__init__(self, jinja_env, context=context) - if current_app.config.get('DEBUG_TB_PROFILER_ENABLED'): + def __init__(self, jinja_env, context=None): + super().__init__(jinja_env, context=context) + + if current_app.config.get("DEBUG_TB_PROFILER_ENABLED"): self.is_active = True self.dump_filename = current_app.config.get( "DEBUG_TB_PROFILER_DUMP_FILENAME" @@ -49,45 +49,48 @@ def process_response(self, request, response): if self.profiler is not None: self.profiler.disable() + try: stats = pstats.Stats(self.profiler) except TypeError: self.is_active = False return False + function_calls = [] + for func in stats.sort_stats(1).fcn_list: current = {} info = stats.stats[func] # Number of calls if info[0] != info[1]: - current['ncalls'] = '%d/%d' % (info[1], info[0]) + current["ncalls"] = f"{info[1]}/{info[0]}" else: - current['ncalls'] = info[1] + current["ncalls"] = info[1] # Total time - current['tottime'] = info[2] * 1000 + current["tottime"] = info[2] * 1000 # Quotient of total time divided by number of calls if info[1]: - current['percall'] = info[2] * 1000 / info[1] + current["percall"] = info[2] * 1000 / info[1] else: - current['percall'] = 0 + current["percall"] = 0 # Cumulative time - current['cumtime'] = info[3] * 1000 + current["cumtime"] = info[3] * 1000 # Quotient of the cumulative time divided by the number of # primitive calls. if info[0]: - current['percall_cum'] = info[3] * 1000 / info[0] + current["percall_cum"] = info[3] * 1000 / info[0] else: - current['percall_cum'] = 0 + current["percall_cum"] = 0 # Filename filename = pstats.func_std_string(func) - current['filename_long'] = filename - current['filename'] = format_fname(filename) + current["filename_long"] = filename + current["filename"] = format_fname(filename) function_calls.append(current) self.stats = stats @@ -98,6 +101,7 @@ def process_response(self, request, response): filename = self.dump_filename() else: filename = self.dump_filename + self.profiler.dump_stats(filename) return response @@ -105,25 +109,27 @@ def process_response(self, request, response): def title(self): if not self.is_active: return "Profiler not active" - return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,) + + return f"View: {float(self.stats.total_tt) * 1000:.2f}ms" def nav_title(self): - return 'Profiler' + return "Profiler" def nav_subtitle(self): if not self.is_active: return "in-active" - return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,) + + return f"View: {float(self.stats.total_tt) * 1000:.2f}ms" def url(self): - return '' + return "" def content(self): if not self.is_active: return "The profiler is not activated, activate it to use it" context = { - 'stats': self.stats, - 'function_calls': self.function_calls, + "stats": self.stats, + "function_calls": self.function_calls, } - return self.render('panels/profiler.html', context) + return self.render("panels/profiler.html", context) diff --git a/src/flask_debugtoolbar/panels/request_vars.py b/src/flask_debugtoolbar/panels/request_vars.py index 4119dbf..a886090 100644 --- a/src/flask_debugtoolbar/panels/request_vars.py +++ b/src/flask_debugtoolbar/panels/request_vars.py @@ -1,25 +1,22 @@ from flask import session -from flask_debugtoolbar.panels import DebugPanel - -_ = lambda x: x +from . import DebugPanel class RequestVarsDebugPanel(DebugPanel): - """ - A panel to display request variables (POST/GET, session, cookies). - """ - name = 'RequestVars' + """A panel to display request variables (POST/GET, session, cookies).""" + + name = "RequestVars" has_content = True def nav_title(self): - return _('Request Vars') + return "Request Vars" def title(self): - return _('Request Vars') + return "Request Vars" def url(self): - return '' + return "" def process_request(self, request): self.request = request @@ -34,16 +31,20 @@ def process_view(self, request, view_func, view_kwargs): def content(self): context = self.context.copy() - context.update({ - 'get': self.request.args.lists(), - 'post': self.request.form.lists(), - 'cookies': self.request.cookies.items(), - 'view_func': ('%s.%s' % (self.view_func.__module__, - self.view_func.__name__) - if self.view_func else '[unknown]'), - 'view_args': self.view_args, - 'view_kwargs': self.view_kwargs or {}, - 'session': self.session.items(), - }) - - return self.render('panels/request_vars.html', context) + context.update( + { + "get": self.request.args.lists(), + "post": self.request.form.lists(), + "cookies": self.request.cookies.items(), + "view_func": ( + f"{self.view_func.__module__}.{self.view_func.__name__}" + if self.view_func + else "[unknown]" + ), + "view_args": self.view_args, + "view_kwargs": self.view_kwargs or {}, + "session": self.session.items(), + } + ) + + return self.render("panels/request_vars.html", context) diff --git a/src/flask_debugtoolbar/panels/route_list.py b/src/flask_debugtoolbar/panels/route_list.py index a13cd01..461815c 100644 --- a/src/flask_debugtoolbar/panels/route_list.py +++ b/src/flask_debugtoolbar/panels/route_list.py @@ -1,38 +1,40 @@ -from flask_debugtoolbar.panels import DebugPanel from flask import current_app -_ = lambda x: x +from . import DebugPanel class RouteListDebugPanel(DebugPanel): - """ - Panel that displays the URL routing rules. - """ - name = 'RouteList' + """Panel that displays the URL routing rules.""" + + name = "RouteList" has_content = True routes = [] def nav_title(self): - return _('Route List') + return "Route List" def title(self): - return _('Route List') + return "Route List" def url(self): - return '' + return "" def nav_subtitle(self): count = len(self.routes) - return '%s %s' % (count, 'route' if count == 1 else 'routes') + plural = "route" if count == 1 else "routes" + return f"{count} {plural}" def process_request(self, request): self.routes = [ rule for rule in current_app.url_map.iter_rules() - if not rule.rule.startswith('/_debug_toolbar') + if not rule.rule.startswith("/_debug_toolbar") ] def content(self): - return self.render('panels/route_list.html', { - 'routes': self.routes, - }) + return self.render( + "panels/route_list.html", + { + "routes": self.routes, + }, + ) diff --git a/src/flask_debugtoolbar/panels/sqlalchemy.py b/src/flask_debugtoolbar/panels/sqlalchemy.py index 44aaafb..49e6db4 100644 --- a/src/flask_debugtoolbar/panels/sqlalchemy.py +++ b/src/flask_debugtoolbar/panels/sqlalchemy.py @@ -1,3 +1,14 @@ +import itsdangerous +from flask import abort +from flask import current_app +from flask import g +from flask import request + +from .. import module +from ..utils import format_fname +from ..utils import format_sql +from . import DebugPanel + try: from flask_sqlalchemy import SQLAlchemy except ImportError: @@ -7,6 +18,7 @@ else: try: from flask_sqlalchemy.record_queries import get_recorded_queries + debug_enables_record_queries = False except ImportError: # For flask_sqlalchemy < 3.0.0 @@ -15,28 +27,21 @@ # flask_sqlalchemy < 3.0.0 automatically enabled # SQLALCHEMY_RECORD_QUERIES in debug or test mode debug_enables_record_queries = True - - location_property = 'context' + location_property = "context" else: - location_property = 'location' - sqlalchemy_available = True - -from flask import request, current_app, abort, g -from flask_debugtoolbar import module -from flask_debugtoolbar.panels import DebugPanel -from flask_debugtoolbar.utils import format_fname, format_sql -import itsdangerous + location_property = "location" -_ = lambda x: x + sqlalchemy_available = True def query_signer(): - return itsdangerous.URLSafeSerializer(current_app.config['SECRET_KEY'], - salt='fdt-sql-query') + return itsdangerous.URLSafeSerializer( + current_app.config["SECRET_KEY"], salt="fdt-sql-query" + ) def is_select(statement): - prefix = b'select' if isinstance(statement, bytes) else 'select' + prefix = b"select" if isinstance(statement, bytes) else "select" return statement.lower().strip().startswith(prefix) @@ -52,7 +57,7 @@ def dump_query(statement, params): def load_query(data): try: - statement, params = query_signer().loads(request.args['query']) + statement, params = query_signer().loads(request.args["query"]) except (itsdangerous.BadSignature, TypeError): abort(406) @@ -64,14 +69,13 @@ def load_query(data): def extension_used(): - return 'sqlalchemy' in current_app.extensions + return "sqlalchemy" in current_app.extensions def recording_enabled(): return ( - (debug_enables_record_queries and current_app.debug) or - current_app.config.get('SQLALCHEMY_RECORD_QUERIES') - ) + debug_enables_record_queries and current_app.debug + ) or current_app.config.get("SQLALCHEMY_RECORD_QUERIES") def is_available(): @@ -86,10 +90,9 @@ def get_queries(): class SQLAlchemyDebugPanel(DebugPanel): - """ - Panel that displays the time a response took in milliseconds. - """ - name = 'SQLAlchemy' + """Panel that displays the time a response took in milliseconds.""" + + name = "SQLAlchemy" @property def has_content(self): @@ -102,64 +105,76 @@ def process_response(self, request, response): pass def nav_title(self): - return _('SQLAlchemy') + return "SQLAlchemy" def nav_subtitle(self): count = len(get_queries()) if not count and not is_available(): - return 'Unavailable' + return "Unavailable" - return '%d %s' % (count, 'query' if count == 1 else 'queries') + plural = "query" if count == 1 else "queries" + return f"{count} {plural}" def title(self): - return _('SQLAlchemy queries') + return "SQLAlchemy queries" def url(self): - return '' + return "" def content(self): queries = get_queries() if not queries and not is_available(): - return self.render('panels/sqlalchemy_error.html', { - 'sqlalchemy_available': sqlalchemy_available, - 'extension_used': extension_used(), - 'recording_enabled': recording_enabled(), - }) + return self.render( + "panels/sqlalchemy_error.html", + { + "sqlalchemy_available": sqlalchemy_available, + "extension_used": extension_used(), + "recording_enabled": recording_enabled(), + }, + ) data = [] + for query in queries: - data.append({ - 'duration': query.duration, - 'sql': format_sql(query.statement, query.parameters), - 'signed_query': dump_query(query.statement, query.parameters), - 'location_long': getattr(query, location_property), - 'location': format_fname(getattr(query, location_property)) - }) - return self.render('panels/sqlalchemy.html', {'queries': data}) + data.append( + { + "duration": query.duration, + "sql": format_sql(query.statement, query.parameters), + "signed_query": dump_query(query.statement, query.parameters), + "location_long": getattr(query, location_property), + "location": format_fname(getattr(query, location_property)), + } + ) + + return self.render("panels/sqlalchemy.html", {"queries": data}) # Panel views -@module.route('/sqlalchemy/sql_select', methods=['GET', 'POST']) -@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'], - defaults=dict(explain=True)) +@module.route("/sqlalchemy/sql_select", methods=["GET", "POST"]) +@module.route( + "/sqlalchemy/sql_explain", methods=["GET", "POST"], defaults=dict(explain=True) +) def sql_select(explain=False): - statement, params = load_query(request.args['query']) + statement, params = load_query(request.args["query"]) engine = SQLAlchemy().get_engine(current_app) if explain: - if engine.driver == 'pysqlite': - statement = 'EXPLAIN QUERY PLAN\n%s' % statement + if engine.driver == "pysqlite": + statement = f"EXPLAIN QUERY PLAN\n{statement}" else: - statement = 'EXPLAIN\n%s' % statement + statement = f"EXPLAIN\n{statement}" result = engine.execute(statement, params) - return g.debug_toolbar.render('panels/sqlalchemy_select.html', { - 'result': result.fetchall(), - 'headers': result.keys(), - 'sql': format_sql(statement, params), - 'duration': float(request.args['duration']), - }) + return g.debug_toolbar.render( + "panels/sqlalchemy_select.html", + { + "result": result.fetchall(), + "headers": result.keys(), + "sql": format_sql(statement, params), + "duration": float(request.args["duration"]), + }, + ) diff --git a/src/flask_debugtoolbar/panels/template.py b/src/flask_debugtoolbar/panels/template.py index c19a214..6a6250c 100644 --- a/src/flask_debugtoolbar/panels/template.py +++ b/src/flask_debugtoolbar/panels/template.py @@ -3,21 +3,22 @@ import sys import uuid -from flask import ( - template_rendered, request, g, - Response, current_app, abort, url_for -) -from flask_debugtoolbar import module -from flask_debugtoolbar.panels import DebugPanel +from flask import abort +from flask import current_app +from flask import g +from flask import request +from flask import Response +from flask import template_rendered +from flask import url_for -_ = lambda x: x +from .. import module +from . import DebugPanel class TemplateDebugPanel(DebugPanel): - """ - Panel that displays the time a response took in milliseconds. - """ - name = 'Template' + """Panel that displays the time a response took in milliseconds.""" + + name = "Template" has_content = True # save the context for the 5 most recent requests @@ -28,10 +29,11 @@ def get_cache_for_key(self, key): for cache_key, value in self.template_cache: if key == cache_key: return value + raise KeyError(key) def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.key = str(uuid.uuid4()) self.templates = [] template_rendered.connect(self._store_template_info) @@ -41,6 +43,7 @@ def _store_template_info(self, sender, **kwargs): # actually a template for this request if not self.templates and is_editor_enabled(): self.template_cache.append((self.key, self.templates)) + self.templates.append(kwargs) def process_request(self, request): @@ -50,27 +53,30 @@ def process_response(self, request, response): pass def nav_title(self): - return _('Templates') + return "Templates" def nav_subtitle(self): - return "%d rendered" % len(self.templates) + return f"{len(self.templates)} rendered" def title(self): - return _('Templates') + return "Templates" def url(self): - return '' + return "" def content(self): - return self.render('panels/template.html', { - 'key': self.key, - 'templates': self.templates, - 'editable': is_editor_enabled(), - }) + return self.render( + "panels/template.html", + { + "key": self.key, + "templates": self.templates, + "editable": is_editor_enabled(), + }, + ) def is_editor_enabled(): - return current_app.config.get('DEBUG_TB_TEMPLATE_EDITOR_ENABLED') + return current_app.config.get("DEBUG_TB_TEMPLATE_EDITOR_ENABLED") def require_enabled(): @@ -79,58 +85,64 @@ def require_enabled(): def _get_source(template): - with open(template.filename, 'rb') as fp: + with open(template.filename, "rb") as fp: source = fp.read() + return source.decode(_template_encoding()) def _template_encoding(): - return getattr(current_app.jinja_loader, 'encoding', 'utf-8') + return getattr(current_app.jinja_loader, "encoding", "utf-8") -@module.route('/template/') +@module.route("/template/") def template_editor(key): require_enabled() # TODO set up special loader that caches templates it loads # and can override template contents - templates = [t['template'] for t in - TemplateDebugPanel.get_cache_for_key(key)] - return g.debug_toolbar.render('panels/template_editor.html', { - 'static_path': url_for('_debug_toolbar.static', filename=''), - 'request': request, - 'templates': [ - {'name': t.name, 'source': _get_source(t)} - for t in templates - ] - }) - - -@module.route('/template//save', methods=['POST']) + templates = [t["template"] for t in TemplateDebugPanel.get_cache_for_key(key)] + return g.debug_toolbar.render( + "panels/template_editor.html", + { + "static_path": url_for("_debug_toolbar.static", filename=""), + "request": request, + "templates": [ + {"name": t.name, "source": _get_source(t)} for t in templates + ], + }, + ) + + +@module.route("/template//save", methods=["POST"]) def save_template(key): require_enabled() - template = TemplateDebugPanel.get_cache_for_key(key)[0]['template'] - content = request.form['content'].encode(_template_encoding()) - with open(template.filename, 'wb') as fp: + template = TemplateDebugPanel.get_cache_for_key(key)[0]["template"] + content = request.form["content"].encode(_template_encoding()) + + with open(template.filename, "wb") as fp: fp.write(content) - return 'ok' + return "ok" -@module.route('/template/', methods=['POST']) + +@module.route("/template/", methods=["POST"]) def template_preview(key): require_enabled() - context = TemplateDebugPanel.get_cache_for_key(key)[0]['context'] - content = request.form['content'] + context = TemplateDebugPanel.get_cache_for_key(key)[0]["context"] + content = request.form["content"] env = current_app.jinja_env.overlay(autoescape=True) + try: template = env.from_string(content) return template.render(context) except Exception as e: tb = sys.exc_info()[2] + try: while tb.tb_next: tb = tb.tb_next - msg = {'lineno': tb.tb_lineno, 'error': str(e)} - return Response(json.dumps(msg), status=400, - mimetype='application/json') + + msg = {"lineno": tb.tb_lineno, "error": str(e)} + return Response(json.dumps(msg), status=400, mimetype="application/json") finally: del tb diff --git a/src/flask_debugtoolbar/panels/timer.py b/src/flask_debugtoolbar/panels/timer.py index df3317e..2478ae0 100644 --- a/src/flask_debugtoolbar/panels/timer.py +++ b/src/flask_debugtoolbar/panels/timer.py @@ -1,95 +1,88 @@ +import time + +from . import DebugPanel + try: import resource -except ImportError: - pass # Will fail on Win32 systems -import time -from flask_debugtoolbar.panels import DebugPanel -_ = lambda x: x + HAVE_RESOURCE = True +except ImportError: + HAVE_RESOURCE = False class TimerDebugPanel(DebugPanel): - """ - Panel that displays the time a response took in milliseconds. - """ - name = 'Timer' - try: # if resource module not available, don't show content panel - resource - except NameError: - has_content = False - has_resource = False - else: - has_content = True - has_resource = True + """Panel that displays the time a response took in milliseconds.""" + + name = "Timer" + has_content = HAVE_RESOURCE def process_request(self, request): self._start_time = time.time() - if self.has_resource: + + if HAVE_RESOURCE: self._start_rusage = resource.getrusage(resource.RUSAGE_SELF) def process_response(self, request, response): self.total_time = (time.time() - self._start_time) * 1000 - if self.has_resource: + + if HAVE_RESOURCE: self._end_rusage = resource.getrusage(resource.RUSAGE_SELF) def nav_title(self): - return _('Time') + return "Time" def nav_subtitle(self): - # TODO l10n - if not self.has_resource: - return 'TOTAL: %0.2fms' % (self.total_time) + if not HAVE_RESOURCE: + return f"TOTAL: {self.total_time:0.2f}ms" utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime - return 'CPU: %0.2fms (%0.2fms)' % ( - (utime + stime) * 1000.0, self.total_time) + return f"CPU: {(utime + stime) * 1000.0:0.2f}ms ({self.total_time:0.2f}ms)" def title(self): - return _('Resource Usage') + return "Resource Usage" def url(self): - return '' + return "" def _elapsed_ru(self, name): - return (getattr(self._end_rusage, name) - getattr(self._start_rusage, name)) + return getattr(self._end_rusage, name) - getattr(self._start_rusage, name) def content(self): - - utime = 1000 * self._elapsed_ru('ru_utime') - stime = 1000 * self._elapsed_ru('ru_stime') - vcsw = self._elapsed_ru('ru_nvcsw') - ivcsw = self._elapsed_ru('ru_nivcsw') - # minflt = self._elapsed_ru('ru_minflt') - # majflt = self._elapsed_ru('ru_majflt') - -# these are documented as not meaningful under Linux. If you're running BSD -# feel free to enable them, and add any others that I hadn't gotten to before -# I noticed that I was getting nothing but zeroes and that the docs agreed. :-( -# -# blkin = self._elapsed_ru('ru_inblock') -# blkout = self._elapsed_ru('ru_oublock') -# swap = self._elapsed_ru('ru_nswap') -# rss = self._end_rusage.ru_maxrss -# srss = self._end_rusage.ru_ixrss -# urss = self._end_rusage.ru_idrss -# usrss = self._end_rusage.ru_isrss - - # TODO l10n on values + utime = 1000 * self._elapsed_ru("ru_utime") + stime = 1000 * self._elapsed_ru("ru_stime") + vcsw = self._elapsed_ru("ru_nvcsw") + ivcsw = self._elapsed_ru("ru_nivcsw") + # minflt = self._elapsed_ru("ru_minflt") + # majflt = self._elapsed_ru("ru_majflt") + + # these are documented as not meaningful under Linux. If you're running BSD + # feel free to enable them, and add any others that I hadn't gotten to before + # I noticed that I was getting nothing but zeroes and that the docs agreed. :-( + # blkin = self._elapsed_ru("ru_inblock") + # blkout = self._elapsed_ru("ru_oublock") + # swap = self._elapsed_ru("ru_nswap") + # rss = self._end_rusage.ru_maxrss + # srss = self._end_rusage.ru_ixrss + # urss = self._end_rusage.ru_idrss + # usrss = self._end_rusage.ru_isrss rows = ( - (_('User CPU time'), '%0.3f msec' % utime), - (_('System CPU time'), '%0.3f msec' % stime), - (_('Total CPU time'), '%0.3f msec' % (utime + stime)), - (_('Elapsed time'), '%0.3f msec' % self.total_time), - (_('Context switches'), '%d voluntary, %d involuntary' % (vcsw, ivcsw)), - # ('Memory use', '%d max RSS, %d shared, %d unshared' % (rss, srss, urss + usrss)), - # ('Page faults', '%d no i/o, %d requiring i/o' % (minflt, majflt)), - # ('Disk operations', '%d in, %d out, %d swapout' % (blkin, blkout, swap)), + ("User CPU time", f"{utime:0.3f} msec"), + ("System CPU time", f"{stime:0.3f} msec"), + ("Total CPU time", f"{(utime + stime):0.3f} msec"), + ("Elapsed time", f"{self.total_time:0.3f} msec"), + ("Context switches", f"{vcsw} voluntary, {ivcsw} involuntary"), + # ( + # "Memory use", + # f"{rss} max RSS, {srss} shared, {urss + usrss} unshared", + # ), + # ("Page faults", f"{minflt} no i/o, {majflt} requiring i/o"), + # ("Disk operations", f"{blkin} in, {blkout} out, {swap} swapout"), ) - context = self.context.copy() - context.update({ - 'rows': rows, - }) - - return self.render('panels/timer.html', context) + context.update( + { + "rows": rows, + } + ) + return self.render("panels/timer.html", context) diff --git a/src/flask_debugtoolbar/panels/versions.py b/src/flask_debugtoolbar/panels/versions.py index 02e3b0f..2fd07a7 100644 --- a/src/flask_debugtoolbar/panels/versions.py +++ b/src/flask_debugtoolbar/panels/versions.py @@ -2,37 +2,36 @@ import os from sysconfig import get_path -from flask_debugtoolbar.panels import DebugPanel +from . import DebugPanel flask_version = importlib.metadata.version("flask") -_ = lambda x: x - class VersionDebugPanel(DebugPanel): - """ - Panel that displays the Flask version. - """ - name = 'Version' + """Panel that displays the Flask version.""" + + name = "Version" has_content = True def nav_title(self): - return _('Versions') + return "Versions" def nav_subtitle(self): - return 'Flask %s' % flask_version + return f"Flask {flask_version}" def url(self): - return '' + return "" def title(self): - return _('Versions') + return "Versions" def content(self): packages_metadata = [p.metadata for p in importlib.metadata.distributions()] - packages = sorted(packages_metadata, key=lambda p: p['Name'].lower()) - - return self.render('panels/versions.html', { - 'packages': packages, - 'python_lib_dir': os.path.normpath(get_path('platlib')), - }) + packages = sorted(packages_metadata, key=lambda p: p["Name"].lower()) + return self.render( + "panels/versions.html", + { + "packages": packages, + "python_lib_dir": os.path.normpath(get_path("platlib")), + }, + ) diff --git a/src/flask_debugtoolbar/toolbar.py b/src/flask_debugtoolbar/toolbar.py index 0dadd15..51e26d9 100644 --- a/src/flask_debugtoolbar/toolbar.py +++ b/src/flask_debugtoolbar/toolbar.py @@ -1,11 +1,11 @@ from urllib.parse import unquote -from flask import url_for, current_app +from flask import current_app +from flask import url_for from werkzeug.utils import import_string -class DebugToolbar(object): - +class DebugToolbar: _cached_panel_classes = {} def __init__(self, request, jinja_env): @@ -14,21 +14,20 @@ def __init__(self, request, jinja_env): self.panels = [] self.template_context = { - 'static_path': url_for('_debug_toolbar.static', filename='') + "static_path": url_for("_debug_toolbar.static", filename="") } self.create_panels() def create_panels(self): - """ - Populate debug panels - """ - activated = self.request.cookies.get('fldt_active', '') - activated = unquote(activated).split(';') + """Populate debug panels""" + activated = self.request.cookies.get("fldt_active", "") + activated = unquote(activated).split(";") for panel_class in self._iter_panels(current_app): - panel_instance = panel_class(jinja_env=self.jinja_env, - context=self.template_context) + panel_instance = panel_class( + jinja_env=self.jinja_env, context=self.template_context + ) if panel_instance.dom_id() in activated: panel_instance.is_active = True @@ -37,9 +36,9 @@ def create_panels(self): def render_toolbar(self): context = self.template_context.copy() - context.update({'panels': self.panels}) + context.update({"panels": self.panels}) - template = self.jinja_env.get_template('base.html') + template = self.jinja_env.get_template("base.html") return template.render(**context) @classmethod @@ -50,8 +49,9 @@ def load_panels(cls, app): @classmethod def _iter_panels(cls, app): - for panel_path in app.config['DEBUG_TB_PANELS']: + for panel_path in app.config["DEBUG_TB_PANELS"]: panel_class = cls._import_panel(app, panel_path) + if panel_class is not None: yield panel_class @@ -67,7 +67,7 @@ def _import_panel(cls, app, path): try: panel_class = import_string(path) except ImportError as e: - app.logger.warning('Disabled %s due to ImportError: %s', path, e) + app.logger.warning("Disabled %s due to ImportError: %s", path, e) panel_class = None cache[path] = panel_class diff --git a/src/flask_debugtoolbar/utils.py b/src/flask_debugtoolbar/utils.py index 76957e1..50144a3 100644 --- a/src/flask_debugtoolbar/utils.py +++ b/src/flask_debugtoolbar/utils.py @@ -1,33 +1,34 @@ +import gzip +import io import itertools import os.path import sys -import io -import gzip + +from flask import current_app +from markupsafe import Markup try: from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import SqlLexer from pygments.styles import get_style_by_name - PYGMENT_STYLE = get_style_by_name('colorful') + + PYGMENT_STYLE = get_style_by_name("colorful") HAVE_PYGMENTS = True except ImportError: HAVE_PYGMENTS = False try: import sqlparse + HAVE_SQLPARSE = True except ImportError: HAVE_SQLPARSE = False -from flask import current_app - -from markupsafe import Markup - def format_fname(value): # If the value has a builtin prefix, return it unchanged - if value.startswith(('{', '<')): + if value.startswith(("{", "<")): return value value = os.path.normpath(value) @@ -35,15 +36,14 @@ def format_fname(value): # If the file is absolute, try normalizing it relative to the project root # to handle it as a project file if os.path.isabs(value): - value = _shortest_relative_path( - value, [current_app.root_path], os.path) + value = _shortest_relative_path(value, [current_app.root_path], os.path) # If the value is a relative path, it is a project file if not os.path.isabs(value): - return os.path.join('.', value) + return os.path.join(".", value) # Otherwise, normalize other paths relative to sys.path - return '<%s>' % _shortest_relative_path(value, sys.path, os.path) + return f"<{_shortest_relative_path(value, sys.path, os.path)}>" def _shortest_relative_path(value, paths, path_module): @@ -59,43 +59,45 @@ def _relative_paths(value, paths, path_module): # on Windows, relpath throws a ValueError for # paths with different drives continue + if not relval.startswith(path_module.pardir): yield relval def decode_text(value): """ - Decode a text-like value for display. + Decode a text-like value for display. - Unicode values are returned unchanged. Byte strings will be decoded - with a text-safe replacement for unrecognized characters. + Unicode values are returned unchanged. Byte strings will be decoded + with a text-safe replacement for unrecognized characters. """ if isinstance(value, bytes): - return value.decode('ascii', 'replace') + return value.decode("ascii", "replace") else: return value def format_sql(query, args): if HAVE_SQLPARSE: - query = sqlparse.format(query, reindent=True, keyword_case='upper') + query = sqlparse.format(query, reindent=True, keyword_case="upper") if not HAVE_PYGMENTS: return decode_text(query) - return Markup(highlight( - query, - SqlLexer(), - HtmlFormatter(noclasses=True, style=PYGMENT_STYLE))) + return Markup( + highlight(query, SqlLexer(), HtmlFormatter(noclasses=True, style=PYGMENT_STYLE)) + ) def gzip_compress(data, compresslevel=6): buff = io.BytesIO() - with gzip.GzipFile(fileobj=buff, mode='wb', compresslevel=compresslevel) as f: + + with gzip.GzipFile(fileobj=buff, mode="wb", compresslevel=compresslevel) as f: f.write(data) + return buff.getvalue() def gzip_decompress(data): - with gzip.GzipFile(fileobj=io.BytesIO(data), mode='rb') as f: + with gzip.GzipFile(fileobj=io.BytesIO(data), mode="rb") as f: return f.read() diff --git a/tests/basic_app.py b/tests/basic_app.py index 9c3618d..ed43534 100644 --- a/tests/basic_app.py +++ b/tests/basic_app.py @@ -1,34 +1,35 @@ -from flask import Flask, render_template +from flask import Flask +from flask import render_template from flask_sqlalchemy import SQLAlchemy from flask_debugtoolbar import DebugToolbarExtension -app = Flask('basic_app') -app.config['DEBUG'] = True -app.config['SECRET_KEY'] = 'abc123' -app.config['SQLALCHEMY_RECORD_QUERIES'] = True -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' +app = Flask("basic_app") +app.config["DEBUG"] = True +app.config["SECRET_KEY"] = "abc123" +app.config["SQLALCHEMY_RECORD_QUERIES"] = True +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" # This is no longer needed for Flask-SQLAlchemy 3.0+, # if you're using 2.X you'll want to define this: # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # make sure these are printable in the config panel -app.config['BYTES_VALUE'] = b'\x00' -app.config['UNICODE_VALUE'] = u'\uffff' +app.config["BYTES_VALUE"] = b"\x00" +app.config["UNICODE_VALUE"] = "\uffff" toolbar = DebugToolbarExtension(app) db = SQLAlchemy(app) class Foo(db.Model): - __tablename__ = 'foo' + __tablename__ = "foo" id = db.Column(db.Integer, primary_key=True) -@app.route('/') +@app.route("/") def index(): Foo.query.filter_by(id=1).all() - return render_template('basic_app.html') + return render_template("basic_app.html") with app.app_context(): diff --git a/tests/test_toolbar.py b/tests/test_toolbar.py index 6ad24ac..f1d6553 100644 --- a/tests/test_toolbar.py +++ b/tests/test_toolbar.py @@ -1,16 +1,11 @@ -import sys - -from flask_debugtoolbar import _printable - - def load_app(name): app = __import__(name).app - app.config['TESTING'] = True + app.config["TESTING"] = True return app.test_client() def test_basic_app(): - app = load_app('basic_app') - index = app.get('/') + app = load_app("basic_app") + index = app.get("/") assert index.status_code == 200 assert b'