134 Commits

Author SHA1 Message Date
Jeff Widman
2b1e7d9907 Bump version to 0.14.0 (#223)
We need this to land before we can push a release...
2023-12-06 16:48:19 -08:00
Mac Newbold
d0360218fd Drop CHANGES.rst in favor of GitHub Releases (#198) 2023-11-21 08:26:28 -07:00
Mac Newbold
e9fd3072a9 Merge branch 'master' into switch-to-using-github-auto-generated-releases 2023-11-21 08:24:53 -07:00
Grey Li
1aedfb0e2e Use standard Python gitignore file (#220)
Created from https://github.com/github/gitignore/blob/main/Python.gitignore
2023-11-20 18:29:03 -08:00
Grey Li
e6ae9d0288 Fix the test for basic app (#221)
1. Update the test app

If I understand correctly, the debug toolbar will only be enabled if `app.debug` is `True`. So I added the `DEBUG=True` to the test app.

Related code: 2b8bf9cc44/src/flask_debugtoolbar/__init__.py (L114)

2. Update the `src/flask_debugtoolbar/__init__.py`

Fix the two if statements to prevent the following errors:

```
>       if 'gzip' in response.headers.get('Content-Encoding'):
E       TypeError: argument of type 'NoneType' is not iterable
```

Since the `response.headers.get('Content-Encoding')` could be None.

With this PR, all the tests will be passed. The failed style checker will be fixed in #219
2023-11-20 18:21:38 -08:00
Grey Li
62ce443f8b Fix lint issues and lint config (#219)
For the two ignored rules:

- E731: It's OK to use lambda
- W504: W503 and W504 are conflicts with each other, we need to disable one of them.
2023-11-20 10:59:24 -08:00
Grey Li
2b8bf9cc44 Remove the use of before_first_request (#218) 2023-11-17 22:12:44 +08:00
Grey Li
42d859534a Remove the use of before_first_request 2023-11-17 22:09:25 +08:00
Grey Li
f959951185 Fix tox and GitHub actions settings (#217)
* Remove the branch constraint for pull request triggering
* Use Python 3.12 for main tests
* Use `ubuntu-20.04` for Python 3.6 since it's been removed in `ubuntu-latest`
* Remove Python 2.7 since it's not supported by GitHub Actions anymore (https://github.com/actions/setup-python/issues/672)
* Add the missing `setup-python` step
* Merge the `pip install` commands
2023-11-16 14:41:30 -08:00
dadavec
e1c8704444 Remove deprecated charset property from process_response content crafting (#211)
The `Request.charset` property is deprecated since Werkzeug version 2.3. It was removed in Werkzeug 3.0. Request data must always be UTF-8.

https://werkzeug.palletsprojects.com/en/2.3.x/wrappers/#werkzeug.wrappers.Request.charset
2023-11-15 19:53:02 -08:00
Mac Newbold
8a4cfa5e3c No need to specify custom default value if key not found (#210) 2023-11-15 17:30:39 -07:00
Grey Li
51d105afad Set up GitHub actions to replace Travis (#215) 2023-11-16 02:10:00 +08:00
Grey Li
15192f19e0 Set up github actions 2023-11-15 22:28:43 +08:00
Jeff Widman
5712e57869 No need to specify custom default value if key not found
The `''` arg specifies a custom default value if the key isn't found. However, the default of `None` works fine for boolean testing:

```python
>>> 'gzip' in [None]
False
```

I missed this when I originally reviewed https://github.com/pallets-eco/flask-debugtoolbar/pull/154.
2023-10-13 13:39:35 +00:00
Hiromasa Ihara
9571d06df5 fix: drop response.charset because charset deprecated (#206)
I took a quick peek at upstream to see if this has any backwards-breaking issues whereby we'd need to check the `werkzeug` version, but from my reading it looks it should be fine most of the time.

Also, this was deprecated back in April's `2.3.0` release and then removed in the recent `3.0.0` release, so I suspect most of our userbase will have already migrated to those versions.

Given this lib is a dev-tooling library not typically used in production, I'm not too worried about ensuring we support the 0.01% case where someone is using an old version of `werkzeug` + a custom charset.

More context:
* https://github.com/pallets/werkzeug/issues/2602
* https://github.com/pallets/werkzeug/pull/2641
* https://github.com/pallets/werkzeug/pull/2768
2023-10-13 06:35:25 -07:00
Nick Janetakis
3b25e114e9 fix: use urllib.parse.quote_plus and drop werkzeug.urls.url_quote_plus (#207) 2023-10-06 18:19:51 -04:00
Hiromasa Ihara
f7ae3fd591 fix: use urllib.parse.quote_plus and drop werkzeug.urls.url_quote_plus 2023-10-01 16:03:15 +09:00
Hiromasa Ihara
ce02d2da3c fix: migrate from deprecated flask.Markup to markupsafe.Markup (#203) 2023-05-24 10:22:56 -07:00
Jeff Widman
bd346a0fc1 Drop CHANGES.rst in favor of GitHub Releases
I started to cut a new release but realized it's a bit painful that we
are currently maintaining both a `CHANGES.rst` file and also tagging
releases in GitHub releases UI.

For a busy project, maintaining a dedicated changelog makes sense... but
the reality is this project is in maintenance mode, and we the
maintainers are more likely to cut releases when they're easy/low
friction. Since GitHub very nicely has the "Auto-generate-release-notes"
button, let's just use that.

I considered copy/pasting the results from that to `CHANGES.rst`, but
even that feels like a waste of time.
2023-01-17 20:05:25 +00:00
Jeff Widman
ed8243e17e Point URLs at pallets-eco/flask-debugtoolbar (#197) 2023-01-17 08:16:30 -08:00
Nick Janetakis
02c99a7b64 Fix Flask SQLAlchemy quickstart link (#196) 2022-12-27 08:10:52 -05:00
Francesco Frassinelli
96514793e4 Update sqlalchemy_error.html 2022-12-27 13:10:16 +01:00
Francesco Frassinelli
ec8cc3a0ba Fix Flask SQLAlchemy quickstart link 2022-12-27 10:54:53 +01:00
Jeff Widman
e3c8ab0ca2 Point at new location of django-debug-toolbar (#189) 2022-11-02 12:53:49 -07:00
Jeff Widman
fefb32b04d Fix outdated docs links (#187) 2022-11-02 12:51:06 -07:00
Nick Janetakis
b5a7c032ab Merge pull request #186 from Dosenpfand/flask-sqlalchemy-v3
Flask-SQLAlchemy 3 compatibility
2022-10-28 11:41:38 -04:00
Dosenpfand
6af24f5f44 Do not explicitly set SQLALCHEMY_TRACK_MODIFICATIONS for test/example 2022-10-28 11:28:50 +02:00
Dosenpfand
b4a197f87f Enable query recording if debug is active, remove warning 2022-10-26 14:31:14 +02:00
Dosenpfand
15b6fee933 Fix typo 2022-10-24 21:29:59 +02:00
Dosenpfand
9e03576c94 Keep config for flask_sqlalchemy < 3 2022-10-24 19:35:47 +02:00
Dosenpfand
bfa48c5a2c Add support for flask_sqlalchemy >= 3.0.0 2022-10-24 18:59:28 +02:00
sur.la.route
890fd9c7fb updated to work with flask 2.2+ (#183)
Replaced `_request_ctx_stack.top` with `request_ctx` per https://github.com/pallets/flask/pull/4682

This checks the version of `flask` and only does the switcheroo on newer versions of flask...
2022-10-04 16:20:14 -07:00
Hugo van Kemenade
42a9651950 Replace deprecated threading.currentThread with threading.current_thread (#179) 2022-09-18 14:08:43 -07:00
Nick Janetakis
ff01910f6a Merge pull request #182 from caffeinatedMike/master
Fixed scrollbar issues
2022-08-18 11:20:45 -04:00
Michael Hill
9cdb04edcb Make flDebugToolbar vertically scrollable
When on small screens where the debug toolbar is longer than the screen there was no way to access the items listed at the bottom of the toolbar. Adding `overflow-y: auto` allows a scrollbar to be used when this is the case.
2022-08-02 09:41:44 -04:00
Michael Hill
f546d4633b Fixed classname and bottom padding for scrollbar 2022-07-26 19:08:25 -04:00
Michael Hill
db07028aa0 Update scroll classname to make base.html template 2022-07-26 19:07:03 -04:00
Nick Janetakis
d44ab40729 Merge pull request #180 from timgates42/bugfix_typos
docs: Fix a few typos
2022-07-13 17:03:28 -04:00
Tim Gates
0d409f54f5 docs: Fix a few typos
There are small typos in:
- src/flask_debugtoolbar/panels/profiler.py
- src/flask_debugtoolbar/static/codemirror/mode/rst/rst.js
- src/flask_debugtoolbar/static/codemirror/mode/xquery/xquery.js
- src/flask_debugtoolbar/static/js/jquery.tablesorter.js

Fixes:
- Should read `second` rather than `secound`.
- Should read `preceded` rather than `preceeded`.
- Should read `initial` rather than `inital`.
- Should read `divided` rather than `divded`.
- Should read `debugging` rather than `debuging`.
- Should read `convenience` rather than `conveinence`.
- Should read `capabilities` rather than `capabilitys`.
2022-07-14 06:34:03 +10:00
Nate Collins
708df1c07a Expand HTTP codes on which the toolbar will be displayed (#176)
Change to expand what HTTP codes the toolbar displays on. Currently it only displays on 200 (or redirects when DEBUG_TB_INTERCEPT_REDIRECTS is enabled). However, debugging or verifying non-200 pages is commonly necessary. This change would enable display of the toolbar on other pages that may serve HTML, specifically: 201, 400, 401, 403, 404, 405, 500, 501, 502, 503, 504
2022-04-01 13:32:48 -07:00
Nathan Collins
10186a4202 Permit scrolling for content panels 2022-04-01 13:30:42 -07:00
Nate Collins
3cd2dceace Add ARIA role to toolbar for accessibility improvement (#174)
Change to improve accessibility compliance, for example useful when a developer is using a screen reader. Using the ARIA role attribute is a HTML4 compatible way of accomplishing this.

For reference: https://dequeuniversity.com/rules/axe/4.3/region

Co-authored-by: Megan Schanz <schanzme@msu.edu>
2022-04-01 13:30:16 -07:00
Megan Schanz
376c3deab3 Submodule to use https protocol after unencrypted git proto deprecated
Ref: https://github.blog/2021-09-01-improving-git-protocol-security-github/
2022-04-01 13:29:24 -07:00
Nick Janetakis
45d3588bb6 Release 0.13.1 2022-03-29 18:39:20 -04:00
Nick Janetakis
15e8d77a49 Fix changelog formatting for 0.12.X 2022-03-29 18:10:40 -04:00
Nick Janetakis
4d84f262ae Merge pull request #172 from flask-debugtoolbar/fix-setupcfg
Move library into src/ directory
2022-03-29 18:08:01 -04:00
Nick Janetakis
db64ce632c Move library into src/ directory
After building this locally I noticed all of the expected files were in
the wheel where as before this patch it was missing a lot of files.

This idea of using a src/ directory is mentioned in the official Python
documentation for packaging files at:

https://packaging.python.org/en/latest/tutorials/packaging-projects/

It's also used in Flask and other large Python projects.
2022-03-29 08:18:21 -04:00
Jeff Widman
956d7501ec Bump version for development 2022-03-28 01:19:17 -07:00
Jeff Widman
a758a9df7a Release 0.12.1 2022-03-28 01:16:17 -07:00
Jeff Widman
5eea25882c Fix changelog / docs link
I fat-fingered this in
https://github.com/flask-debugtoolbar/flask-debugtoolbar/pull/164 so
correcting it.
2022-03-28 01:14:12 -07:00
Jeff Widman
e954cd9fae Bump version for development 2022-03-27 23:48:24 -07:00
Jeff Widman
03d79be02c Release 0.12.0 2022-03-27 23:48:08 -07:00
Jeff Widman
7f17d2ce57 Update PyPI metadata files: add setup.cfg etc (#164)
Update to the newer PyPI / python packaging metadata file structure.

A lot of this was cribbed from how [`Flask`](https://github.com/pallets/flask) itself
exposes its metadata.
2022-03-26 21:51:54 -07:00
Nick Janetakis
30fba11f36 Remove with_ extension for Jinja 3.0 (#157)
Jinja 3.0 throws a deprecation warning that this will be removed in 3.1
because it's built into Jinja now without needing an extension.

However since folks might want to use Jinja 2 for a while this supports
both versions by only using the extension with Jinja 2.
2022-03-25 21:26:26 -07:00
jnnkB
d474a6a689 prefixed css classes, fixes #152 (#153) 2020-08-14 11:39:15 -07:00
zaw007
83d398d9d5 Support gzip response (#154)
* add gzip compress and decompress

* support gzip response
2020-08-14 11:37:37 -07:00
Jeff Widman
3929742d9c Cleanup version handling slightly (#149)
Some improvements I saw over on
https://github.com/FactoryBoy/factory_boy/pull/676/files that looked
useful here as well.
2020-03-09 13:10:02 -07:00
Jeff Widman
70abd78e55 Setup DB properly
When I switched over to `flask run` entrypoint in b92391d177,
I forgot that the `if name==__main__` code no longer triggers.
So the SQLite in-memory database wasn't getting created for the example
app.

This moves the DB creation to a werkzeug/Flask hook that runs before the
first request to the app, so that the DB table is created when we query
it.

Also updated the test which worked fine previously, but this is more
idiomatic.
2020-03-09 10:01:59 -07:00
Jeff Widman
dbea74b626 Update README.rst 2020-03-09 09:46:05 -07:00
Jeff Widman
c6102aeb14 Change docs to pull version from setup.py 2020-03-02 09:37:37 -08:00
Jeff Widman
10c9c1ae5d Update Flask-SQLAlchemy links 2020-02-22 20:39:21 -08:00
Yaser Amiri
9e600c6e13 Add flask.g section to show g object content. 2020-02-22 09:47:21 -08:00
Matthew Swabey
9b8a8afa97 Fix SQLAlchemy SELECT/EXPLAIN to use url_for to respect app prefixes. Provide url_for to all toolbar templates 2020-02-22 09:24:23 -08:00
Jeff Widman
39ac97a7e0 pycodestyle fixes 2020-02-18 01:21:40 -08:00
Jeff Widman
a5cb5a709f Bump version for development 2020-02-18 01:19:49 -08:00
Jeff Widman
02064c76ed Release 0.11.0 2020-02-18 01:11:19 -08:00
Jeff Widman
d713732807 Cleanup tox/travis
* Switch to python 3.8 in Travis. I tried to add 3.8 while keeping 3.7
and 3.6, but ran into issues with Travis config, so instead just bumped
straight to 3.8. Long term I'd like to explore moving to Azure
Pipelines, but don't have the time to figure that out just yet.
* `flake8` was renamed to `pycodestyle`
* `py.test` was deprecated in favor of `pytest`
2020-02-18 00:42:39 -08:00
Jeff Widman
b92391d177 Switch to Flask's native CLI
Drop `flask_script` in favor of Flask's native CLI:
* https://flask.palletsprojects.com/en/master/cli/

This also requires changing the tests so that `pytest` mocks the env var
`FLASK_ENV` so that the test app starts in development mode. Unlike
normal test apps, we _do_ want development/debug mode, in addition to
testing mode.
2020-02-17 23:57:04 -08:00
Florian
4964ae261f RoutesList: Do not show debugtoolbar routes 2020-02-17 22:30:10 -08:00
Pierre GIRAUD
ad847299c4 Add doc for SQL syntax highlighting 2020-02-17 21:57:01 -08:00
Jeff Widman
7ce099c3d0 Remove deprecated request.is_xhr
This was removed from `werkzeug` `1.0.0`.

Details:
* https://github.com/pallets/werkzeug/issues/1077
* https://github.com/pallets/werkzeug/issues/1714 (search for `is_xhr`)

Fix #144.
2020-02-17 21:50:56 -08:00
Jeff Widman
9c7db48362 Explicitly disable SQLALCHEMY_TRACK_MODIFICATIONS
This silences deprecation warnings.
Background: https://stackoverflow.com/a/33790196/770425

Note: This code can be removed once `flask_sqlalchemy` 3.0 ships, or any
release that includes
https://github.com/pallets/flask-sqlalchemy/pull/727.
2020-02-17 21:36:01 -08:00
Tim Gates
88f15cba35 Fix simple typo: exapanded -> expanded
Closes #141
2020-02-06 14:03:36 -08:00
Matt Good
d852042ccb Merge pull request #119 from davidism/json-available
don't use flask.json_available
2018-02-07 09:52:11 -08:00
David Lord
5bd2e8a423 flask.json_available is a no-op
it is removed in Flask 1.0
2018-02-07 07:13:21 -08:00
Jeff Widman
c27256c00a Bump dev version 2017-02-12 03:33:05 -08:00
Jeff Widman
010206298e Release 0.10.1 2017-02-12 03:04:07 -08:00
Jeff Widman
678ec31229 Revert "Change docs to pull version from setup.py"
This reverts commit 45b70c952f.
This commit broke the readthedocs build because RTD is building the docs
without running setup.py. So there is no package metadata.
2017-02-12 02:48:44 -08:00
Jeff Widman
23d52703fd Make copyright string track current year 2017-02-12 02:33:47 -08:00
Jeff Widman
45b70c952f Change docs to pull version from setup.py 2017-02-12 02:32:47 -08:00
Jeff Widman
9f2c353e86 Trivial imports cleanup 2017-02-12 02:31:26 -08:00
Jeff Widman
cfe142b285 Point Flask-SQLAlchemy docs at ‘latest’ 2017-02-12 00:21:20 -08:00
Jeff Widman
19a25fd895 Add universal wheel support
Let’s roll!
2017-02-10 01:09:59 -08:00
Jeff Widman
8dcb97c05a Travis should use python 3.6 interpreter 2017-02-10 00:50:38 -08:00
Jeff Widman
b49af327cf Use containers for travis tests 2017-02-10 00:44:27 -08:00
Jeff Widman
38ab3a49c1 Test using modern python 2017-02-10 00:42:00 -08:00
Jeff Widman
9f901e34ae Remove deprecated tox arg —use-mirrors 2017-02-10 00:36:19 -08:00
Jeff Widman
f050a6b0de Use latest for flask-sqlalchemy docs rather than pinning version 2017-02-10 00:28:45 -08:00
Jeff Widman
e33f3e1c85 Update RTD links w/https & .io 2017-02-09 23:54:16 -08:00
Peter M. Landwehr
02ff95acde Add LICENSE file to MANIFEST.in / source bundle (#102) 2016-09-18 23:13:08 -07:00
Iuri de Silvio
2436239964 Additional cleanup of deprecated flask.ext.* magic imports. (#97) 2016-06-29 16:52:23 -07:00
Michael Lenzen
18a0030354 Fix deprecated import from flask.ext.sqlalchemy (#94) 2016-06-29 13:33:02 -07:00
Matt Good
ee726eece2 Better setup.py description 2015-04-17 17:53:40 -07:00
Matt Good
1cd9ba69d1 Update CHANGES for 0.10 2015-04-17 17:49:30 -07:00
Matt Good
30a1cd399a Add docs note on Versions panel displaying other packages 2015-04-17 17:49:30 -07:00
Matt Good
0524e7f3e9 Update "Thanks" section of docs 2015-04-17 17:49:30 -07:00
Matt Good
14cd912df7 Fix style errors and add to automated checks
Adds a "stylecheck" to the Tox build config to automatically run flake8 checks.
Fixes existing flake8 issues.
2015-04-17 14:23:58 -07:00
Matt Good
f087311f7c Add missing sqlalchemy_error template
Missed adding this file in the last commit.
2015-04-17 14:19:41 -07:00
Matt Good
61d1fc2678 Display steps needed to display SQL queries
In the SQLAlchemy panel, detect if Flask-SQLAlchemy is not set up to record
queries for the current app, and display the necessary steps to set it up.

The following steps will be detected and displayed if needed:

* install Flask-SQLAlchemy package
* add the extension to this app
* set SQLALCHEMY_RECORD_QUERIES if DEBUG is False
2015-04-17 13:51:10 -07:00
Matt Good
6b2566c01f Enable toolbar on HTML5 pages without </body> tag
Includes the toolbar on HTML5 pages without an explicit </body> tag by checking
for the HTML5 `<!doctype html>`.

Fixes #79
2015-04-17 12:56:10 -07:00
Matt Good
31ea3bce41 Merge branch 'body-warning' 2015-04-16 14:54:32 -07:00
Matt Good
2d60ea6c8d Fix case-insensitive HTML insertion
Keeps the case-insensitive search for "</body>" and removes extra scan for the
tag by trying to insert the toolbar, and warning if unsuccessful.
2015-04-16 14:47:46 -07:00
Matt Good
56994a522d Document using init_app() for the factor pattern 2015-04-16 14:24:24 -07:00
Matt Good
aa90a9d052 Remove restyling table rows before sorting
It doesn't seem to be necessary to remove the "even / odd" styles before
sorting, since they are explicitly toggled to the right state after sorting. It
should be slightly more responsive to skip this step when sorting large tables.
2015-04-15 12:47:14 -07:00
Matt Good
32e0aa8ca3 Ensure profiler "Calls" are sorted numerically
JS Tablesorter's detection of the data type is occasionally confused when the
profiler calls show ratios like "94/57" for total calls to primitive calls.
This change enables parsing "data" attributes on the table headers to pass as
options to Tablesorter. This way we can explicitly specify to use the "digit"
sorter which works the way we want.

Fixes #62
2015-04-15 12:39:59 -07:00
Matt Good
e052b005ec Merge branch 'package-versions' 2015-04-14 19:21:45 -07:00
Matt Good
2f18ac90f9 Convert versions template to 2 spaces for indentation 2015-04-14 19:20:29 -07:00
Matt Good
612f93c129 Show relative paths for package paths
Since most of the paths are the same, only display the path relative to the
main Python library path.
2015-04-14 19:18:45 -07:00
Matt Good
c0826c9a31 Add detailed docs on the built-in panels 2015-04-14 17:22:07 -07:00
Matt Good
7e2de8f588 Merge pull request #48 from yoloseem/sqlparse
Suggestion: Use sqlparse.format() for better sql indentation
2015-04-14 15:44:00 -07:00
Matt Good
5aaa7a6634 Merge branch 'routes' 2015-04-14 15:28:16 -07:00
Matt Good
f7feecc751 Include the "static" route in the list
While this is enabled for most apps, it's optional and might still be useful to display.
2015-04-14 15:23:04 -07:00
Matt Good
cfe1624730 Simplify route methods display
Show just the names as text, without the Python list syntax formatting.
2015-04-14 14:58:31 -07:00
Matt Good
95d7eb977f Format route_list template with spaces 2015-04-14 14:57:47 -07:00
Matt Good
794380fc03 Merge pull request #81 from EricWorkman/master
Add tablesorter to slqlalchemy panel
2015-04-14 14:46:40 -07:00
Eric Workman
5a05620257 Add tablesorter to slqlalchemy panel 2015-04-10 15:44:38 -04:00
Matt Good
bebe884615 Update for 0.9.2 release 2014-12-05 15:43:46 -08:00
Matt Good
10c03880c7 Safely decode non-ascii SQL queries for display
SQL queries containing non-ascii byte strings would cause errors, both with and
without Pygments highlighting.

This updates the non-Pygments case to handle a simple decoding to ensure the
value is ascii-safe. It also removes passing an explicit "utf-8" encoding to
Pygments, since this causes errors when the bytes are not utf-8. When the
encoding is omitted, Pygments will default to "guess" the encoding by trying
utf-8 and falling back to latin-1.

Fixes #55
2014-12-04 14:06:48 -08:00
Matt Good
3fcdfc8f83 Use platform-sensitive filename normalization
The os.path.commonprefix() function only does basic string prefix checking, and
isn't aware of case-insensitive file names, or path separators. Instead, this
switches the filename formatting to use os.path.relpath() for normalizing paths
relative to sys.path.

Fixes #67
2014-12-02 18:15:59 -08:00
Matt Good
914553ddf5 Consolidate redundant code for SQL select/explain
The code for displaying the SQL select results and explain plan were nearly
identical, so this consolidates it to one function and template. It also fixes
correctly displaying the timing results as milliseconds.
2014-11-24 15:04:50 -08:00
Matt Good
b4fe0b954c Fix misspelled CSS classes 2014-11-24 14:46:41 -08:00
Matt Good
7557ee6794 Ensure SQL queries are HTML-escaped
The SQL queries were displayed with the `safe` filter which allowed properly
including the Pygments-highlighted HTML, but if Pygments wasn't installed this
allowed the raw SQL to be included without escaping. This change removes the
`safe` filter and instead wraps the Pygments HTML with the `Markup` class. This
allows proper auto-escaping in the template.

Fixes #70
2014-11-24 14:36:56 -08:00
Matt Good
382c5e7da9 Remove old commented code
Looks like some old code from the original Django toolbar was left in the
template.
2014-11-24 14:25:32 -08:00
Matt Good
6286dadc27 Version update for 0.9.1 release 2014-11-24 13:25:17 -08:00
=
81f8e34846 Removed printing of routes to console. 2014-03-23 15:14:28 +11:00
=
79717926a5 Added endpoints, HTTP methods, Is alias and Redirect to columns to route list. 2014-03-23 15:12:31 +11:00
Justin McKay
5084428c9d Added the Route List panel to show the routes that are available within Flask. 2014-03-22 23:57:48 +11:00
Matt Good
82295aa4aa printable filter should replace non-ascii bytes
In Python 2, when repr() returns bytes, replace any non-ascii bytes
with the unicode ? character to ensure that the result is printable.

Fixes #66
2014-02-02 14:46:41 -08:00
Matt Good
70488fc14a Fix Py3 support for bytes SQL queries
Fixes #64
2014-01-14 17:56:47 -08:00
Lucas Taylor
ccd8ba66b2 Display installed packages and versions in Versions panel (requires setup tools) 2013-03-27 17:28:51 -07:00
Lucas Taylor
093763909f Display installed packages and versions in Versions panel (requires Yolk) 2013-03-27 17:16:28 -07:00
Hyunjun Kim
bd2b65d068 Improve format_sql(): use sqlparse.format() 2013-03-11 13:33:41 +09:00
Rune Halvorsen
f6e37be73c Issue a warning, rather than an exception, when toolbar can't be inserted 2012-03-10 19:36:57 +01:00
Rune Halvorsen
71bd15a4d6 Added an exception when debug toolbar can't be inserted due to missing markup 2012-02-29 01:44:17 +01:00
211 changed files with 1317 additions and 624 deletions

41
.github/workflows/tests.yml vendored Normal file
View 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
View File

@@ -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
View File

@@ -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

View File

@@ -1,9 +0,0 @@
language: python
python: "2.7"
env:
- TOXENV=py26
- TOXENV=py27
- TOXENV=py33
install:
- pip install tox --use-mirrors
script: tox

View File

@@ -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

View File

@@ -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 *

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
docs/_static/screenshot-config-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/_static/screenshot-logger-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/_static/screenshot-time-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -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,
}

View File

@@ -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
View 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

View File

@@ -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()

View File

@@ -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

View File

@@ -1,63 +0,0 @@
<table>
<thead>
<tr>
<th>&nbsp;(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 }}&amp;duration={{ query.duration|urlencode }}">SELECT</a><br />
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?query={{ query.signed_query }}&amp;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>

View File

@@ -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>

View File

@@ -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
View File

@@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

36
setup.cfg Normal file
View 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

View File

@@ -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'
]
)

View File

@@ -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

View File

@@ -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

View 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)

View File

@@ -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')

View File

@@ -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 ''

View File

@@ -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 {},

View 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,
})

View File

@@ -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']),
})

View File

@@ -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

View File

@@ -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()

View 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,
})

Some files were not shown because too many files have changed in this diff Show More