Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1e7d9907 | ||
|
|
d0360218fd | ||
|
|
e9fd3072a9 | ||
|
|
1aedfb0e2e | ||
|
|
e6ae9d0288 | ||
|
|
62ce443f8b | ||
|
|
2b8bf9cc44 | ||
|
|
42d859534a | ||
|
|
f959951185 | ||
|
|
e1c8704444 | ||
|
|
8a4cfa5e3c | ||
|
|
51d105afad | ||
|
|
15192f19e0 | ||
|
|
5712e57869 | ||
|
|
9571d06df5 | ||
|
|
3b25e114e9 | ||
|
|
f7ae3fd591 | ||
|
|
ce02d2da3c | ||
|
|
bd346a0fc1 | ||
|
|
ed8243e17e | ||
|
|
02c99a7b64 | ||
|
|
96514793e4 | ||
|
|
ec8cc3a0ba | ||
|
|
e3c8ab0ca2 | ||
|
|
fefb32b04d | ||
|
|
b5a7c032ab | ||
|
|
6af24f5f44 | ||
|
|
b4a197f87f | ||
|
|
15b6fee933 | ||
|
|
9e03576c94 | ||
|
|
bfa48c5a2c | ||
|
|
890fd9c7fb | ||
|
|
42a9651950 | ||
|
|
ff01910f6a | ||
|
|
9cdb04edcb | ||
|
|
f546d4633b | ||
|
|
db07028aa0 | ||
|
|
d44ab40729 | ||
|
|
0d409f54f5 | ||
|
|
708df1c07a | ||
|
|
10186a4202 | ||
|
|
3cd2dceace | ||
|
|
376c3deab3 | ||
|
|
45d3588bb6 | ||
|
|
15e8d77a49 | ||
|
|
4d84f262ae | ||
|
|
db64ce632c | ||
|
|
956d7501ec | ||
|
|
a758a9df7a | ||
|
|
5eea25882c | ||
|
|
e954cd9fae | ||
|
|
03d79be02c | ||
|
|
7f17d2ce57 | ||
|
|
30fba11f36 | ||
|
|
d474a6a689 | ||
|
|
83d398d9d5 | ||
|
|
3929742d9c | ||
|
|
70abd78e55 | ||
|
|
dbea74b626 | ||
|
|
c6102aeb14 | ||
|
|
10c9c1ae5d | ||
|
|
9e600c6e13 | ||
|
|
9b8a8afa97 | ||
|
|
39ac97a7e0 | ||
|
|
a5cb5a709f | ||
|
|
02064c76ed | ||
|
|
d713732807 | ||
|
|
b92391d177 | ||
|
|
4964ae261f | ||
|
|
ad847299c4 | ||
|
|
7ce099c3d0 | ||
|
|
9c7db48362 | ||
|
|
88f15cba35 | ||
|
|
d852042ccb | ||
|
|
5bd2e8a423 | ||
|
|
c27256c00a | ||
|
|
010206298e | ||
|
|
678ec31229 | ||
|
|
23d52703fd | ||
|
|
45b70c952f | ||
|
|
9f2c353e86 | ||
|
|
cfe142b285 | ||
|
|
19a25fd895 | ||
|
|
8dcb97c05a | ||
|
|
b49af327cf | ||
|
|
38ab3a49c1 | ||
|
|
9f901e34ae | ||
|
|
f050a6b0de | ||
|
|
e33f3e1c85 | ||
|
|
02ff95acde | ||
|
|
2436239964 | ||
|
|
18a0030354 | ||
|
|
ee726eece2 | ||
|
|
1cd9ba69d1 | ||
|
|
30a1cd399a | ||
|
|
0524e7f3e9 | ||
|
|
14cd912df7 | ||
|
|
f087311f7c | ||
|
|
61d1fc2678 | ||
|
|
6b2566c01f | ||
|
|
31ea3bce41 | ||
|
|
2d60ea6c8d | ||
|
|
56994a522d | ||
|
|
aa90a9d052 | ||
|
|
32e0aa8ca3 | ||
|
|
e052b005ec | ||
|
|
2f18ac90f9 | ||
|
|
612f93c129 | ||
|
|
c0826c9a31 | ||
|
|
7e2de8f588 | ||
|
|
5aaa7a6634 | ||
|
|
f7feecc751 | ||
|
|
cfe1624730 | ||
|
|
95d7eb977f | ||
|
|
794380fc03 | ||
|
|
5a05620257 | ||
|
|
bebe884615 | ||
|
|
10c03880c7 | ||
|
|
3fcdfc8f83 | ||
|
|
914553ddf5 | ||
|
|
b4fe0b954c | ||
|
|
7557ee6794 | ||
|
|
382c5e7da9 | ||
|
|
6286dadc27 | ||
|
|
81f8e34846 | ||
|
|
79717926a5 | ||
|
|
5084428c9d | ||
|
|
82295aa4aa | ||
|
|
70488fc14a | ||
|
|
ccd8ba66b2 | ||
|
|
093763909f | ||
|
|
bd2b65d068 | ||
|
|
f6e37be73c | ||
|
|
71bd15a4d6 |
41
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.rst'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.rst'
|
||||
jobs:
|
||||
tests:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- {name: Linux, python: '3.12', os: ubuntu-latest, tox: py312}
|
||||
- {name: Windows, python: '3.12', os: windows-latest, tox: py312}
|
||||
- {name: Mac, python: '3.12', os: macos-latest, tox: py312}
|
||||
- {name: '3.11', python: '3.11', os: ubuntu-latest, tox: py311}
|
||||
- {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310}
|
||||
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
|
||||
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
|
||||
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
|
||||
- {name: '3.6', python: '3.6', os: ubuntu-20.04, tox: py36} # ubuntu-latest doesn't support 3.6
|
||||
- {name: Style, python: '3.10', os: ubuntu-latest, tox: stylecheck}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- name: update pip
|
||||
run: |
|
||||
pip install -U setuptools wheel
|
||||
python -m pip install -U pip
|
||||
- run: pip install tox
|
||||
- run: tox -e ${{ matrix.tox }}
|
||||
162
.gitignore
vendored
@@ -1,6 +1,160 @@
|
||||
*.egg-info
|
||||
*.pyc
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
docs/_build
|
||||
.tox
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "docs/_themes"]
|
||||
path = docs/_themes
|
||||
url = git://github.com/mitsuhiko/flask-sphinx-themes.git
|
||||
url = https://github.com/mitsuhiko/flask-sphinx-themes.git
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
language: python
|
||||
python: "2.7"
|
||||
env:
|
||||
- TOXENV=py26
|
||||
- TOXENV=py27
|
||||
- TOXENV=py33
|
||||
install:
|
||||
- pip install tox --use-mirrors
|
||||
script: tox
|
||||
111
CHANGES.rst
@@ -1,111 +0,0 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
0.9.0 (2014-01-03)
|
||||
--------
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Python 3 compatibility (#54, thanks justinmayer and jmagnusson)
|
||||
- Support .init_app() (#38)
|
||||
- New "Config" panel displaying Flask config values (#51, thanks Alexey Diyan)
|
||||
- Better PEP8-style formatting (#63, thanks Ivan Ivaschenko)
|
||||
|
||||
Fixes:
|
||||
|
||||
- Fix template editor with non-ASCII templates (#46)
|
||||
|
||||
|
||||
0.8 (2013-02-21)
|
||||
----------------
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Use `itsdangerous <http://pythonhosted.org/itsdangerous/>`_ to sign SQL queries
|
||||
- Expose the jQuery object as ``fldt.$`` so extensions can use the toolbar's
|
||||
copy of jQuery (#42)
|
||||
|
||||
Fixes:
|
||||
|
||||
- Don't intercept redirects on XHR requests (#41)
|
||||
- Fix SQL query time display as milliseconds (#36)
|
||||
- Fix ``functools.partial`` error (#35)
|
||||
- Fix werkzeug request logging with logging panel (#33)
|
||||
- Fix SQL panel unicode encoding error (#31)
|
||||
|
||||
|
||||
0.7.1 (2012-05-18)
|
||||
------------------
|
||||
|
||||
Fixes:
|
||||
|
||||
- loading template editor in-place over current page
|
||||
|
||||
|
||||
0.7 (2012-05-18)
|
||||
----------------
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Add an in-browser template editor to the template panel
|
||||
- ``DEBUG_TB_PROFILER_ENABLED`` config option to enable the profiler on all
|
||||
requests (normally it is user-enabled by clicking the checkmark)
|
||||
|
||||
|
||||
0.6.3.1 (2012-04-16)
|
||||
--------------------
|
||||
|
||||
New release to add missing changelog for 0.6.3
|
||||
|
||||
|
||||
0.6.3 (2012-04-16)
|
||||
------------------
|
||||
Fixes:
|
||||
|
||||
- Compatibility with Flask-SQLAlchemy 0.16 package name
|
||||
|
||||
|
||||
0.6.2 (2012-02-18)
|
||||
------------------
|
||||
|
||||
Fixes:
|
||||
|
||||
- Installation issue on Windows with trailing slashes in MANIFEST.in
|
||||
|
||||
- JavaScript error when using conditional comments for ``<html>`` tag
|
||||
(like in HTML5 Boilerplate)
|
||||
|
||||
|
||||
0.6.1 (2012-02-15)
|
||||
------------------
|
||||
|
||||
Fixes:
|
||||
|
||||
- Memory leak when toolbar was enabled
|
||||
|
||||
- UnicodeDecodeError when request data contained binary data (e.g. session values)
|
||||
|
||||
|
||||
Enhancements:
|
||||
|
||||
- ``DEBUG_TB_ENABLED`` config setting to explicitly enable or disable the toolbar
|
||||
|
||||
- ``DEBUG_TB_HOSTS`` config setting to enable toolbar only for specific remote hosts
|
||||
|
||||
- New logo for Flask instead of Django
|
||||
|
||||
- Monospaced font on table data
|
||||
|
||||
Thanks to kennethreitz and joeshaw for their contributions.
|
||||
|
||||
|
||||
0.6 (2012-01-04)
|
||||
----------------
|
||||
|
||||
Flask 0.8 or higher is required
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Flask 0.8 compatibility
|
||||
|
||||
Thanks to mvantellingen
|
||||
@@ -1,2 +1,3 @@
|
||||
recursive-include flask_debugtoolbar/templates *.html
|
||||
recursive-include flask_debugtoolbar/static *
|
||||
include LICENSE
|
||||
recursive-include src/flask_debugtoolbar/templates *.html
|
||||
recursive-include src/flask_debugtoolbar/static *
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Flask Debug-toolbar
|
||||
===================
|
||||
|
||||
This is a port of the excellent `django-debug-toolbar <https://github.com/django-debug-toolbar/django-debug-toolbar>`_
|
||||
This is a port of the excellent `django-debug-toolbar <https://github.com/jazzband/django-debug-toolbar>`_
|
||||
for Flask applications.
|
||||
|
||||
.. image:: https://travis-ci.org/mgood/flask-debugtoolbar.png?branch=master
|
||||
:target: https://travis-ci.org/mgood/flask-debugtoolbar
|
||||
.. image:: https://github.com/pallets-eco/flask-debugtoolbar/actions/workflows/tests.yml/badge.svg
|
||||
:target: https://github.com/pallets-eco/flask-debugtoolbar/actions
|
||||
|
||||
|
||||
Installation
|
||||
@@ -40,4 +40,4 @@ In production, setting ``app.debug = False`` will disable the toolbar.
|
||||
|
||||
See the `documentation`_ for more information.
|
||||
|
||||
.. _documentation: http://flask-debugtoolbar.readthedocs.org
|
||||
.. _documentation: https://flask-debugtoolbar.readthedocs.io/
|
||||
|
||||
BIN
docs/_static/example.gif
vendored
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
docs/_static/screenshot-config-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/_static/screenshot-headers-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/_static/screenshot-logger-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/_static/screenshot-profiler-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/_static/screenshot-request-vars-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/_static/screenshot-sqlalchemy-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/_static/screenshot-template-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/_static/screenshot-time-panel.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
31
docs/conf.py
@@ -11,7 +11,16 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import datetime
|
||||
import os
|
||||
import pkg_resources
|
||||
import sys
|
||||
import time
|
||||
|
||||
import flask_debugtoolbar
|
||||
|
||||
|
||||
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -25,7 +34,10 @@ import sys, os
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.viewcode']
|
||||
extensions = [
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -41,16 +53,16 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Flask-DebugToolbar'
|
||||
copyright = u'2012, Matt Good'
|
||||
copyright = u'2012-{0}'.format(BUILD_DATE.year)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.9'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.9.0'
|
||||
release = flask_debugtoolbar.__version__
|
||||
# The short X.Y version.
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -86,18 +98,21 @@ pygments_style = 'sphinx'
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
intersphinx_mapping = {
|
||||
'flasksqlalchemy': ('https://flask-sqlalchemy.palletsprojects.com/', None)
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'flask_small'
|
||||
html_theme = 'flask'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
'github_fork': 'mgood/flask-debugtoolbar',
|
||||
'index_logo': None,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
.. Flask-DebugToolbar documentation master file, created by
|
||||
sphinx-quickstart on Wed Feb 15 18:08:39 2012.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Flask-DebugToolbar
|
||||
==================
|
||||
|
||||
This is a port of the excellent `django-debug-toolbar <https://github.com/django-debug-toolbar/django-debug-toolbar>`_
|
||||
for Flask applications.
|
||||
This extension adds a toolbar overlay to Flask applications containing useful information for debugging.
|
||||
|
||||
.. image:: _static/example.gif
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Installing is simple with pip::
|
||||
Installing is simple with `pip`_::
|
||||
|
||||
$ pip install flask-debugtoolbar
|
||||
|
||||
.. _pip: https://pip.pypa.io/
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -36,9 +34,16 @@ Setting up the debug toolbar is simple::
|
||||
toolbar = DebugToolbarExtension(app)
|
||||
|
||||
|
||||
The toolbar will automatically be injected into Jinja templates when debug mode is on.
|
||||
In production, setting ``app.debug = False`` will disable the toolbar.
|
||||
The toolbar will automatically be injected into HTML responses when debug mode
|
||||
is on. In production, setting ``app.debug = False`` will disable the toolbar.
|
||||
|
||||
This extension also supports the Flask app factory pattern by separately
|
||||
creating the toolbar and later initializing it for an app::
|
||||
|
||||
toolbar = DebugToolbarExtension()
|
||||
# Then later on.
|
||||
app = create_app('the-config.cfg')
|
||||
toolbar.init_app(app)
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
@@ -61,11 +66,26 @@ To change one of the config options, set it in the Flask app's config like::
|
||||
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
|
||||
|
||||
|
||||
Panels
|
||||
------
|
||||
|
||||
.. toctree::
|
||||
|
||||
panels
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Fork us `on GitHub <https://github.com/mgood/flask-debugtoolbar>`_
|
||||
Fork us `on GitHub <https://github.com/pallets-eco/flask-debugtoolbar>`_
|
||||
|
||||
Thanks
|
||||
------
|
||||
|
||||
This was based on the original `django-debug-toolbar`_. Thanks to `Michael van Tellingen`_ for the original development of this Flask extension, and to all the `individual contributors`_.
|
||||
|
||||
.. _django-debug-toolbar: https://github.com/jazzband/django-debug-toolbar
|
||||
.. _Michael van Tellingen: https://github.com/mvantellingen
|
||||
.. _individual contributors: https://github.com/pallets-eco/flask-debugtoolbar/graphs/contributors
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
110
docs/panels.rst
Normal file
@@ -0,0 +1,110 @@
|
||||
Built-In Panels
|
||||
===============
|
||||
|
||||
Versions
|
||||
--------
|
||||
flask_debugtoolbar.panels.versions.VersionDebugPanel
|
||||
|
||||
Shows the installed Flask version. The expanded view displays all installed packages and their versions as detected by ``setuptools``.
|
||||
|
||||
|
||||
Time
|
||||
----
|
||||
|
||||
flask_debugtoolbar.panels.timer.TimerDebugPanel
|
||||
|
||||
Shows the time taken to process the current request. The expanded view includes the breakdown of CPU time, by user and system, wall clock time, and context switches.
|
||||
|
||||
.. image:: _static/screenshot-time-panel.png
|
||||
|
||||
|
||||
HTTP Headers
|
||||
------------
|
||||
|
||||
flask_debugtoolbar.panels.headers.HeaderDebugPanel
|
||||
|
||||
Displays the HTTP headers for the current request.
|
||||
|
||||
.. image:: _static/screenshot-headers-panel.png
|
||||
|
||||
|
||||
Request Vars
|
||||
------------
|
||||
|
||||
flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel
|
||||
|
||||
Displays details of the Flask request-related variables, including the view function parameters, cookies, session variables, and GET and POST variables.
|
||||
|
||||
.. image:: _static/screenshot-request-vars-panel.png
|
||||
|
||||
|
||||
Config
|
||||
------
|
||||
|
||||
flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel
|
||||
|
||||
Shows the contents of the Flask application's config dict ``app.config``.
|
||||
|
||||
.. image:: _static/screenshot-config-panel.png
|
||||
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
flask_debugtoolbar.panels.template.TemplateDebugPanel
|
||||
|
||||
Shows information about the templates rendered for this request, and the value of the template parameters provided.
|
||||
|
||||
.. image:: _static/screenshot-template-panel.png
|
||||
|
||||
|
||||
SQLAlchemy
|
||||
----------
|
||||
|
||||
flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel
|
||||
|
||||
Shows SQL queries run during the current request.
|
||||
|
||||
.. note:: This panel requires using the `Flask-SQLAlchemy`_ extension in order
|
||||
to record the queries. See the Flask-SQLAlchemy
|
||||
:ref:`flasksqlalchemy:quickstart` section to configure it.
|
||||
|
||||
For additional details on query recording see the
|
||||
:py:func:`~flask_sqlalchemy.get_debug_queries` documentation.
|
||||
|
||||
.. note:: SQL syntax highlighting requires `Pygments`_ to be installed.
|
||||
|
||||
.. image:: _static/screenshot-sqlalchemy-panel.png
|
||||
|
||||
.. _Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/
|
||||
|
||||
.. _Pygments: https://pygments.org/
|
||||
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
flask_debugtoolbar.panels.logger.LoggingPanel
|
||||
|
||||
Displays log messages recorded during the current request.
|
||||
|
||||
.. image:: _static/screenshot-logger-panel.png
|
||||
|
||||
|
||||
Route List
|
||||
----------
|
||||
|
||||
flask_debugtoolbar.panels.route_list.RouteListDebugPanel
|
||||
|
||||
|
||||
Displays the Flask URL routing rules.
|
||||
|
||||
|
||||
Profiler
|
||||
--------
|
||||
|
||||
flask_debugtoolbar.panels.profiler.ProfilerDebugPanel
|
||||
|
||||
Reports profiling data for the current request. Due to the performance overhead, profiling is disabled by default. Click the checkmark to toggle profiling on or off. After enabling the profiler, refresh the page to re-run it with profiling.
|
||||
|
||||
.. image:: _static/screenshot-profiler-panel.png
|
||||
@@ -1,9 +1,7 @@
|
||||
import sys
|
||||
sys.path.insert(0, '.')
|
||||
# Run using: `FLASK_DEBUG=True flask run`
|
||||
|
||||
from flask import Flask, render_template, redirect, url_for
|
||||
from flask.ext.script import Manager
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_debugtoolbar import DebugToolbarExtension
|
||||
|
||||
|
||||
@@ -16,10 +14,12 @@ app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = True
|
||||
#)
|
||||
#app.config['DEBUG_TB_HOSTS'] = ('127.0.0.1', '::1' )
|
||||
app.config['SECRET_KEY'] = 'asd'
|
||||
app.config['DEBUG'] = True
|
||||
app.config['SQLALCHEMY_RECORD_QUERIES'] = True
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
|
||||
db = SQLAlchemy(app)
|
||||
# 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)
|
||||
toolbar = DebugToolbarExtension(app)
|
||||
|
||||
|
||||
@@ -37,13 +37,10 @@ def index():
|
||||
|
||||
@app.route('/redirect')
|
||||
def redirect_example():
|
||||
|
||||
response = redirect(url_for('index'))
|
||||
response.set_cookie('test_cookie', '1')
|
||||
return response
|
||||
|
||||
if __name__ == "__main__":
|
||||
db.create_all()
|
||||
|
||||
manager = Manager(app)
|
||||
manager.run()
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
@@ -1,27 +0,0 @@
|
||||
from flask import __version__ as flask_version
|
||||
from flask_debugtoolbar.panels import DebugPanel
|
||||
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
class VersionDebugPanel(DebugPanel):
|
||||
"""
|
||||
Panel that displays the Flask version.
|
||||
"""
|
||||
name = 'Version'
|
||||
has_content = False
|
||||
|
||||
def nav_title(self):
|
||||
return _('Versions')
|
||||
|
||||
def nav_subtitle(self):
|
||||
return 'Flask %s' % flask_version
|
||||
|
||||
def url(self):
|
||||
return ''
|
||||
|
||||
def title(self):
|
||||
return _('Versions')
|
||||
|
||||
def content(self):
|
||||
return None
|
||||
@@ -1,63 +0,0 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> (ms)</th>
|
||||
<th>Action</th>
|
||||
<th>Context</th>
|
||||
<th>Query</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for query in queries %}
|
||||
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
|
||||
<td>{{ '%.4f'|format(query.duration * 1000) }}</td>
|
||||
<td>
|
||||
{% if query.signed_query %}
|
||||
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_select?query={{ query.signed_query }}&duration={{ query.duration|urlencode }}">SELECT</a><br />
|
||||
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?query={{ query.signed_query }}&duration={{ query.duration|urlencode }}">EXPLAIN</a><br />
|
||||
{% endif %}
|
||||
</td>
|
||||
<td title="{{ query.context_long }}">
|
||||
{{ query.context }}
|
||||
</td>
|
||||
<td class="syntax">
|
||||
<div class="flDebugSqlWrap">
|
||||
<div class="flDebugSql">{{ query.sql|safe }}</div>
|
||||
{#
|
||||
{% if query.stacktrace %}
|
||||
<div class="djSQLHideStacktraceDiv" style="display:none;">
|
||||
<table>
|
||||
<tr>
|
||||
<th>{% trans "Line" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "File" %}</th>
|
||||
</tr>
|
||||
{% for file, line, method in query.stacktrace %}
|
||||
<tr>
|
||||
<td>{{ line }}</td>
|
||||
<td><code>{{ method|escape }}</code></td>
|
||||
<td><code>{{ file|escape }}</code></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if query.template_info %}
|
||||
<table>
|
||||
{% for line in query.template_info.context %}
|
||||
<tr>
|
||||
<td>{{ line.num }}</td>
|
||||
<td><code style="font-family: monospace;{% if line.highlight %}background-color: lightgrey{% endif %}">{{ line.content }}</code></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p><strong>{{ query.template_info.name|default:"(unknown)" }}</strong></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<span class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}" style="width:{{ query.width_ratio }}%; left:{{ query.start_offset }}%;"></span>
|
||||
#}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1,33 +0,0 @@
|
||||
<div class="flDebugPanelTitle">
|
||||
<a class="flDebugClose flDebugBack" href="">Back</a>
|
||||
<h3>SQL Explained</h3>
|
||||
</div>
|
||||
<div class="flDebugPanelContent">
|
||||
<div class="scroll">
|
||||
<dl>
|
||||
<dt>Executed SQL</dt>
|
||||
<dd>{{ sql|safe }}</dd>
|
||||
<dt>Time</dt>
|
||||
<dd>{{ '%.4f'|format(duration) }} ms</dd>
|
||||
</dl>
|
||||
<table class="flSqlExplain">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for h in headers %}
|
||||
<th>{{ h|upper }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in result %}
|
||||
<tr class="{{ loop.cycle('fjDebugOdd', 'fjDebugEven') }}">
|
||||
{% for column in row %}
|
||||
<td>{{ column }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
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')
|
||||
HAVE_PYGMENTS = True
|
||||
except ImportError:
|
||||
HAVE_PYGMENTS = False
|
||||
|
||||
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def format_fname(value):
|
||||
# If the value is not an absolute path, the it is a builtin or
|
||||
# a relative file (thus a project file).
|
||||
if not os.path.isabs(value):
|
||||
if value.startswith(('{', '<')):
|
||||
return value
|
||||
if value.startswith('.' + os.path.sep):
|
||||
return value
|
||||
return '.' + os.path.sep + value
|
||||
|
||||
# If the file is absolute and within the project root handle it as
|
||||
# a project file
|
||||
if value.startswith(current_app.root_path):
|
||||
return "." + value[len(current_app.root_path):]
|
||||
|
||||
# Loop through sys.path to find the longest match and return
|
||||
# the relative path from there.
|
||||
paths = sys.path
|
||||
prefix = None
|
||||
prefix_len = 0
|
||||
for path in sys.path:
|
||||
new_prefix = os.path.commonprefix([path, value])
|
||||
if len(new_prefix) > prefix_len:
|
||||
prefix = new_prefix
|
||||
prefix_len = len(prefix)
|
||||
|
||||
if not prefix.endswith(os.path.sep):
|
||||
prefix_len -= 1
|
||||
path = value[prefix_len:]
|
||||
return '<%s>' % path
|
||||
|
||||
|
||||
def format_sql(query, args):
|
||||
if not HAVE_PYGMENTS:
|
||||
return query
|
||||
|
||||
return highlight(
|
||||
query,
|
||||
SqlLexer(encoding='utf-8'),
|
||||
HtmlFormatter(encoding='utf-8', noclasses=True, style=PYGMENT_STYLE))
|
||||
6
pyproject.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
36
setup.cfg
Normal file
@@ -0,0 +1,36 @@
|
||||
[metadata]
|
||||
name = Flask-DebugToolbar
|
||||
version = 0.14.0
|
||||
author = Michael van Tellingen
|
||||
author_email = michaelvantellingen@gmail.com
|
||||
maintainer = Matt Good
|
||||
maintainer_email = matt@matt-good.net
|
||||
description = A toolbar overlay for debugging Flask applications.
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
keywords = flask, debug, toolbar
|
||||
url = https://github.com/pallets-eco/flask-debugtoolbar
|
||||
project_urls =
|
||||
Changelog = https://github.com/pallets-eco/flask-debugtoolbar/releases
|
||||
Documentation = https://flask-debugtoolbar.readthedocs.io/
|
||||
classifiers =
|
||||
Development Status :: 4 - Beta
|
||||
Environment :: Web Environment
|
||||
Framework :: Flask
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: BSD License
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python
|
||||
Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Topic :: Software Development :: Libraries :: Python Modules
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= src
|
||||
|
||||
packages = find:
|
||||
include_package_data = True
|
||||
python_requires = >=2.7
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
43
setup.py
@@ -1,48 +1,13 @@
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
try:
|
||||
README = open(os.path.join(here, 'README.rst')).read()
|
||||
CHANGES = open(os.path.join(here, 'CHANGES.rst')).read()
|
||||
except:
|
||||
README = ''
|
||||
CHANGES = ''
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
|
||||
setup(
|
||||
name='Flask-DebugToolbar',
|
||||
version='0.9.0',
|
||||
url='http://flask-debugtoolbar.rtfd.org/',
|
||||
license='BSD',
|
||||
author='Michael van Tellingen',
|
||||
author_email='michaelvantellingen@gmail.com',
|
||||
maintainer='Matt Good',
|
||||
maintainer_email='matt@matt-good.net',
|
||||
description='A port of the Django debug toolbar to Flask',
|
||||
long_description=README + '\n\n' + CHANGES,
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
include_package_data=True,
|
||||
packages=['flask_debugtoolbar',
|
||||
'flask_debugtoolbar.panels'
|
||||
],
|
||||
name="Flask-DebugToolbar",
|
||||
install_requires=[
|
||||
'Flask>=0.8',
|
||||
'Blinker',
|
||||
'itsdangerous',
|
||||
'werkzeug',
|
||||
'MarkupSafe',
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
import os
|
||||
import urllib.parse
|
||||
import warnings
|
||||
|
||||
from flask import current_app, request, g
|
||||
from flask.globals import _request_ctx_stack
|
||||
from flask import send_from_directory
|
||||
import flask
|
||||
from packaging import version as version_builder
|
||||
from flask import Blueprint, current_app, request, g, send_from_directory, url_for
|
||||
|
||||
|
||||
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
|
||||
from flask.globals import request_ctx
|
||||
else:
|
||||
from flask.globals import _request_ctx_stack
|
||||
|
||||
|
||||
from jinja2 import __version__ as __jinja_version__
|
||||
from jinja2 import Environment, PackageLoader
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.urls import url_quote_plus
|
||||
|
||||
from flask_debugtoolbar.toolbar import DebugToolbar
|
||||
from flask_debugtoolbar.compat import iteritems
|
||||
from flask import Blueprint
|
||||
from flask_debugtoolbar.toolbar import DebugToolbar
|
||||
from flask_debugtoolbar.utils import decode_text, gzip_compress, gzip_decompress
|
||||
|
||||
try:
|
||||
# Python 3.8+
|
||||
from importlib.metadata import version
|
||||
|
||||
__version__ = version("Flask-DebugToolbar")
|
||||
except ImportError:
|
||||
import pkg_resources
|
||||
|
||||
__version__ = pkg_resources.get_distribution("Flask-DebugToolbar").version
|
||||
|
||||
|
||||
module = Blueprint('debugtoolbar', __name__)
|
||||
@@ -30,7 +49,7 @@ def replace_insensitive(string, target, replacement):
|
||||
|
||||
def _printable(value):
|
||||
try:
|
||||
return repr(value)
|
||||
return decode_text(repr(value))
|
||||
except Exception as e:
|
||||
return '<repr(%s) raised %s: %s>' % (
|
||||
object.__repr__(value), type(e).__name__, e)
|
||||
@@ -40,20 +59,26 @@ class DebugToolbarExtension(object):
|
||||
_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]
|
||||
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
self.debug_toolbars = {}
|
||||
jinja_extensions = ['jinja2.ext.i18n']
|
||||
|
||||
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=['jinja2.ext.i18n', 'jinja2.ext.with_'],
|
||||
extensions=jinja_extensions,
|
||||
loader=PackageLoader(__name__, 'templates'))
|
||||
self.jinja_env.filters['urlencode'] = url_quote_plus
|
||||
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)
|
||||
@@ -98,13 +123,20 @@ class DebugToolbarExtension(object):
|
||||
'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,
|
||||
}
|
||||
|
||||
def dispatch_request(self):
|
||||
"""Modified version of Flask.dispatch_request to call process_view."""
|
||||
req = _request_ctx_stack.top.request
|
||||
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
|
||||
req = request_ctx.request
|
||||
else:
|
||||
req = _request_ctx_stack.top.request
|
||||
|
||||
app = current_app
|
||||
|
||||
if req.routing_exception is not None:
|
||||
@@ -147,7 +179,9 @@ class DebugToolbarExtension(object):
|
||||
|
||||
real_request = request._get_current_object()
|
||||
|
||||
self.debug_toolbars[real_request] = DebugToolbar(real_request, self.jinja_env)
|
||||
self.debug_toolbars[real_request] = (
|
||||
DebugToolbar(real_request, self.jinja_env))
|
||||
|
||||
for panel in self.debug_toolbars[real_request].panels:
|
||||
panel.process_request(real_request)
|
||||
|
||||
@@ -156,11 +190,16 @@ class DebugToolbarExtension(object):
|
||||
This is done by the dispatch_request method.
|
||||
"""
|
||||
real_request = request._get_current_object()
|
||||
if real_request in self.debug_toolbars:
|
||||
for panel in self.debug_toolbars[real_request].panels:
|
||||
new_view = panel.process_view(real_request, view_func, view_kwargs)
|
||||
if new_view:
|
||||
view_func = new_view
|
||||
try:
|
||||
toolbar = self.debug_toolbars[real_request]
|
||||
except KeyError:
|
||||
return view_func
|
||||
|
||||
for panel in toolbar.panels:
|
||||
new_view = panel.process_view(real_request, view_func, view_kwargs)
|
||||
if new_view:
|
||||
view_func = new_view
|
||||
|
||||
return view_func
|
||||
|
||||
def process_response(self, response):
|
||||
@@ -171,8 +210,7 @@ class DebugToolbarExtension(object):
|
||||
# Intercept http redirect codes and display an html page with a
|
||||
# link to the target.
|
||||
if current_app.config['DEBUG_TB_INTERCEPT_REDIRECTS']:
|
||||
if (response.status_code in self._redirect_codes and
|
||||
not real_request.is_xhr):
|
||||
if response.status_code in self._redirect_codes:
|
||||
redirect_to = response.location
|
||||
redirect_code = response.status_code
|
||||
if redirect_to:
|
||||
@@ -185,22 +223,46 @@ class DebugToolbarExtension(object):
|
||||
response.response = [content]
|
||||
response.status_code = 200
|
||||
|
||||
# If the http response code is 200 then we process to add the
|
||||
# If the http response code is an allowed code then we process to add the
|
||||
# toolbar to the returned html response.
|
||||
if (response.status_code == 200 and
|
||||
if not (response.status_code in self._toolbar_codes and
|
||||
response.is_sequence and
|
||||
response.headers['content-type'].startswith('text/html')):
|
||||
for panel in self.debug_toolbars[real_request].panels:
|
||||
panel.process_response(real_request, response)
|
||||
return response
|
||||
|
||||
if response.is_sequence:
|
||||
response_html = response.data.decode(response.charset)
|
||||
toolbar_html = self.debug_toolbars[real_request].render_toolbar()
|
||||
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)
|
||||
|
||||
content = replace_insensitive(
|
||||
response_html, '</body>', toolbar_html + '</body>')
|
||||
content = content.encode(response.charset)
|
||||
response.response = [content]
|
||||
response.content_length = len(content)
|
||||
no_case = response_html.lower()
|
||||
body_end = no_case.rfind('</body>')
|
||||
|
||||
if body_end >= 0:
|
||||
before = response_html[:body_end]
|
||||
after = response_html[body_end:]
|
||||
elif no_case.startswith('<!doctype html>'):
|
||||
before = response_html
|
||||
after = ''
|
||||
else:
|
||||
warnings.warn('Could not insert debug toolbar.'
|
||||
' </body> tag not found in response.')
|
||||
return response
|
||||
|
||||
toolbar = self.debug_toolbars[real_request]
|
||||
|
||||
for panel in toolbar.panels:
|
||||
panel.process_response(real_request, 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 = gzip_compress(content)
|
||||
response.response = [content]
|
||||
response.content_length = len(content)
|
||||
|
||||
return response
|
||||
|
||||
@@ -6,7 +6,9 @@ class DebugPanel(object):
|
||||
Base class for debug panels.
|
||||
"""
|
||||
# name = Base
|
||||
has_content = False # If content returns something, set to true in subclass
|
||||
|
||||
# If content returns something, set to true in subclass
|
||||
has_content = False
|
||||
|
||||
# If the client is able to activate/de-activate the panel
|
||||
user_enable = False
|
||||
28
src/flask_debugtoolbar/panels/g.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from flask import g
|
||||
from flask_debugtoolbar.panels import DebugPanel
|
||||
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
class GDebugPanel(DebugPanel):
|
||||
"""
|
||||
A panel to display flask.g content.
|
||||
"""
|
||||
name = 'g'
|
||||
has_content = True
|
||||
|
||||
def nav_title(self):
|
||||
return _('flask.g')
|
||||
|
||||
def title(self):
|
||||
return _('flask.g content')
|
||||
|
||||
def url(self):
|
||||
return ''
|
||||
|
||||
def content(self):
|
||||
context = self.context.copy()
|
||||
context.update({
|
||||
'g_content': g.__dict__
|
||||
})
|
||||
return self.render('panels/g.html', context)
|
||||
@@ -26,18 +26,18 @@ class ThreadTrackingHandler(logging.Handler):
|
||||
|
||||
def get_records(self, thread=None):
|
||||
"""
|
||||
Returns a list of records for the provided thread, of if none is provided,
|
||||
returns a list for the current thread.
|
||||
Returns a list of records for the provided thread, of if none is
|
||||
provided, returns a list for the current thread.
|
||||
"""
|
||||
if thread is None:
|
||||
thread = threading.currentThread()
|
||||
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.currentThread()
|
||||
thread = threading.current_thread()
|
||||
if thread in self.records:
|
||||
del self.records[thread]
|
||||
|
||||
@@ -57,8 +57,8 @@ def _init_once():
|
||||
# Call werkzeug's internal logging to make sure it gets configured
|
||||
# before we add our handler. Otherwise werkzeug will see our handler
|
||||
# and not configure console logging for the request log.
|
||||
# Werkzeug's default log level is INFO so this message probably won't be
|
||||
# seen.
|
||||
# Werkzeug's default log level is INFO so this message probably won't
|
||||
# be seen.
|
||||
try:
|
||||
from werkzeug._internal import _log
|
||||
except ImportError:
|
||||
@@ -88,8 +88,8 @@ class LoggingPanel(DebugPanel):
|
||||
|
||||
def nav_subtitle(self):
|
||||
# FIXME l10n: use ngettext
|
||||
return "%s message%s" % \
|
||||
(len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's')
|
||||
num_records = len(handler.get_records())
|
||||
return '%s message%s' % (num_records, '' if num_records == 1 else 's')
|
||||
|
||||
def title(self):
|
||||
return _('Log Messages')
|
||||
@@ -1,10 +1,8 @@
|
||||
import sys
|
||||
try:
|
||||
import cProfile as profile
|
||||
except ImportError:
|
||||
import profile
|
||||
import functools
|
||||
import os.path
|
||||
import pstats
|
||||
|
||||
from flask import current_app
|
||||
@@ -75,7 +73,7 @@ class ProfilerDebugPanel(DebugPanel):
|
||||
# Cumulative time
|
||||
current['cumtime'] = info[3] * 1000
|
||||
|
||||
# Quotient of the cumulative time divded by the number of
|
||||
# Quotient of the cumulative time divided by the number of
|
||||
# primitive calls.
|
||||
if info[0]:
|
||||
current['percall_cum'] = info[3] * 1000 / info[0]
|
||||
@@ -96,7 +94,7 @@ class ProfilerDebugPanel(DebugPanel):
|
||||
def title(self):
|
||||
if not self.is_active:
|
||||
return "Profiler not active"
|
||||
return 'View: %.2fms' % (float(self.stats.total_tt)*1000,)
|
||||
return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,)
|
||||
|
||||
def nav_title(self):
|
||||
return 'Profiler'
|
||||
@@ -104,7 +102,7 @@ class ProfilerDebugPanel(DebugPanel):
|
||||
def nav_subtitle(self):
|
||||
if not self.is_active:
|
||||
return "in-active"
|
||||
return 'View: %.2fms' % (float(self.stats.total_tt)*1000,)
|
||||
return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,)
|
||||
|
||||
def url(self):
|
||||
return ''
|
||||
@@ -35,10 +35,11 @@ class RequestVarsDebugPanel(DebugPanel):
|
||||
def content(self):
|
||||
context = self.context.copy()
|
||||
context.update({
|
||||
'get': [(k, self.request.args.getlist(k)) for k in self.request.args],
|
||||
'post': [(k, self.request.form.getlist(k)) for k in self.request.form],
|
||||
'cookies': [(k, self.request.cookies.get(k)) for k in self.request.cookies],
|
||||
'view_func': ('%s.%s' % (self.view_func.__module__, self.view_func.__name__)
|
||||
'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 {},
|
||||
38
src/flask_debugtoolbar/panels/route_list.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from flask_debugtoolbar.panels import DebugPanel
|
||||
from flask import current_app
|
||||
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
class RouteListDebugPanel(DebugPanel):
|
||||
"""
|
||||
Panel that displays the URL routing rules.
|
||||
"""
|
||||
name = 'RouteList'
|
||||
has_content = True
|
||||
routes = []
|
||||
|
||||
def nav_title(self):
|
||||
return _('Route List')
|
||||
|
||||
def title(self):
|
||||
return _('Route List')
|
||||
|
||||
def url(self):
|
||||
return ''
|
||||
|
||||
def nav_subtitle(self):
|
||||
count = len(self.routes)
|
||||
return '%s %s' % (count, 'route' if count == 1 else 'routes')
|
||||
|
||||
def process_request(self, request):
|
||||
self.routes = [
|
||||
rule
|
||||
for rule in current_app.url_map.iter_rules()
|
||||
if not rule.rule.startswith('/_debug_toolbar')
|
||||
]
|
||||
|
||||
def content(self):
|
||||
return self.render('panels/route_list.html', {
|
||||
'routes': self.routes,
|
||||
})
|
||||
@@ -1,18 +1,26 @@
|
||||
try:
|
||||
from flask.ext.sqlalchemy import get_debug_queries, SQLAlchemy
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
except ImportError:
|
||||
sqlalchemy_available = False
|
||||
get_debug_queries = SQLAlchemy = None
|
||||
get_recorded_queries = SQLAlchemy = None
|
||||
else:
|
||||
try:
|
||||
from flask_sqlalchemy.record_queries import get_recorded_queries
|
||||
except ImportError:
|
||||
# For flask_sqlalchemy < 3.0.0
|
||||
from flask_sqlalchemy import get_debug_queries as get_recorded_queries
|
||||
|
||||
location_property = 'context'
|
||||
else:
|
||||
location_property = 'location'
|
||||
sqlalchemy_available = True
|
||||
|
||||
from flask import request, current_app, abort, json_available, g
|
||||
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
|
||||
|
||||
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
@@ -21,8 +29,13 @@ def query_signer():
|
||||
salt='fdt-sql-query')
|
||||
|
||||
|
||||
def is_select(statement):
|
||||
prefix = b'select' if isinstance(statement, bytes) else 'select'
|
||||
return statement.lower().strip().startswith(prefix)
|
||||
|
||||
|
||||
def dump_query(statement, params):
|
||||
if not params or not statement.lower().strip().startswith('select'):
|
||||
if not params or not is_select(statement):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -38,12 +51,31 @@ def load_query(data):
|
||||
abort(406)
|
||||
|
||||
# Make sure it is a select statement
|
||||
if not statement.lower().strip().startswith('select'):
|
||||
if not is_select(statement):
|
||||
abort(406)
|
||||
|
||||
return statement, params
|
||||
|
||||
|
||||
def extension_used():
|
||||
return 'sqlalchemy' in current_app.extensions
|
||||
|
||||
|
||||
def recording_enabled():
|
||||
return (current_app.debug or current_app.config.get('SQLALCHEMY_RECORD_QUERIES'))
|
||||
|
||||
|
||||
def is_available():
|
||||
return sqlalchemy_available and extension_used() and recording_enabled()
|
||||
|
||||
|
||||
def get_queries():
|
||||
if get_recorded_queries:
|
||||
return get_recorded_queries()
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
class SQLAlchemyDebugPanel(DebugPanel):
|
||||
"""
|
||||
Panel that displays the time a response took in milliseconds.
|
||||
@@ -52,9 +84,7 @@ class SQLAlchemyDebugPanel(DebugPanel):
|
||||
|
||||
@property
|
||||
def has_content(self):
|
||||
if not json_available or not sqlalchemy_available:
|
||||
return True # will display an error message
|
||||
return bool(get_debug_queries())
|
||||
return bool(get_queries()) or not is_available()
|
||||
|
||||
def process_request(self, request):
|
||||
pass
|
||||
@@ -66,12 +96,12 @@ class SQLAlchemyDebugPanel(DebugPanel):
|
||||
return _('SQLAlchemy')
|
||||
|
||||
def nav_subtitle(self):
|
||||
if not json_available or not sqlalchemy_available:
|
||||
count = len(get_queries())
|
||||
|
||||
if not count and not is_available():
|
||||
return 'Unavailable'
|
||||
|
||||
if get_debug_queries:
|
||||
count = len(get_debug_queries())
|
||||
return "%d %s" % (count, "query" if count == 1 else "queries")
|
||||
return '%d %s' % (count, 'query' if count == 1 else 'queries')
|
||||
|
||||
def title(self):
|
||||
return _('SQLAlchemy queries')
|
||||
@@ -80,35 +110,43 @@ class SQLAlchemyDebugPanel(DebugPanel):
|
||||
return ''
|
||||
|
||||
def content(self):
|
||||
if not json_available or not sqlalchemy_available:
|
||||
msg = ['Missing required libraries:', '<ul>']
|
||||
if not json_available:
|
||||
msg.append('<li>simplejson</li>')
|
||||
if not sqlalchemy_available:
|
||||
msg.append('<li>Flask-SQLAlchemy</li>')
|
||||
msg.append('</ul>')
|
||||
return '\n'.join(msg)
|
||||
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(),
|
||||
})
|
||||
|
||||
queries = get_debug_queries()
|
||||
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),
|
||||
'context_long': query.context,
|
||||
'context': format_fname(query.context)
|
||||
'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'])
|
||||
def sql_select():
|
||||
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'],
|
||||
defaults=dict(explain=True))
|
||||
def sql_select(explain=False):
|
||||
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
|
||||
else:
|
||||
statement = 'EXPLAIN\n%s' % statement
|
||||
|
||||
result = engine.execute(statement, params)
|
||||
return g.debug_toolbar.render('panels/sqlalchemy_select.html', {
|
||||
'result': result.fetchall(),
|
||||
@@ -116,22 +154,3 @@ def sql_select():
|
||||
'sql': format_sql(statement, params),
|
||||
'duration': float(request.args['duration']),
|
||||
})
|
||||
|
||||
|
||||
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'])
|
||||
def sql_explain():
|
||||
statement, params = load_query(request.args['query'])
|
||||
engine = SQLAlchemy().get_engine(current_app)
|
||||
|
||||
if engine.driver == 'pysqlite':
|
||||
query = 'EXPLAIN QUERY PLAN %s' % statement
|
||||
else:
|
||||
query = 'EXPLAIN %s' % statement
|
||||
|
||||
result = engine.execute(query, params)
|
||||
return g.debug_toolbar.render('panels/sqlalchemy_explain.html', {
|
||||
'result': result.fetchall(),
|
||||
'headers': result.keys(),
|
||||
'sql': format_sql(statement, params),
|
||||
'duration': float(request.args['duration']),
|
||||
})
|
||||
@@ -1,12 +1,10 @@
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
import uuid
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
|
||||
from flask import (
|
||||
template_rendered, request, g, render_template_string,
|
||||
template_rendered, request, g,
|
||||
Response, current_app, abort, url_for
|
||||
)
|
||||
from flask_debugtoolbar import module
|
||||
@@ -95,7 +93,8 @@ 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)]
|
||||
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,
|
||||
@@ -131,6 +130,7 @@ def template_preview(key):
|
||||
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')
|
||||
return Response(json.dumps(msg), status=400,
|
||||
mimetype='application/json')
|
||||
finally:
|
||||
del tb
|
||||
@@ -37,13 +37,14 @@ class TimerDebugPanel(DebugPanel):
|
||||
|
||||
def nav_subtitle(self):
|
||||
# TODO l10n
|
||||
if self.has_resource:
|
||||
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)
|
||||
else:
|
||||
if not self.has_resource:
|
||||
return 'TOTAL: %0.2fms' % (self.total_time)
|
||||
|
||||
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)
|
||||
|
||||
def title(self):
|
||||
return _('Resource Usage')
|
||||
|
||||
@@ -51,7 +52,7 @@ class TimerDebugPanel(DebugPanel):
|
||||
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):
|
||||
|
||||
@@ -59,8 +60,8 @@ class TimerDebugPanel(DebugPanel):
|
||||
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')
|
||||
# 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
|
||||
@@ -81,9 +82,9 @@ class TimerDebugPanel(DebugPanel):
|
||||
(_('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)),
|
||||
# ('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)),
|
||||
)
|
||||
|
||||
context = self.context.copy()
|
||||
52
src/flask_debugtoolbar/panels/versions.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
from distutils.sysconfig import get_python_lib
|
||||
|
||||
from flask import __version__ as flask_version
|
||||
from flask_debugtoolbar.panels import DebugPanel
|
||||
|
||||
_ = lambda x: x
|
||||
|
||||
|
||||
def relpath(location, python_lib):
|
||||
location = os.path.normpath(location)
|
||||
relative = os.path.relpath(location, python_lib)
|
||||
if relative == os.path.curdir:
|
||||
return ''
|
||||
elif relative.startswith(os.path.pardir):
|
||||
return location
|
||||
return relative
|
||||
|
||||
|
||||
class VersionDebugPanel(DebugPanel):
|
||||
"""
|
||||
Panel that displays the Flask version.
|
||||
"""
|
||||
name = 'Version'
|
||||
has_content = True
|
||||
|
||||
def nav_title(self):
|
||||
return _('Versions')
|
||||
|
||||
def nav_subtitle(self):
|
||||
return 'Flask %s' % flask_version
|
||||
|
||||
def url(self):
|
||||
return ''
|
||||
|
||||
def title(self):
|
||||
return _('Versions')
|
||||
|
||||
def content(self):
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
packages = []
|
||||
else:
|
||||
packages = sorted(pkg_resources.working_set,
|
||||
key=lambda p: p.project_name.lower())
|
||||
|
||||
return self.render('panels/versions.html', {
|
||||
'packages': packages,
|
||||
'python_lib': os.path.normpath(get_python_lib()),
|
||||
'relpath': relpath,
|
||||
})
|
||||