diff --git a/sanic/app.py b/sanic/app.py index 8eceec9a..05ed7d54 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -550,6 +550,9 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta): ) else: params["version_prefix"] = blueprint.version_prefix + name_prefix = getattr(blueprint, "name_prefix", None) + if name_prefix and "name_prefix" not in params: + params["name_prefix"] = name_prefix self.blueprint(item, **params) return if blueprint.name in self.blueprints: diff --git a/sanic/blueprint_group.py b/sanic/blueprint_group.py index a9b51410..8b706c59 100644 --- a/sanic/blueprint_group.py +++ b/sanic/blueprint_group.py @@ -65,6 +65,7 @@ class BlueprintGroup(MutableSequence): "_version", "_strict_slashes", "_version_prefix", + "_name_prefix", ) def __init__( @@ -73,6 +74,7 @@ class BlueprintGroup(MutableSequence): version: Optional[Union[int, str, float]] = None, strict_slashes: Optional[bool] = None, version_prefix: str = "/v", + name_prefix: Optional[str] = "", ): """ Create a new Blueprint Group @@ -87,6 +89,7 @@ class BlueprintGroup(MutableSequence): self._version = version self._version_prefix = version_prefix self._strict_slashes = strict_slashes + self._name_prefix = name_prefix @property def url_prefix(self) -> Optional[Union[int, str, float]]: @@ -134,6 +137,15 @@ class BlueprintGroup(MutableSequence): """ return self._version_prefix + @property + def name_prefix(self) -> Optional[str]: + """ + Name prefix for the blueprint group + + :return: str + """ + return self._name_prefix + def __iter__(self): """ Tun the class Blueprint Group into an Iterable item diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 508e25ac..ef9c6756 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -250,6 +250,7 @@ class Blueprint(BaseSanic): version: Optional[Union[int, str, float]] = None, strict_slashes: Optional[bool] = None, version_prefix: str = "/v", + name_prefix: Optional[str] = "", ) -> BlueprintGroup: """ Create a list of blueprints, optionally grouping them under a @@ -275,6 +276,7 @@ class Blueprint(BaseSanic): version=version, strict_slashes=strict_slashes, version_prefix=version_prefix, + name_prefix=name_prefix, ) for bp in chain(blueprints): bps.append(bp) @@ -295,6 +297,7 @@ class Blueprint(BaseSanic): opt_version = options.get("version", None) opt_strict_slashes = options.get("strict_slashes", None) opt_version_prefix = options.get("version_prefix", self.version_prefix) + opt_name_prefix = options.get("name_prefix", None) error_format = options.get( "error_format", app.config.FALLBACK_ERROR_FORMAT ) @@ -326,7 +329,10 @@ class Blueprint(BaseSanic): future.strict_slashes, opt_strict_slashes, self.strict_slashes ) - name = app._generate_name(future.name) + name = future.name + if opt_name_prefix: + name = f"{opt_name_prefix}_{future.name}" + name = app._generate_name(name) host = future.host or self.host if isinstance(host, list): host = tuple(host) diff --git a/tests/test_blueprint_group.py b/tests/test_blueprint_group.py index 5e793cc1..330d5d9a 100644 --- a/tests/test_blueprint_group.py +++ b/tests/test_blueprint_group.py @@ -1,3 +1,5 @@ +import pytest + from pytest import raises from sanic.app import Sanic @@ -340,3 +342,40 @@ def test_nested_bp_group_properties(): routes = [route.path for route in app.router.routes] assert routes == ["three/one/four"] + + +@pytest.mark.asyncio +async def test_multiple_nested_bp_group(): + bp1 = Blueprint("bp1", url_prefix="/bp1") + bp2 = Blueprint("bp2", url_prefix="/bp2") + + bp1.add_route(lambda _: ..., "/", name="route1") + bp2.add_route(lambda _: ..., "/", name="route2") + + group_a = Blueprint.group( + bp1, bp2, url_prefix="/group-a", name_prefix="group-a" + ) + group_b = Blueprint.group( + bp1, bp2, url_prefix="/group-b", name_prefix="group-b" + ) + + app = Sanic("PropTest") + app.blueprint(group_a) + app.blueprint(group_b) + + await app._startup() + + routes = [route.path for route in app.router.routes] + assert routes == [ + "group-a/bp1", + "group-a/bp2", + "group-b/bp1", + "group-b/bp2", + ] + names = [route.name for route in app.router.routes] + assert names == [ + "PropTest.group-a_bp1.route1", + "PropTest.group-a_bp2.route2", + "PropTest.group-b_bp1.route1", + "PropTest.group-b_bp2.route2", + ]