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.")
|
||||
|
||||
self.name = name
|
||||
self.url_prefix = url_prefix
|
||||
self._url_prefix = url_prefix
|
||||
self.subdomain = subdomain
|
||||
self.deferred_functions: list[DeferredSetupFunction] = []
|
||||
|
||||
|
|
@ -209,6 +209,57 @@ class Blueprint(Scaffold):
|
|||
self.url_values_defaults = url_defaults
|
||||
self.cli_group = cli_group
|
||||
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:
|
||||
if self._got_registered_once:
|
||||
|
|
@ -320,6 +371,13 @@ class Blueprint(Scaffold):
|
|||
self._got_registered_once = True
|
||||
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:
|
||||
state.add_url_rule(
|
||||
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