feat(blueprints): 添加蓝图URL前缀动态计算功能
添加对嵌套蓝图URL前缀的动态计算支持,包括: 1. 将url_prefix改为属性并添加setter 2. 新增full_url_prefix属性获取完整前缀 3. 添加get_registered_url_prefix方法获取特定注册的前缀 4. 添加_registrations字典存储注册信息 5. 新增测试用例验证功能
This commit is contained in:
parent
7374c85dde
commit
ee141077bb
2 changed files with 239 additions and 1 deletions
|
|
@ -199,7 +199,7 @@ class Blueprint(Scaffold):
|
||||||
raise ValueError("'name' may not contain a dot '.' character.")
|
raise ValueError("'name' may not contain a dot '.' character.")
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url_prefix = url_prefix
|
self._url_prefix = url_prefix
|
||||||
self.subdomain = subdomain
|
self.subdomain = subdomain
|
||||||
self.deferred_functions: list[DeferredSetupFunction] = []
|
self.deferred_functions: list[DeferredSetupFunction] = []
|
||||||
|
|
||||||
|
|
@ -209,6 +209,57 @@ class Blueprint(Scaffold):
|
||||||
self.url_values_defaults = url_defaults
|
self.url_values_defaults = url_defaults
|
||||||
self.cli_group = cli_group
|
self.cli_group = cli_group
|
||||||
self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = []
|
self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = []
|
||||||
|
self._registrations: dict[str, dict[str, t.Any]] = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_prefix(self) -> str | None:
|
||||||
|
"""The URL prefix for this blueprint as set during initialization.
|
||||||
|
|
||||||
|
To get the full URL prefix including parent blueprint prefixes after
|
||||||
|
registration, use :attr:`full_url_prefix` or
|
||||||
|
:meth:`get_registered_url_prefix`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
return self._url_prefix
|
||||||
|
|
||||||
|
@url_prefix.setter
|
||||||
|
def url_prefix(self, value: str | None) -> None:
|
||||||
|
self._url_prefix = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_url_prefix(self) -> str | None:
|
||||||
|
"""The full URL prefix for this blueprint including any parent blueprint
|
||||||
|
prefixes.
|
||||||
|
|
||||||
|
If the blueprint has been registered exactly once, returns the full
|
||||||
|
URL prefix. If the blueprint has not been registered or has been
|
||||||
|
registered multiple times, returns None.
|
||||||
|
|
||||||
|
To get the URL prefix for a specific registration when the blueprint
|
||||||
|
has been registered multiple times, use :meth:`get_registered_url_prefix`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
"""
|
||||||
|
if len(self._registrations) == 1:
|
||||||
|
return next(iter(self._registrations.values())).get("url_prefix")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_registered_url_prefix(self, name: str | None = None) -> str | None:
|
||||||
|
"""Get the full URL prefix for a specific registration of this blueprint.
|
||||||
|
|
||||||
|
:param name: The registration name. If not provided and the blueprint
|
||||||
|
has been registered exactly once, returns that registration's prefix.
|
||||||
|
:return: The full URL prefix for the registration, or None if not found.
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
"""
|
||||||
|
if name is None:
|
||||||
|
if len(self._registrations) == 1:
|
||||||
|
return next(iter(self._registrations.values())).get("url_prefix")
|
||||||
|
return None
|
||||||
|
registration = self._registrations.get(name)
|
||||||
|
return registration.get("url_prefix") if registration else None
|
||||||
|
|
||||||
def _check_setup_finished(self, f_name: str) -> None:
|
def _check_setup_finished(self, f_name: str) -> None:
|
||||||
if self._got_registered_once:
|
if self._got_registered_once:
|
||||||
|
|
@ -320,6 +371,13 @@ class Blueprint(Scaffold):
|
||||||
self._got_registered_once = True
|
self._got_registered_once = True
|
||||||
state = self.make_setup_state(app, options, first_bp_registration)
|
state = self.make_setup_state(app, options, first_bp_registration)
|
||||||
|
|
||||||
|
self._registrations[name] = {
|
||||||
|
"url_prefix": state.url_prefix,
|
||||||
|
"subdomain": state.subdomain,
|
||||||
|
"name": name,
|
||||||
|
"options": options.copy(),
|
||||||
|
}
|
||||||
|
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
state.add_url_rule(
|
state.add_url_rule(
|
||||||
f"{self.static_url_path}/<path:filename>",
|
f"{self.static_url_path}/<path:filename>",
|
||||||
|
|
|
||||||
180
test_nested_blueprint_prefix.py
Normal file
180
test_nested_blueprint_prefix.py
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
"""Test cases for nested blueprint URL prefix dynamic calculation."""
|
||||||
|
import pytest
|
||||||
|
import flask
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_url_prefix_original():
|
||||||
|
"""Test that url_prefix returns the original value set during initialization."""
|
||||||
|
bp = flask.Blueprint("test", __name__, url_prefix="/test")
|
||||||
|
|
||||||
|
assert bp.url_prefix == "/test"
|
||||||
|
assert bp._url_prefix == "/test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_full_url_prefix_before_registration():
|
||||||
|
"""Test that full_url_prefix returns None before registration."""
|
||||||
|
bp = flask.Blueprint("test", __name__, url_prefix="/test")
|
||||||
|
|
||||||
|
assert bp.full_url_prefix is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_full_url_prefix_after_single_registration():
|
||||||
|
"""Test that full_url_prefix returns the full path after single registration."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
bp = flask.Blueprint("test", __name__, url_prefix="/test")
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
def index():
|
||||||
|
return "test"
|
||||||
|
|
||||||
|
app.register_blueprint(bp, url_prefix="/api")
|
||||||
|
|
||||||
|
assert bp.url_prefix == "/test"
|
||||||
|
assert bp.full_url_prefix == "/api/test"
|
||||||
|
assert bp.get_registered_url_prefix() == "/api/test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_blueprint_full_url_prefix():
|
||||||
|
"""Test that nested blueprints get the full URL prefix including parent prefixes."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
parent = flask.Blueprint("parent", __name__, url_prefix="/parent")
|
||||||
|
child = flask.Blueprint("child", __name__, url_prefix="/child")
|
||||||
|
grandchild = flask.Blueprint("grandchild", __name__, url_prefix="/grandchild")
|
||||||
|
|
||||||
|
@parent.route("/")
|
||||||
|
def parent_index():
|
||||||
|
return "parent"
|
||||||
|
|
||||||
|
@child.route("/")
|
||||||
|
def child_index():
|
||||||
|
return "child"
|
||||||
|
|
||||||
|
@grandchild.route("/")
|
||||||
|
def grandchild_index():
|
||||||
|
return "grandchild"
|
||||||
|
|
||||||
|
child.register_blueprint(grandchild)
|
||||||
|
parent.register_blueprint(child)
|
||||||
|
app.register_blueprint(parent, url_prefix="/api")
|
||||||
|
|
||||||
|
assert parent.url_prefix == "/parent"
|
||||||
|
assert parent.full_url_prefix == "/api/parent"
|
||||||
|
|
||||||
|
assert child.url_prefix == "/child"
|
||||||
|
assert child.full_url_prefix == "/api/parent/child"
|
||||||
|
|
||||||
|
assert grandchild.url_prefix == "/grandchild"
|
||||||
|
assert grandchild.full_url_prefix == "/api/parent/child/grandchild"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_blueprint_url_rules():
|
||||||
|
"""Test that nested blueprints have correct URL rules."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
parent = flask.Blueprint("parent", __name__, url_prefix="/parent")
|
||||||
|
child = flask.Blueprint("child", __name__, url_prefix="/child")
|
||||||
|
|
||||||
|
@parent.route("/home")
|
||||||
|
def parent_home():
|
||||||
|
return "parent home"
|
||||||
|
|
||||||
|
@child.route("/home")
|
||||||
|
def child_home():
|
||||||
|
return "child home"
|
||||||
|
|
||||||
|
parent.register_blueprint(child)
|
||||||
|
app.register_blueprint(parent, url_prefix="/api")
|
||||||
|
|
||||||
|
client = app.test_client()
|
||||||
|
|
||||||
|
assert client.get("/api/parent/home").data == b"parent home"
|
||||||
|
assert client.get("/api/parent/child/home").data == b"child home"
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_registrations():
|
||||||
|
"""Test behavior when blueprint is registered multiple times."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
bp = flask.Blueprint("test", __name__, url_prefix="/test")
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
def index():
|
||||||
|
return flask.request.endpoint
|
||||||
|
|
||||||
|
app.register_blueprint(bp, url_prefix="/api1")
|
||||||
|
app.register_blueprint(bp, name="test2", url_prefix="/api2")
|
||||||
|
|
||||||
|
assert bp.url_prefix == "/test"
|
||||||
|
assert bp.full_url_prefix is None
|
||||||
|
assert bp.get_registered_url_prefix() is None
|
||||||
|
assert bp.get_registered_url_prefix("test") == "/api1/test"
|
||||||
|
assert bp.get_registered_url_prefix("test2") == "/api2/test"
|
||||||
|
|
||||||
|
client = app.test_client()
|
||||||
|
assert client.get("/api1/test/").data == b"test.index"
|
||||||
|
assert client.get("/api2/test/").data == b"test2.index"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_registered_url_prefix_with_name():
|
||||||
|
"""Test get_registered_url_prefix with specific name."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
bp = flask.Blueprint("test", __name__, url_prefix="/test")
|
||||||
|
|
||||||
|
app.register_blueprint(bp, url_prefix="/api")
|
||||||
|
|
||||||
|
assert bp.get_registered_url_prefix("test") == "/api/test"
|
||||||
|
assert bp.get_registered_url_prefix("nonexistent") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_without_url_prefix():
|
||||||
|
"""Test blueprint without initial url_prefix."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
bp = flask.Blueprint("test", __name__)
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
def index():
|
||||||
|
return "test"
|
||||||
|
|
||||||
|
app.register_blueprint(bp, url_prefix="/api")
|
||||||
|
|
||||||
|
assert bp.url_prefix is None
|
||||||
|
assert bp.full_url_prefix == "/api"
|
||||||
|
assert bp.get_registered_url_prefix() == "/api"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_with_partial_prefixes():
|
||||||
|
"""Test nested blueprints with some missing prefixes."""
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
parent = flask.Blueprint("parent", __name__)
|
||||||
|
child = flask.Blueprint("child", __name__, url_prefix="/child")
|
||||||
|
grandchild = flask.Blueprint("grandchild", __name__)
|
||||||
|
|
||||||
|
@parent.route("/")
|
||||||
|
def parent_index():
|
||||||
|
return "parent"
|
||||||
|
|
||||||
|
@child.route("/")
|
||||||
|
def child_index():
|
||||||
|
return "child"
|
||||||
|
|
||||||
|
@grandchild.route("/")
|
||||||
|
def grandchild_index():
|
||||||
|
return "grandchild"
|
||||||
|
|
||||||
|
child.register_blueprint(grandchild, url_prefix="/gc")
|
||||||
|
parent.register_blueprint(child)
|
||||||
|
app.register_blueprint(parent, url_prefix="/api")
|
||||||
|
|
||||||
|
assert parent.full_url_prefix == "/api"
|
||||||
|
assert child.full_url_prefix == "/api/child"
|
||||||
|
assert grandchild.full_url_prefix == "/api/child/gc"
|
||||||
|
|
||||||
|
client = app.test_client()
|
||||||
|
assert client.get("/api/").data == b"parent"
|
||||||
|
assert client.get("/api/child/").data == b"child"
|
||||||
|
assert client.get("/api/child/gc/").data == b"grandchild"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
Loading…
Add table
Add a link
Reference in a new issue