Skip to content

Commit 08e459e

Browse files
authored
Merge pull request #4045 from pallets/blueprint-name-dot
blueprint name may not contain a dot
2 parents d8c37f4 + 7c52614 commit 08e459e

File tree

4 files changed

+28
-85
lines changed

4 files changed

+28
-85
lines changed

CHANGES.rst

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Unreleased
1515
- Fix some types that weren't available in Python 3.6.0. :issue:`4040`
1616
- Improve typing for ``send_file``, ``send_from_directory``, and
1717
``get_send_file_max_age``. :issue:`4044`, :pr:`4026`
18+
- Show an error when a blueprint name contains a dot. The ``.`` has
19+
special meaning, it is used to separate (nested) blueprint names and
20+
the endpoint name. :issue:`4041`
1821

1922

2023
Version 2.0.0

src/flask/blueprints.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ def __init__(
188188
template_folder=template_folder,
189189
root_path=root_path,
190190
)
191+
192+
if "." in name:
193+
raise ValueError("'name' may not contain a dot '.' character.")
194+
191195
self.name = name
192196
self.url_prefix = url_prefix
193197
self.subdomain = subdomain
@@ -360,12 +364,12 @@ def add_url_rule(
360364
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
361365
the :func:`url_for` function is prefixed with the name of the blueprint.
362366
"""
363-
if endpoint:
364-
assert "." not in endpoint, "Blueprint endpoints should not contain dots"
365-
if view_func and hasattr(view_func, "__name__"):
366-
assert (
367-
"." not in view_func.__name__
368-
), "Blueprint view function name should not contain dots"
367+
if endpoint and "." in endpoint:
368+
raise ValueError("'endpoint' may not contain a dot '.' character.")
369+
370+
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
371+
raise ValueError("'view_func' name may not contain a dot '.' character.")
372+
369373
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
370374

371375
def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable:

tests/test_basic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,7 @@ def something_else():
16311631

16321632

16331633
def test_inject_blueprint_url_defaults(app):
1634-
bp = flask.Blueprint("foo.bar.baz", __name__, template_folder="template")
1634+
bp = flask.Blueprint("foo", __name__, template_folder="template")
16351635

16361636
@bp.url_defaults
16371637
def bp_defaults(endpoint, values):
@@ -1644,12 +1644,12 @@ def view(page):
16441644
app.register_blueprint(bp)
16451645

16461646
values = dict()
1647-
app.inject_url_defaults("foo.bar.baz.view", values)
1647+
app.inject_url_defaults("foo.view", values)
16481648
expected = dict(page="login")
16491649
assert values == expected
16501650

16511651
with app.test_request_context("/somepage"):
1652-
url = flask.url_for("foo.bar.baz.view")
1652+
url = flask.url_for("foo.view")
16531653
expected = "/login"
16541654
assert url == expected
16551655

tests/test_blueprints.py

+12-76
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import functools
2-
31
import pytest
42
from jinja2 import TemplateNotFound
53
from werkzeug.http import parse_cache_control_header
@@ -253,28 +251,9 @@ def test_templates_list(test_apps):
253251
assert templates == ["admin/index.html", "frontend/index.html"]
254252

255253

256-
def test_dotted_names(app, client):
257-
frontend = flask.Blueprint("myapp.frontend", __name__)
258-
backend = flask.Blueprint("myapp.backend", __name__)
259-
260-
@frontend.route("/fe")
261-
def frontend_index():
262-
return flask.url_for("myapp.backend.backend_index")
263-
264-
@frontend.route("/fe2")
265-
def frontend_page2():
266-
return flask.url_for(".frontend_index")
267-
268-
@backend.route("/be")
269-
def backend_index():
270-
return flask.url_for("myapp.frontend.frontend_index")
271-
272-
app.register_blueprint(frontend)
273-
app.register_blueprint(backend)
274-
275-
assert client.get("/fe").data.strip() == b"/be"
276-
assert client.get("/fe2").data.strip() == b"/fe"
277-
assert client.get("/be").data.strip() == b"/fe"
254+
def test_dotted_name_not_allowed(app, client):
255+
with pytest.raises(ValueError):
256+
flask.Blueprint("app.ui", __name__)
278257

279258

280259
def test_dotted_names_from_app(app, client):
@@ -343,62 +322,19 @@ def index():
343322
def test_route_decorator_custom_endpoint_with_dots(app, client):
344323
bp = flask.Blueprint("bp", __name__)
345324

346-
@bp.route("/foo")
347-
def foo():
348-
return flask.request.endpoint
349-
350-
try:
351-
352-
@bp.route("/bar", endpoint="bar.bar")
353-
def foo_bar():
354-
return flask.request.endpoint
355-
356-
except AssertionError:
357-
pass
358-
else:
359-
raise AssertionError("expected AssertionError not raised")
360-
361-
try:
362-
363-
@bp.route("/bar/123", endpoint="bar.123")
364-
def foo_bar_foo():
365-
return flask.request.endpoint
366-
367-
except AssertionError:
368-
pass
369-
else:
370-
raise AssertionError("expected AssertionError not raised")
371-
372-
def foo_foo_foo():
373-
pass
374-
375-
pytest.raises(
376-
AssertionError,
377-
lambda: bp.add_url_rule("/bar/123", endpoint="bar.123", view_func=foo_foo_foo),
378-
)
379-
380-
pytest.raises(
381-
AssertionError, bp.route("/bar/123", endpoint="bar.123"), lambda: None
382-
)
383-
384-
foo_foo_foo.__name__ = "bar.123"
325+
with pytest.raises(ValueError):
326+
bp.route("/", endpoint="a.b")(lambda: "")
385327

386-
pytest.raises(
387-
AssertionError, lambda: bp.add_url_rule("/bar/123", view_func=foo_foo_foo)
388-
)
328+
with pytest.raises(ValueError):
329+
bp.add_url_rule("/", endpoint="a.b")
389330

390-
bp.add_url_rule(
391-
"/bar/456", endpoint="foofoofoo", view_func=functools.partial(foo_foo_foo)
392-
)
331+
def view():
332+
return ""
393333

394-
app.register_blueprint(bp, url_prefix="/py")
334+
view.__name__ = "a.b"
395335

396-
assert client.get("/py/foo").data == b"bp.foo"
397-
# The rule's didn't actually made it through
398-
rv = client.get("/py/bar")
399-
assert rv.status_code == 404
400-
rv = client.get("/py/bar/123")
401-
assert rv.status_code == 404
336+
with pytest.raises(ValueError):
337+
bp.add_url_rule("/", view_func=view)
402338

403339

404340
def test_endpoint_decorator(app, client):

0 commit comments

Comments
 (0)