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)):
|
if isinstance(blueprint, (list, tuple, BlueprintGroup)):
|
||||||
for item in blueprint:
|
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
|
return
|
||||||
if blueprint.name in self.blueprints:
|
if blueprint.name in self.blueprints:
|
||||||
assert self.blueprints[blueprint.name] is blueprint, (
|
assert self.blueprints[blueprint.name] is blueprint, (
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import MutableSequence
|
from collections.abc import MutableSequence
|
||||||
from typing import TYPE_CHECKING, List, Optional, Union
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
import sanic
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
@ -97,7 +97,7 @@ class BlueprintGroup(MutableSequence):
|
||||||
return self._url_prefix
|
return self._url_prefix
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def blueprints(self) -> List["sanic.Blueprint"]:
|
def blueprints(self) -> List[Blueprint]:
|
||||||
"""
|
"""
|
||||||
Retrieve a list of all the available blueprints under this group.
|
Retrieve a list of all the available blueprints under this group.
|
||||||
|
|
||||||
|
@ -187,37 +187,16 @@ class BlueprintGroup(MutableSequence):
|
||||||
"""
|
"""
|
||||||
return len(self._blueprints)
|
return len(self._blueprints)
|
||||||
|
|
||||||
def _sanitize_blueprint(self, bp: "sanic.Blueprint") -> "sanic.Blueprint":
|
def append(self, value: Blueprint) -> None:
|
||||||
"""
|
|
||||||
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:
|
|
||||||
"""
|
"""
|
||||||
The Abstract class `MutableSequence` leverages this append method to
|
The Abstract class `MutableSequence` leverages this append method to
|
||||||
perform the `BlueprintGroup.append` operation.
|
perform the `BlueprintGroup.append` operation.
|
||||||
:param value: New `Blueprint` object.
|
:param value: New `Blueprint` object.
|
||||||
:return: None
|
: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
|
The Abstract class `MutableSequence` leverages this insert method to
|
||||||
perform the `BlueprintGroup.append` operation.
|
perform the `BlueprintGroup.append` operation.
|
||||||
|
@ -226,7 +205,7 @@ class BlueprintGroup(MutableSequence):
|
||||||
:param item: New `Blueprint` object.
|
:param item: New `Blueprint` object.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._blueprints.insert(index, self._sanitize_blueprint(item))
|
self._blueprints.insert(index, item)
|
||||||
|
|
||||||
def middleware(self, *args, **kwargs):
|
def middleware(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -168,8 +168,6 @@ class Blueprint(BaseSanic):
|
||||||
for i in nested:
|
for i in nested:
|
||||||
if isinstance(i, (list, tuple)):
|
if isinstance(i, (list, tuple)):
|
||||||
yield from chain(i)
|
yield from chain(i)
|
||||||
elif isinstance(i, BlueprintGroup):
|
|
||||||
yield from i.blueprints
|
|
||||||
else:
|
else:
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
|
@ -196,6 +194,7 @@ class Blueprint(BaseSanic):
|
||||||
self._apps.add(app)
|
self._apps.add(app)
|
||||||
url_prefix = options.get("url_prefix", self.url_prefix)
|
url_prefix = options.get("url_prefix", self.url_prefix)
|
||||||
opt_version = options.get("version", None)
|
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_version_prefix = options.get("version_prefix", self.version_prefix)
|
||||||
|
|
||||||
routes = []
|
routes = []
|
||||||
|
@ -220,18 +219,13 @@ class Blueprint(BaseSanic):
|
||||||
version_prefix = prefix
|
version_prefix = prefix
|
||||||
break
|
break
|
||||||
|
|
||||||
version = self.version
|
version = self._extract_value(
|
||||||
for v in (future.version, opt_version, self.version):
|
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
|
|
||||||
)
|
)
|
||||||
|
strict_slashes = self._extract_value(
|
||||||
|
future.strict_slashes, opt_strict_slashes, self.strict_slashes
|
||||||
|
)
|
||||||
|
|
||||||
name = app._generate_name(future.name)
|
name = app._generate_name(future.name)
|
||||||
|
|
||||||
apply_route = FutureRoute(
|
apply_route = FutureRoute(
|
||||||
|
@ -315,3 +309,12 @@ class Blueprint(BaseSanic):
|
||||||
return_when=asyncio.FIRST_COMPLETED,
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
timeout=timeout,
|
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_1 = Blueprint.group(
|
||||||
Blueprint.group(blueprint_1, blueprint_2)
|
Blueprint.group(blueprint_1, blueprint_2)
|
||||||
)
|
)
|
||||||
assert len(blueprint_group_1) == 2
|
assert len(blueprint_group_1) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_blueprint_group_insert():
|
def test_blueprint_group_insert():
|
||||||
|
@ -215,9 +215,29 @@ def test_blueprint_group_insert():
|
||||||
group.insert(0, blueprint_1)
|
group.insert(0, blueprint_1)
|
||||||
group.insert(0, blueprint_2)
|
group.insert(0, blueprint_2)
|
||||||
group.insert(0, blueprint_3)
|
group.insert(0, blueprint_3)
|
||||||
assert group.blueprints[1].strict_slashes is False
|
|
||||||
assert group.blueprints[2].strict_slashes is True
|
@blueprint_1.route("/")
|
||||||
assert group.blueprints[0].url_prefix == "/test"
|
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():
|
def test_bp_group_properties():
|
||||||
|
@ -231,19 +251,25 @@ def test_bp_group_properties():
|
||||||
url_prefix="/grouped",
|
url_prefix="/grouped",
|
||||||
strict_slashes=True,
|
strict_slashes=True,
|
||||||
)
|
)
|
||||||
|
primary = Blueprint.group(group, url_prefix="/primary")
|
||||||
|
|
||||||
assert group.version_prefix == "/api/v"
|
@blueprint_1.route("/")
|
||||||
assert blueprint_1.version_prefix == "/api/v"
|
def blueprint_1_default_route(request):
|
||||||
assert blueprint_2.version_prefix == "/api/v"
|
return text("BP1_OK")
|
||||||
|
|
||||||
assert group.version == 1
|
@blueprint_2.route("/")
|
||||||
assert blueprint_1.version == 1
|
def blueprint_2_default_route(request):
|
||||||
assert blueprint_2.version == 1
|
return text("BP2_OK")
|
||||||
|
|
||||||
assert group.strict_slashes
|
app = Sanic("PropTest")
|
||||||
assert blueprint_1.strict_slashes
|
app.blueprint(group)
|
||||||
assert blueprint_2.strict_slashes
|
app.blueprint(primary)
|
||||||
|
app.router.finalize()
|
||||||
|
|
||||||
assert group.url_prefix == "/grouped"
|
routes = [route.path for route in app.router.routes]
|
||||||
assert blueprint_1.url_prefix == "/grouped/bp1"
|
|
||||||
assert blueprint_2.url_prefix == "/grouped/bp2"
|
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