GIT-2045: enable versioning and strict slash on BlueprintGroup (#2047)
* GIT-2045: enable versioning and strict slash on BlueprintGroup * GIT-2045: convert named tuple into typed format + unit tests * GIT-2045: add example code for versioned bpg * GIT-2045: None value for strict slashes check * GIT-2045: refactor handler types and add benchmark for urlparse * GIT-2045: reduce urlparse benchmark iterations * GIT-2045: add unit test and url merge behavior * GIT-2045: cleanup example code and remove print * GIT-2045: add test for slash duplication avoidence * GIT-2045: fix issue with tailing / getting appended * GIT-2045: use Optional instead of Union for Typing * GIT-2045: use string for version arg * GIT-2045: combine optional with union
This commit is contained in:
parent
be905e0009
commit
2c25af8cf5
35
examples/versioned_blueprint_group.py
Normal file
35
examples/versioned_blueprint_group.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
from sanic.response import json
|
||||||
|
|
||||||
|
|
||||||
|
app = Sanic(name="blue-print-group-version-example")
|
||||||
|
|
||||||
|
bp1 = Blueprint(name="ultron", url_prefix="/ultron")
|
||||||
|
bp2 = Blueprint(name="vision", url_prefix="/vision", strict_slashes=None)
|
||||||
|
|
||||||
|
bpg = Blueprint.group([bp1, bp2], url_prefix="/sentient/robot", version=1, strict_slashes=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bp1.get("/name")
|
||||||
|
async def bp1_name(request):
|
||||||
|
"""This will expose an Endpoint GET /v1/sentient/robot/ultron/name"""
|
||||||
|
return json({"name": "Ultron"})
|
||||||
|
|
||||||
|
|
||||||
|
@bp2.get("/name")
|
||||||
|
async def bp2_name(request):
|
||||||
|
"""This will expose an Endpoint GET /v1/sentient/robot/vision/name"""
|
||||||
|
return json({"name": "vision"})
|
||||||
|
|
||||||
|
|
||||||
|
@bp2.get("/name", version=2)
|
||||||
|
async def bp2_revised_name(request):
|
||||||
|
"""This will expose an Endpoint GET /v2/sentient/robot/vision/name"""
|
||||||
|
return json({"name": "new vision"})
|
||||||
|
|
||||||
|
|
||||||
|
app.blueprint(bpg)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host="0.0.0.0", port=8000)
|
|
@ -40,7 +40,7 @@ from sanic.exceptions import (
|
||||||
ServerError,
|
ServerError,
|
||||||
URLBuildError,
|
URLBuildError,
|
||||||
)
|
)
|
||||||
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
from sanic.handlers import ErrorHandler
|
||||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||||
from sanic.mixins.listeners import ListenerEvent
|
from sanic.mixins.listeners import ListenerEvent
|
||||||
from sanic.models.futures import (
|
from sanic.models.futures import (
|
||||||
|
@ -50,6 +50,7 @@ from sanic.models.futures import (
|
||||||
FutureRoute,
|
FutureRoute,
|
||||||
FutureStatic,
|
FutureStatic,
|
||||||
)
|
)
|
||||||
|
from sanic.models.handler_types import ListenerType, MiddlewareType
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import BaseHTTPResponse, HTTPResponse
|
from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||||
from sanic.router import Router
|
from sanic.router import Router
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from collections.abc import MutableSequence
|
from collections.abc import MutableSequence
|
||||||
from typing import List
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
import sanic
|
||||||
|
|
||||||
|
|
||||||
class BlueprintGroup(MutableSequence):
|
class BlueprintGroup(MutableSequence):
|
||||||
|
@ -16,6 +18,11 @@ class BlueprintGroup(MutableSequence):
|
||||||
bp1 = Blueprint('bp1', url_prefix='/bp1')
|
bp1 = Blueprint('bp1', url_prefix='/bp1')
|
||||||
bp2 = Blueprint('bp2', url_prefix='/bp2')
|
bp2 = Blueprint('bp2', url_prefix='/bp2')
|
||||||
|
|
||||||
|
bp3 = Blueprint('bp3', url_prefix='/bp4')
|
||||||
|
bp3 = Blueprint('bp3', url_prefix='/bp4')
|
||||||
|
|
||||||
|
bpg = BlueprintGroup(bp3, bp4, url_prefix="/api", version="v1")
|
||||||
|
|
||||||
@bp1.middleware('request')
|
@bp1.middleware('request')
|
||||||
async def bp1_only_middleware(request):
|
async def bp1_only_middleware(request):
|
||||||
print('applied on Blueprint : bp1 Only')
|
print('applied on Blueprint : bp1 Only')
|
||||||
|
@ -28,6 +35,14 @@ class BlueprintGroup(MutableSequence):
|
||||||
async def bp2_route(request, param):
|
async def bp2_route(request, param):
|
||||||
return text(param)
|
return text(param)
|
||||||
|
|
||||||
|
@bp3.route('/')
|
||||||
|
async def bp1_route(request):
|
||||||
|
return text('bp1')
|
||||||
|
|
||||||
|
@bp4.route('/<param>')
|
||||||
|
async def bp2_route(request, param):
|
||||||
|
return text(param)
|
||||||
|
|
||||||
group = Blueprint.group(bp1, bp2)
|
group = Blueprint.group(bp1, bp2)
|
||||||
|
|
||||||
@group.middleware('request')
|
@group.middleware('request')
|
||||||
|
@ -36,18 +51,23 @@ class BlueprintGroup(MutableSequence):
|
||||||
|
|
||||||
# Register Blueprint group under the app
|
# Register Blueprint group under the app
|
||||||
app.blueprint(group)
|
app.blueprint(group)
|
||||||
|
app.blueprint(bpg)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_blueprints", "_url_prefix")
|
__slots__ = ("_blueprints", "_url_prefix", "_version", "_strict_slashes")
|
||||||
|
|
||||||
def __init__(self, url_prefix=None):
|
def __init__(self, url_prefix=None, version=None, strict_slashes=None):
|
||||||
"""
|
"""
|
||||||
Create a new Blueprint Group
|
Create a new Blueprint Group
|
||||||
|
|
||||||
:param url_prefix: URL: to be prefixed before all the Blueprint Prefix
|
:param url_prefix: URL: to be prefixed before all the Blueprint Prefix
|
||||||
|
:param version: API Version for the blueprint group. This will be inherited by each of the Blueprint
|
||||||
|
:param strict_slashes: URL Strict slash behavior indicator
|
||||||
"""
|
"""
|
||||||
self._blueprints = []
|
self._blueprints = []
|
||||||
self._url_prefix = url_prefix
|
self._url_prefix = url_prefix
|
||||||
|
self._version = version
|
||||||
|
self._strict_slashes = strict_slashes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url_prefix(self) -> str:
|
def url_prefix(self) -> str:
|
||||||
|
@ -59,7 +79,7 @@ class BlueprintGroup(MutableSequence):
|
||||||
return self._url_prefix
|
return self._url_prefix
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def blueprints(self) -> List:
|
def blueprints(self) -> List["sanic.Blueprint"]:
|
||||||
"""
|
"""
|
||||||
Retrieve a list of all the available blueprints under this group.
|
Retrieve a list of all the available blueprints under this group.
|
||||||
|
|
||||||
|
@ -67,6 +87,25 @@ class BlueprintGroup(MutableSequence):
|
||||||
"""
|
"""
|
||||||
return self._blueprints
|
return self._blueprints
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> Optional[Union[str, int, float]]:
|
||||||
|
"""
|
||||||
|
API Version for the Blueprint Group. This will be applied only in case if the Blueprint doesn't already have
|
||||||
|
a version specified
|
||||||
|
|
||||||
|
:return: Version information
|
||||||
|
"""
|
||||||
|
return self._version
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strict_slashes(self) -> Optional[bool]:
|
||||||
|
"""
|
||||||
|
URL Slash termination behavior configuration
|
||||||
|
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
return self._strict_slashes
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
Tun the class Blueprint Group into an Iterable item
|
Tun the class Blueprint Group into an Iterable item
|
||||||
|
@ -121,7 +160,33 @@ class BlueprintGroup(MutableSequence):
|
||||||
"""
|
"""
|
||||||
return len(self._blueprints)
|
return len(self._blueprints)
|
||||||
|
|
||||||
def insert(self, index: int, item: object) -> None:
|
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))
|
||||||
|
return bp
|
||||||
|
|
||||||
|
def append(self, value: "sanic.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))
|
||||||
|
|
||||||
|
def insert(self, index: int, item: "sanic.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.
|
||||||
|
@ -130,7 +195,7 @@ class BlueprintGroup(MutableSequence):
|
||||||
:param item: New `Blueprint` object.
|
:param item: New `Blueprint` object.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._blueprints.insert(index, item)
|
self._blueprints.insert(index, self._sanitize_blueprint(item))
|
||||||
|
|
||||||
def middleware(self, *args, **kwargs):
|
def middleware(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Iterable
|
||||||
|
|
||||||
from sanic_routing.route import Route # type: ignore
|
from sanic_routing.route import Route # type: ignore
|
||||||
|
|
||||||
from sanic.base import BaseSanic
|
from sanic.base import BaseSanic
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.handlers import ListenerType, MiddlewareType, RouteHandler
|
from sanic.models.handler_types import (
|
||||||
|
ListenerType,
|
||||||
|
MiddlewareType,
|
||||||
|
RouteHandler,
|
||||||
|
)
|
||||||
from sanic.models.futures import FutureRoute, FutureStatic
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,16 +91,18 @@ class Blueprint(BaseSanic):
|
||||||
return super().exception(*args, **kwargs)
|
return super().exception(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group(*blueprints, url_prefix=""):
|
def group(*blueprints, url_prefix="", version=None, strict_slashes=None):
|
||||||
"""
|
"""
|
||||||
Create a list of blueprints, optionally grouping them under a
|
Create a list of blueprints, optionally grouping them under a
|
||||||
general URL prefix.
|
general URL prefix.
|
||||||
|
|
||||||
:param blueprints: blueprints to be registered as a group
|
:param blueprints: blueprints to be registered as a group
|
||||||
:param url_prefix: URL route to be prepended to all sub-prefixes
|
:param url_prefix: URL route to be prepended to all sub-prefixes
|
||||||
|
:param version: API Version to be used for Blueprint group
|
||||||
|
:param strict_slashes: Indicate strict slash termination behavior for URL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def chain(nested):
|
def chain(nested) -> Iterable[Blueprint]:
|
||||||
"""itertools.chain() but leaves strings untouched"""
|
"""itertools.chain() but leaves strings untouched"""
|
||||||
for i in nested:
|
for i in nested:
|
||||||
if isinstance(i, (list, tuple)):
|
if isinstance(i, (list, tuple)):
|
||||||
|
@ -106,11 +112,12 @@ class Blueprint(BaseSanic):
|
||||||
else:
|
else:
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
bps = BlueprintGroup(url_prefix=url_prefix)
|
bps = BlueprintGroup(
|
||||||
|
url_prefix=url_prefix,
|
||||||
|
version=version,
|
||||||
|
strict_slashes=strict_slashes,
|
||||||
|
)
|
||||||
for bp in chain(blueprints):
|
for bp in chain(blueprints):
|
||||||
if bp.url_prefix is None:
|
|
||||||
bp.url_prefix = ""
|
|
||||||
bp.url_prefix = url_prefix + bp.url_prefix
|
|
||||||
bps.append(bp)
|
bps.append(bp)
|
||||||
return bps
|
return bps
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from asyncio.events import AbstractEventLoop
|
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from typing import Any, Callable, Coroutine, Optional, TypeVar, Union
|
|
||||||
|
|
||||||
from sanic.errorpages import exception_response
|
from sanic.errorpages import exception_response
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
|
@ -9,24 +7,7 @@ from sanic.exceptions import (
|
||||||
InvalidRangeType,
|
InvalidRangeType,
|
||||||
)
|
)
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
from sanic.request import Request
|
from sanic.response import text
|
||||||
from sanic.response import BaseHTTPResponse, HTTPResponse, text
|
|
||||||
|
|
||||||
|
|
||||||
Sanic = TypeVar("Sanic")
|
|
||||||
|
|
||||||
MiddlewareResponse = Union[
|
|
||||||
Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]]
|
|
||||||
]
|
|
||||||
RequestMiddlewareType = Callable[[Request], MiddlewareResponse]
|
|
||||||
ResponseMiddlewareType = Callable[
|
|
||||||
[Request, BaseHTTPResponse], MiddlewareResponse
|
|
||||||
]
|
|
||||||
MiddlewareType = Union[RequestMiddlewareType, ResponseMiddlewareType]
|
|
||||||
ListenerType = Callable[
|
|
||||||
[Sanic, AbstractEventLoop], Optional[Coroutine[Any, Any, None]]
|
|
||||||
]
|
|
||||||
RouteHandler = Callable[..., Coroutine[Any, Any, HTTPResponse]]
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler:
|
class ErrorHandler:
|
||||||
|
|
|
@ -1,39 +1,52 @@
|
||||||
from collections import namedtuple
|
from pathlib import PurePath
|
||||||
|
from typing import NamedTuple, List, Union, Iterable, Optional
|
||||||
|
|
||||||
|
from sanic.models.handler_types import (
|
||||||
|
ListenerType,
|
||||||
|
MiddlewareType,
|
||||||
|
ErrorMiddlewareType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
FutureRoute = namedtuple(
|
class FutureRoute(NamedTuple):
|
||||||
"FutureRoute",
|
handler: str
|
||||||
[
|
uri: str
|
||||||
"handler",
|
methods: Optional[Iterable[str]]
|
||||||
"uri",
|
host: str
|
||||||
"methods",
|
strict_slashes: bool
|
||||||
"host",
|
stream: bool
|
||||||
"strict_slashes",
|
version: Optional[int]
|
||||||
"stream",
|
name: str
|
||||||
"version",
|
ignore_body: bool
|
||||||
"name",
|
websocket: bool
|
||||||
"ignore_body",
|
subprotocols: Optional[List[str]]
|
||||||
"websocket",
|
unquote: bool
|
||||||
"subprotocols",
|
static: bool
|
||||||
"unquote",
|
|
||||||
"static",
|
|
||||||
],
|
class FutureListener(NamedTuple):
|
||||||
)
|
listener: ListenerType
|
||||||
FutureListener = namedtuple("FutureListener", ["listener", "event"])
|
event: str
|
||||||
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
|
|
||||||
FutureException = namedtuple("FutureException", ["handler", "exceptions"])
|
|
||||||
FutureStatic = namedtuple(
|
class FutureMiddleware(NamedTuple):
|
||||||
"FutureStatic",
|
middleware: MiddlewareType
|
||||||
[
|
attach_to: str
|
||||||
"uri",
|
|
||||||
"file_or_directory",
|
|
||||||
"pattern",
|
class FutureException(NamedTuple):
|
||||||
"use_modified_since",
|
handler: ErrorMiddlewareType
|
||||||
"use_content_range",
|
exceptions: List[BaseException]
|
||||||
"stream_large_files",
|
|
||||||
"name",
|
|
||||||
"host",
|
class FutureStatic(NamedTuple):
|
||||||
"strict_slashes",
|
uri: str
|
||||||
"content_type",
|
file_or_directory: Union[str, bytes, PurePath]
|
||||||
],
|
pattern: str
|
||||||
)
|
use_modified_since: bool
|
||||||
|
use_content_range: bool
|
||||||
|
stream_large_files: bool
|
||||||
|
name: str
|
||||||
|
host: Optional[str]
|
||||||
|
strict_slashes: Optional[bool]
|
||||||
|
content_type: Optional[bool]
|
||||||
|
|
25
sanic/models/handler_types.py
Normal file
25
sanic/models/handler_types.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from asyncio.events import AbstractEventLoop
|
||||||
|
from typing import Any, Callable, Coroutine, Optional, TypeVar, Union
|
||||||
|
|
||||||
|
from sanic.request import Request
|
||||||
|
from sanic.response import BaseHTTPResponse, HTTPResponse
|
||||||
|
|
||||||
|
|
||||||
|
Sanic = TypeVar("Sanic")
|
||||||
|
|
||||||
|
|
||||||
|
MiddlewareResponse = Union[
|
||||||
|
Optional[HTTPResponse], Coroutine[Any, Any, Optional[HTTPResponse]]
|
||||||
|
]
|
||||||
|
RequestMiddlewareType = Callable[[Request], MiddlewareResponse]
|
||||||
|
ResponseMiddlewareType = Callable[
|
||||||
|
[Request, BaseHTTPResponse], MiddlewareResponse
|
||||||
|
]
|
||||||
|
ErrorMiddlewareType = Callable[
|
||||||
|
[Request, BaseException], Optional[Coroutine[Any, Any, None]]
|
||||||
|
]
|
||||||
|
MiddlewareType = Union[RequestMiddlewareType, ResponseMiddlewareType]
|
||||||
|
ListenerType = Callable[
|
||||||
|
[Sanic, AbstractEventLoop], Optional[Coroutine[Any, Any, None]]
|
||||||
|
]
|
||||||
|
RouteHandler = Callable[..., Coroutine[Any, Any, HTTPResponse]]
|
|
@ -10,7 +10,7 @@ from sanic_routing.route import Route # type: ignore
|
||||||
|
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
|
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
|
||||||
from sanic.handlers import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from sanic.handlers import ListenerType
|
from sanic.models.handler_types import ListenerType
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
|
@ -176,9 +176,7 @@ class WebSocketConnection:
|
||||||
await self._send(
|
await self._send(
|
||||||
{
|
{
|
||||||
"type": "websocket.accept",
|
"type": "websocket.accept",
|
||||||
"subprotocol": ",".join(
|
"subprotocol": ",".join(list(self.subprotocols)),
|
||||||
list(self.subprotocols)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ from typing import Tuple
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic_routing.exceptions import RouteExists
|
from sanic_routing.exceptions import RouteExists
|
||||||
from sanic_testing import TestManager
|
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
|
|
|
@ -2,6 +2,7 @@ from pytest import raises
|
||||||
|
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import HTTPResponse, text
|
from sanic.response import HTTPResponse, text
|
||||||
|
|
||||||
|
@ -200,3 +201,20 @@ def test_bp_group_as_nested_group():
|
||||||
Blueprint.group(blueprint_1, blueprint_2)
|
Blueprint.group(blueprint_1, blueprint_2)
|
||||||
)
|
)
|
||||||
assert len(blueprint_group_1) == 2
|
assert len(blueprint_group_1) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_group_insert():
|
||||||
|
blueprint_1 = Blueprint(
|
||||||
|
"blueprint_1", url_prefix="/bp1", strict_slashes=True, version=1
|
||||||
|
)
|
||||||
|
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
|
||||||
|
blueprint_3 = Blueprint("blueprint_3", url_prefix=None)
|
||||||
|
group = BlueprintGroup(
|
||||||
|
url_prefix="/test", version=1.3, strict_slashes=False
|
||||||
|
)
|
||||||
|
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"
|
||||||
|
|
|
@ -893,3 +893,98 @@ def test_strict_slashes_behavior_adoption():
|
||||||
|
|
||||||
assert app.test_client.get("/f1")[1].status == 200
|
assert app.test_client.get("/f1")[1].status == 200
|
||||||
assert app.test_client.get("/f1/")[1].status == 200
|
assert app.test_client.get("/f1/")[1].status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_group_versioning():
|
||||||
|
app = Sanic(name="blueprint-group-test")
|
||||||
|
|
||||||
|
bp1 = Blueprint(name="bp1", url_prefix="/bp1")
|
||||||
|
bp2 = Blueprint(name="bp2", url_prefix="/bp2", version=2)
|
||||||
|
|
||||||
|
bp3 = Blueprint(name="bp3", url_prefix="/bp3")
|
||||||
|
|
||||||
|
@bp3.get("/r1")
|
||||||
|
async def bp3_r1(request):
|
||||||
|
return json({"from": "bp3/r1"})
|
||||||
|
|
||||||
|
@bp1.get("/pre-group")
|
||||||
|
async def pre_group(request):
|
||||||
|
return json({"from": "bp1/pre-group"})
|
||||||
|
|
||||||
|
group = Blueprint.group([bp1, bp2], url_prefix="/group1", version=1)
|
||||||
|
|
||||||
|
group2 = Blueprint.group([bp3])
|
||||||
|
|
||||||
|
@bp1.get("/r1")
|
||||||
|
async def r1(request):
|
||||||
|
return json({"from": "bp1/r1"})
|
||||||
|
|
||||||
|
@bp2.get("/r2")
|
||||||
|
async def r2(request):
|
||||||
|
return json({"from": "bp2/r2"})
|
||||||
|
|
||||||
|
@bp2.get("/r3", version=3)
|
||||||
|
async def r3(request):
|
||||||
|
return json({"from": "bp2/r3"})
|
||||||
|
|
||||||
|
app.blueprint([group, group2])
|
||||||
|
|
||||||
|
assert app.test_client.get("/v1/group1/bp1/r1/")[1].status == 200
|
||||||
|
assert app.test_client.get("/v2/group1/bp2/r2")[1].status == 200
|
||||||
|
assert app.test_client.get("/v1/group1/bp1/pre-group")[1].status == 200
|
||||||
|
assert app.test_client.get("/v3/group1/bp2/r3")[1].status == 200
|
||||||
|
assert app.test_client.get("/bp3/r1")[1].status == 200
|
||||||
|
|
||||||
|
assert group.version == 1
|
||||||
|
assert group2.strict_slashes is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_group_strict_slashes():
|
||||||
|
app = Sanic(name="blueprint-group-test")
|
||||||
|
bp1 = Blueprint(name="bp1", url_prefix=None, strict_slashes=False)
|
||||||
|
|
||||||
|
bp2 = Blueprint(
|
||||||
|
name="bp2", version=3, url_prefix="/bp2", strict_slashes=None
|
||||||
|
)
|
||||||
|
|
||||||
|
bp3 = Blueprint(
|
||||||
|
name="bp3", version=None, url_prefix="/bp3/", strict_slashes=None
|
||||||
|
)
|
||||||
|
|
||||||
|
@bp1.get("/r1")
|
||||||
|
async def bp1_r1(request):
|
||||||
|
return json({"from": "bp1/r1"})
|
||||||
|
|
||||||
|
@bp2.get("/r1")
|
||||||
|
async def bp2_r1(request):
|
||||||
|
return json({"from": "bp2/r1"})
|
||||||
|
|
||||||
|
@bp2.get("/r2/")
|
||||||
|
async def bp2_r2(request):
|
||||||
|
return json({"from": "bp2/r2"})
|
||||||
|
|
||||||
|
@bp3.get("/r1")
|
||||||
|
async def bp3_r1(request):
|
||||||
|
return json({"from": "bp3/r1"})
|
||||||
|
|
||||||
|
group = Blueprint.group(
|
||||||
|
[bp1, bp2],
|
||||||
|
url_prefix="/slash-check/",
|
||||||
|
version=1.3,
|
||||||
|
strict_slashes=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
group2 = Blueprint.group(
|
||||||
|
[bp3], url_prefix="/other-prefix/", version="v2", strict_slashes=False
|
||||||
|
)
|
||||||
|
|
||||||
|
app.blueprint(group)
|
||||||
|
app.blueprint(group2)
|
||||||
|
|
||||||
|
assert app.test_client.get("/v1.3/slash-check/r1")[1].status == 200
|
||||||
|
assert app.test_client.get("/v1.3/slash-check/r1/")[1].status == 200
|
||||||
|
assert app.test_client.get("/v3/slash-check/bp2/r1")[1].status == 200
|
||||||
|
assert app.test_client.get("/v3/slash-check/bp2/r1/")[1].status == 404
|
||||||
|
assert app.test_client.get("/v3/slash-check/bp2/r2")[1].status == 404
|
||||||
|
assert app.test_client.get("/v3/slash-check/bp2/r2/")[1].status == 200
|
||||||
|
assert app.test_client.get("/v2/other-prefix/bp3/r1")[1].status == 200
|
||||||
|
|
Loading…
Reference in New Issue
Block a user