mirror of
https://github.com/pallets-eco/flask-debugtoolbar.git
synced 2026-01-07 05:59:37 -06:00
Merge branch 'main' into dependabot/pip/requirements/zipp-3.19.1
This commit is contained in:
@@ -55,6 +55,13 @@ Name Description De
|
||||
==================================== ===================================== ==========================
|
||||
``DEBUG_TB_ENABLED`` Enable the toolbar? ``app.debug``
|
||||
``DEBUG_TB_HOSTS`` Whitelist of hosts to display toolbar any host
|
||||
``DEBUG_TB_ROUTES_HOST`` The host to associate with toolbar ``None``
|
||||
routes (where its assets are served
|
||||
from), or the sentinel value `*` to
|
||||
serve from the same host as the
|
||||
current request (ie any host). This
|
||||
is only required if Flask is
|
||||
configured to use `host_matching`.
|
||||
``DEBUG_TB_INTERCEPT_REDIRECTS`` Should intercept redirects? ``True``
|
||||
``DEBUG_TB_PANELS`` List of module/class names of panels enable all built-in panels
|
||||
``DEBUG_TB_PROFILER_ENABLED`` Enable the profiler on all requests ``False``, user-enabled
|
||||
|
||||
@@ -59,6 +59,8 @@ class DebugToolbarExtension:
|
||||
|
||||
def __init__(self, app: Flask | None = None) -> None:
|
||||
self.app = app
|
||||
self.toolbar_routes_host: str | None = None
|
||||
|
||||
# Support threads running `flask.copy_current_request_context` without
|
||||
# poping toolbar during `teardown_request`
|
||||
self.debug_toolbars_var: ContextVar[dict[Request, DebugToolbar]] = ContextVar(
|
||||
@@ -97,6 +99,8 @@ class DebugToolbarExtension:
|
||||
"var to be set"
|
||||
)
|
||||
|
||||
self._validate_and_configure_toolbar_routes_host(app)
|
||||
|
||||
DebugToolbar.load_panels(app)
|
||||
|
||||
app.before_request(self.process_request)
|
||||
@@ -110,6 +114,7 @@ class DebugToolbarExtension:
|
||||
"/_debug_toolbar/static/<path:filename>",
|
||||
"_debug_toolbar.static",
|
||||
self.send_static_file,
|
||||
host=self.toolbar_routes_host,
|
||||
)
|
||||
|
||||
app.register_blueprint(module, url_prefix="/_debug_toolbar/views")
|
||||
@@ -118,6 +123,7 @@ class DebugToolbarExtension:
|
||||
return {
|
||||
"DEBUG_TB_ENABLED": app.debug,
|
||||
"DEBUG_TB_HOSTS": (),
|
||||
"DEBUG_TB_ROUTES_HOST": None,
|
||||
"DEBUG_TB_INTERCEPT_REDIRECTS": True,
|
||||
"DEBUG_TB_PANELS": (
|
||||
"flask_debugtoolbar.panels.versions.VersionDebugPanel",
|
||||
@@ -135,6 +141,61 @@ class DebugToolbarExtension:
|
||||
"SQLALCHEMY_RECORD_QUERIES": app.debug,
|
||||
}
|
||||
|
||||
def _validate_and_configure_toolbar_routes_host(self, app: Flask) -> None:
|
||||
toolbar_routes_host = app.config["DEBUG_TB_ROUTES_HOST"]
|
||||
if app.url_map.host_matching and not toolbar_routes_host:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Flask-DebugToolbar requires DEBUG_TB_ROUTES_HOST to be set if Flask "
|
||||
"is running in `host_matching` mode. Static assets for the toolbar "
|
||||
"will not be served correctly unless this is set.",
|
||||
stacklevel=1,
|
||||
)
|
||||
|
||||
if toolbar_routes_host:
|
||||
if not app.url_map.host_matching:
|
||||
raise ValueError(
|
||||
"`DEBUG_TB_ROUTES_HOST` should only be set if your Flask app is "
|
||||
"using `host_matching`."
|
||||
)
|
||||
|
||||
if toolbar_routes_host.strip() == "*":
|
||||
toolbar_routes_host = "<toolbar_routes_host>"
|
||||
elif "<" in toolbar_routes_host and ">" in toolbar_routes_host:
|
||||
raise ValueError(
|
||||
"`DEBUG_TB_ROUTES_HOST` must either be a host name with no "
|
||||
"variables, to serve all Flask-DebugToolbar assets from a single "
|
||||
"host, or `*` to match the current request's host."
|
||||
)
|
||||
|
||||
# Automatically inject `toolbar_routes_host` into `url_for` calls for
|
||||
# the toolbar's `send_static_file` method.
|
||||
@app.url_defaults
|
||||
def inject_toolbar_routes_host_if_required(
|
||||
endpoint: str, values: dict[str, t.Any]
|
||||
) -> None:
|
||||
if app.url_map.is_endpoint_expecting(endpoint, "toolbar_routes_host"):
|
||||
values.setdefault("toolbar_routes_host", request.host)
|
||||
|
||||
# Automatically strip `toolbar_routes_host` from the endpoint values so
|
||||
# that the `send_static_host` method doesn't receive that parameter,
|
||||
# as it's not actually required internally.
|
||||
@app.url_value_preprocessor
|
||||
def strip_toolbar_routes_host_from_static_endpoint(
|
||||
endpoint: str | None, values: dict[str, t.Any] | None
|
||||
) -> None:
|
||||
if (
|
||||
endpoint
|
||||
and values
|
||||
and app.url_map.is_endpoint_expecting(
|
||||
endpoint, "toolbar_routes_host"
|
||||
)
|
||||
):
|
||||
values.pop("toolbar_routes_host", None)
|
||||
|
||||
self.toolbar_routes_host = toolbar_routes_host
|
||||
|
||||
def dispatch_request(self) -> t.Any:
|
||||
"""Modified version of ``Flask.dispatch_request`` to call
|
||||
:meth:`process_view`.
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from flask import Response
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
from flask_debugtoolbar import DebugToolbarExtension
|
||||
|
||||
|
||||
def load_app(name: str) -> FlaskClient:
|
||||
app: Flask = __import__(name).app
|
||||
@@ -15,3 +23,145 @@ def test_basic_app() -> None:
|
||||
index = app.get("/")
|
||||
assert index.status_code == 200
|
||||
assert b'<div id="flDebug"' in index.data
|
||||
|
||||
|
||||
def app_with_config(
|
||||
app_config: dict[str, t.Any], toolbar_config: dict[str, t.Any]
|
||||
) -> Flask:
|
||||
app = Flask(__name__, **app_config)
|
||||
app.config["DEBUG"] = True
|
||||
app.config["SECRET_KEY"] = "abc123"
|
||||
|
||||
for key, value in toolbar_config.items():
|
||||
app.config[key] = value
|
||||
|
||||
DebugToolbarExtension(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def test_toolbar_is_host_matching_but_flask_is_not() -> None:
|
||||
with pytest.raises(ValueError) as e:
|
||||
app_with_config(
|
||||
app_config=dict(host_matching=False),
|
||||
toolbar_config=dict(
|
||||
DEBUG_TB_ENABLED=True, DEBUG_TB_ROUTES_HOST="myapp.com"
|
||||
),
|
||||
)
|
||||
|
||||
assert str(e.value) == (
|
||||
"`DEBUG_TB_ROUTES_HOST` should only be set if your Flask app is "
|
||||
"using `host_matching`."
|
||||
)
|
||||
|
||||
|
||||
def test_flask_is_host_matching_but_toolbar_is_not() -> None:
|
||||
with pytest.warns(UserWarning) as record:
|
||||
app_with_config(
|
||||
app_config=dict(host_matching=True, static_host="static.com"),
|
||||
toolbar_config=dict(DEBUG_TB_ENABLED=True),
|
||||
)
|
||||
|
||||
assert isinstance(record[0].message, UserWarning)
|
||||
assert record[0].message.args[0] == (
|
||||
"Flask-DebugToolbar requires DEBUG_TB_ROUTES_HOST to be set if Flask "
|
||||
"is running in `host_matching` mode. Static assets for the toolbar "
|
||||
"will not be served correctly unless this is set."
|
||||
)
|
||||
|
||||
|
||||
def test_toolbar_host_variables_rejected() -> None:
|
||||
with pytest.raises(ValueError) as e:
|
||||
app_with_config(
|
||||
app_config=dict(host_matching=True, static_host="static.com"),
|
||||
toolbar_config=dict(
|
||||
DEBUG_TB_ENABLED=True, DEBUG_TB_ROUTES_HOST="<host>.com"
|
||||
),
|
||||
)
|
||||
|
||||
assert str(e.value) == (
|
||||
"`DEBUG_TB_ROUTES_HOST` must either be a host name with no "
|
||||
"variables, to serve all Flask-DebugToolbar assets from a single "
|
||||
"host, or `*` to match the current request's host."
|
||||
)
|
||||
|
||||
|
||||
def test_toolbar_in_host_mode_injects_toolbar_html() -> None:
|
||||
app = app_with_config(
|
||||
app_config=dict(host_matching=True, static_host="static.com"),
|
||||
toolbar_config=dict(DEBUG_TB_ENABLED=True, DEBUG_TB_ROUTES_HOST="myapp.com"),
|
||||
)
|
||||
|
||||
@app.route("/", host="myapp.com")
|
||||
def index() -> str:
|
||||
return "<html><head></head><body>OK</body></html>"
|
||||
|
||||
with app.test_client() as client:
|
||||
with app.app_context():
|
||||
response = client.get("/", headers={"Host": "myapp.com"})
|
||||
assert '<div id="flDebug" ' in response.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"tb_routes_host, request_host, expected_static_path",
|
||||
(
|
||||
("myapp.com", "myapp.com", "/_debug_toolbar/static/"),
|
||||
("toolbar.com", "myapp.com", "http://toolbar.com/_debug_toolbar/static/"),
|
||||
("*", "myapp.com", "/_debug_toolbar/static/"),
|
||||
),
|
||||
)
|
||||
def test_toolbar_injects_expected_static_path_for_host(
|
||||
tb_routes_host: str, request_host: str, expected_static_path: str
|
||||
) -> None:
|
||||
app = app_with_config(
|
||||
app_config=dict(host_matching=True, static_host="static.com"),
|
||||
toolbar_config=dict(DEBUG_TB_ENABLED=True, DEBUG_TB_ROUTES_HOST=tb_routes_host),
|
||||
)
|
||||
|
||||
@app.route("/", host=request_host)
|
||||
def index() -> str:
|
||||
return "<html><head></head><body>OK</body></html>"
|
||||
|
||||
with app.test_client() as client:
|
||||
with app.app_context():
|
||||
response = client.get("/", headers={"Host": request_host})
|
||||
|
||||
assert (
|
||||
"""<script type="text/javascript">"""
|
||||
f"""var DEBUG_TOOLBAR_STATIC_PATH = '{expected_static_path}'"""
|
||||
"""</script>"""
|
||||
) in response.text
|
||||
|
||||
|
||||
@patch(
|
||||
"flask.helpers.werkzeug.utils.send_from_directory",
|
||||
return_value=Response(b"some-file", mimetype="text/css", status=200),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"tb_routes_host, request_host, expected_status_code",
|
||||
(
|
||||
("toolbar.com", "toolbar.com", 200),
|
||||
("toolbar.com", "myapp.com", 404),
|
||||
("toolbar.com", "static.com", 404),
|
||||
("*", "toolbar.com", 200),
|
||||
("*", "myapp.com", 200),
|
||||
("*", "static.com", 200),
|
||||
),
|
||||
)
|
||||
def test_toolbar_serves_assets_based_on_host_configuration(
|
||||
mock_send_from_directory: MagicMock,
|
||||
tb_routes_host: str,
|
||||
request_host: str,
|
||||
expected_status_code: int,
|
||||
) -> None:
|
||||
app = app_with_config(
|
||||
app_config=dict(host_matching=True, static_host="static.com"),
|
||||
toolbar_config=dict(DEBUG_TB_ENABLED=True, DEBUG_TB_ROUTES_HOST=tb_routes_host),
|
||||
)
|
||||
|
||||
with app.test_client() as client:
|
||||
with app.app_context():
|
||||
response = client.get(
|
||||
"/_debug_toolbar/static/js/toolbar.js", headers={"Host": request_host}
|
||||
)
|
||||
assert response.status_code == expected_status_code
|
||||
|
||||
Reference in New Issue
Block a user