Allow blueprints and groups to be infinitely reusable (#2150)
* Allow blueprints and groups to be infinitely reusable
This commit is contained in:
parent
108a4a99c7
commit
53da4dd091
28
sanic/app.py
28
sanic/app.py
|
@ -420,7 +420,33 @@ class Sanic(BaseSanic):
|
|||
"""
|
||||
if isinstance(blueprint, (list, tuple, BlueprintGroup)):
|
||||
for item in blueprint:
|
||||
self.blueprint(item, **options)
|
||||
params = {**options}
|
||||
if isinstance(blueprint, BlueprintGroup):
|
||||
if blueprint.url_prefix:
|
||||
merge_from = [
|
||||
options.get("url_prefix", ""),
|
||||
blueprint.url_prefix,
|
||||
]
|
||||
if not isinstance(item, BlueprintGroup):
|
||||
merge_from.append(item.url_prefix or "")
|
||||
merged_prefix = "/".join(
|
||||
u.strip("/") for u in merge_from
|
||||
).rstrip("/")
|
||||
params["url_prefix"] = f"/{merged_prefix}"
|
||||
|
||||
for _attr in ["version", "strict_slashes"]:
|
||||
if getattr(item, _attr) is None:
|
||||
params[_attr] = getattr(
|
||||
blueprint, _attr
|
||||
) or options.get(_attr)
|
||||
if item.version_prefix == "/v":
|
||||
if blueprint.version_prefix == "/v":
|
||||
params["version_prefix"] = options.get(
|
||||
"version_prefix"
|
||||
)
|
||||
else:
|
||||
params["version_prefix"] = blueprint.version_prefix
|
||||
self.blueprint(item, **params)
|
||||
return
|
||||
if blueprint.name in self.blueprints:
|
||||
assert self.blueprints[blueprint.name] is blueprint, (
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableSequence
|
||||
from typing import TYPE_CHECKING, List, Optional, Union
|
||||
|
||||
import sanic
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic.blueprints import Blueprint
|
||||
|
@ -97,7 +97,7 @@ class BlueprintGroup(MutableSequence):
|
|||
return self._url_prefix
|
||||
|
||||
@property
|
||||
def blueprints(self) -> List["sanic.Blueprint"]:
|
||||
def blueprints(self) -> List[Blueprint]:
|
||||
"""
|
||||
Retrieve a list of all the available blueprints under this group.
|
||||
|
||||
|
@ -187,37 +187,16 @@ class BlueprintGroup(MutableSequence):
|
|||
"""
|
||||
return len(self._blueprints)
|
||||
|
||||
def _sanitize_blueprint(self, bp: "sanic.Blueprint") -> "sanic.Blueprint":
|
||||
"""
|
||||
Sanitize the Blueprint Entity to override the Version and strict slash
|
||||
behaviors as required.
|
||||
|
||||
:param bp: Sanic Blueprint entity Object
|
||||
:return: Modified Blueprint
|
||||
"""
|
||||
if self._url_prefix:
|
||||
merged_prefix = "/".join(
|
||||
u.strip("/") for u in [self._url_prefix, bp.url_prefix or ""]
|
||||
).rstrip("/")
|
||||
bp.url_prefix = f"/{merged_prefix}"
|
||||
for _attr in ["version", "strict_slashes"]:
|
||||
if getattr(bp, _attr) is None:
|
||||
setattr(bp, _attr, getattr(self, _attr))
|
||||
if bp.version_prefix == "/v":
|
||||
bp.version_prefix = self._version_prefix
|
||||
|
||||
return bp
|
||||
|
||||
def append(self, value: "sanic.Blueprint") -> None:
|
||||
def append(self, value: Blueprint) -> None:
|
||||
"""
|
||||
The Abstract class `MutableSequence` leverages this append method to
|
||||
perform the `BlueprintGroup.append` operation.
|
||||
:param value: New `Blueprint` object.
|
||||
:return: None
|
||||
"""
|
||||
self._blueprints.append(self._sanitize_blueprint(bp=value))
|
||||
self._blueprints.append(value)
|
||||
|
||||
def insert(self, index: int, item: "sanic.Blueprint") -> None:
|
||||
def insert(self, index: int, item: Blueprint) -> None:
|
||||
"""
|
||||
The Abstract class `MutableSequence` leverages this insert method to
|
||||
perform the `BlueprintGroup.append` operation.
|
||||
|
@ -226,7 +205,7 @@ class BlueprintGroup(MutableSequence):
|
|||
:param item: New `Blueprint` object.
|
||||
:return: None
|
||||
"""
|
||||
self._blueprints.insert(index, self._sanitize_blueprint(item))
|
||||
self._blueprints.insert(index, item)
|
||||
|
||||
def middleware(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -168,8 +168,6 @@ class Blueprint(BaseSanic):
|
|||
for i in nested:
|
||||
if isinstance(i, (list, tuple)):
|
||||
yield from chain(i)
|
||||
elif isinstance(i, BlueprintGroup):
|
||||
yield from i.blueprints
|
||||
else:
|
||||
yield i
|
||||
|
||||
|
@ -196,6 +194,7 @@ class Blueprint(BaseSanic):
|
|||
self._apps.add(app)
|
||||
url_prefix = options.get("url_prefix", self.url_prefix)
|
||||
opt_version = options.get("version", None)
|
||||
opt_strict_slashes = options.get("strict_slashes", None)
|
||||
opt_version_prefix = options.get("version_prefix", self.version_prefix)
|
||||
|
||||
routes = []
|
||||
|
@ -220,18 +219,13 @@ class Blueprint(BaseSanic):
|
|||
version_prefix = prefix
|
||||
break
|
||||
|
||||
version = self.version
|
||||
for v in (future.version, opt_version, self.version):
|
||||
if v is not None:
|
||||
version = v
|
||||
break
|
||||
|
||||
strict_slashes = (
|
||||
self.strict_slashes
|
||||
if future.strict_slashes is None
|
||||
and self.strict_slashes is not None
|
||||
else future.strict_slashes
|
||||
version = self._extract_value(
|
||||
future.version, opt_version, self.version
|
||||
)
|
||||
strict_slashes = self._extract_value(
|
||||
future.strict_slashes, opt_strict_slashes, self.strict_slashes
|
||||
)
|
||||
|
||||
name = app._generate_name(future.name)
|
||||
|
||||
apply_route = FutureRoute(
|
||||
|
@ -315,3 +309,12 @@ class Blueprint(BaseSanic):
|
|||
return_when=asyncio.FIRST_COMPLETED,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _extract_value(*values):
|
||||
value = values[-1]
|
||||
for v in values:
|
||||
if v is not None:
|
||||
value = v
|
||||
break
|
||||
return value
|
||||
|
|
|
@ -200,7 +200,7 @@ def test_bp_group_as_nested_group():
|
|||
blueprint_group_1 = Blueprint.group(
|
||||
Blueprint.group(blueprint_1, blueprint_2)
|
||||
)
|
||||
assert len(blueprint_group_1) == 2
|
||||
assert len(blueprint_group_1) == 1
|
||||
|
||||
|
||||
def test_blueprint_group_insert():
|
||||
|
@ -215,9 +215,29 @@ def test_blueprint_group_insert():
|
|||
group.insert(0, blueprint_1)
|
||||
group.insert(0, blueprint_2)
|
||||
group.insert(0, blueprint_3)
|
||||
assert group.blueprints[1].strict_slashes is False
|
||||
assert group.blueprints[2].strict_slashes is True
|
||||
assert group.blueprints[0].url_prefix == "/test"
|
||||
|
||||
@blueprint_1.route("/")
|
||||
def blueprint_1_default_route(request):
|
||||
return text("BP1_OK")
|
||||
|
||||
@blueprint_2.route("/")
|
||||
def blueprint_2_default_route(request):
|
||||
return text("BP2_OK")
|
||||
|
||||
@blueprint_3.route("/")
|
||||
def blueprint_3_default_route(request):
|
||||
return text("BP3_OK")
|
||||
|
||||
app = Sanic("PropTest")
|
||||
app.blueprint(group)
|
||||
app.router.finalize()
|
||||
|
||||
routes = [(route.path, route.strict) for route in app.router.routes]
|
||||
|
||||
assert len(routes) == 3
|
||||
assert ("v1/test/bp1/", True) in routes
|
||||
assert ("v1.3/test/bp2", False) in routes
|
||||
assert ("v1.3/test", False) in routes
|
||||
|
||||
|
||||
def test_bp_group_properties():
|
||||
|
@ -231,19 +251,25 @@ def test_bp_group_properties():
|
|||
url_prefix="/grouped",
|
||||
strict_slashes=True,
|
||||
)
|
||||
primary = Blueprint.group(group, url_prefix="/primary")
|
||||
|
||||
assert group.version_prefix == "/api/v"
|
||||
assert blueprint_1.version_prefix == "/api/v"
|
||||
assert blueprint_2.version_prefix == "/api/v"
|
||||
@blueprint_1.route("/")
|
||||
def blueprint_1_default_route(request):
|
||||
return text("BP1_OK")
|
||||
|
||||
assert group.version == 1
|
||||
assert blueprint_1.version == 1
|
||||
assert blueprint_2.version == 1
|
||||
@blueprint_2.route("/")
|
||||
def blueprint_2_default_route(request):
|
||||
return text("BP2_OK")
|
||||
|
||||
assert group.strict_slashes
|
||||
assert blueprint_1.strict_slashes
|
||||
assert blueprint_2.strict_slashes
|
||||
app = Sanic("PropTest")
|
||||
app.blueprint(group)
|
||||
app.blueprint(primary)
|
||||
app.router.finalize()
|
||||
|
||||
assert group.url_prefix == "/grouped"
|
||||
assert blueprint_1.url_prefix == "/grouped/bp1"
|
||||
assert blueprint_2.url_prefix == "/grouped/bp2"
|
||||
routes = [route.path for route in app.router.routes]
|
||||
|
||||
assert len(routes) == 4
|
||||
assert "api/v1/grouped/bp1/" in routes
|
||||
assert "api/v1/grouped/bp2/" in routes
|
||||
assert "api/v1/primary/grouped/bp1" in routes
|
||||
assert "api/v1/primary/grouped/bp2" in routes
|
||||
|
|
Loading…
Reference in New Issue
Block a user