Skip to content

Commit

Permalink
GIT-2045: convert named tuple into typed format + unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
harshanarayana committed Mar 7, 2021
1 parent 4855586 commit 1a5bdc5
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 69 deletions.
38 changes: 25 additions & 13 deletions sanic/blueprint_group.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from collections.abc import MutableSequence
from typing import List
from typing import List, Union

import sanic
from sanic.exceptions import APIVersionMismatchException


class BlueprintGroup(MutableSequence):
Expand Down Expand Up @@ -80,14 +79,33 @@ def url_prefix(self) -> str:
return self._url_prefix

@property
def blueprints(self) -> List:
def blueprints(self) -> List["sanic.Blueprint"]:
"""
Retrieve a list of all the available blueprints under this group.
:return: List of Blueprint instance
"""
return self._blueprints

@property
def version(self) -> Union[None, str, int]:
"""
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) -> Union[None, bool]:
"""
URL Slash termination behavior configuration
:return: bool
"""
return self._strict_slashes

def __iter__(self):
"""
Tun the class Blueprint Group into an Iterable item
Expand Down Expand Up @@ -142,7 +160,7 @@ def __len__(self) -> int:
"""
return len(self._blueprints)

def insert(self, index: int, item: "sanic.blueprints.Blueprint") -> None:
def insert(self, index: int, item: "sanic.Blueprint") -> None:
"""
The Abstract class `MutableSequence` leverages this insert method to
perform the `BlueprintGroup.append` operation.
Expand All @@ -151,15 +169,9 @@ def insert(self, index: int, item: "sanic.blueprints.Blueprint") -> None:
:param item: New `Blueprint` object.
:return: None
"""
if self._version and item.version and self._version != item.version:
raise APIVersionMismatchException(
f"API Version Mismatch. Blueprint {item.name} has version {item.version} "
f"while Blueprint Group has {self._version}"
)
if self._version and not item.version:
item.version = self._version
if self._strict_slashes is not None:
item.strict_slashes = self._strict_slashes
for _attr in ["version", "strict_slashes"]:
if getattr(item, _attr) is None:
setattr(item, _attr, getattr(self, _attr))
self._blueprints.insert(index, item)

def middleware(self, *args, **kwargs):
Expand Down
10 changes: 2 additions & 8 deletions sanic/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from sanic.base import BaseSanic
from sanic.blueprint_group import BlueprintGroup
from sanic.exceptions import APIVersionMismatchException
from sanic.handlers import ListenerType, MiddlewareType, RouteHandler
from sanic.models.futures import FutureRoute, FutureStatic

Expand Down Expand Up @@ -115,16 +114,11 @@ def chain(nested) -> Iterable[Blueprint]:
strict_slashes=strict_slashes,
)
for bp in chain(blueprints):
if bp.version and version and bp.version != version:
raise APIVersionMismatchException(
f"API Version Mismatch. Blueprint {bp.name} has version {bp.version} "
f"while Blueprint Group has {version}"
)
if bp.url_prefix is None:
bp.url_prefix = ""
if not bp.version and version:
if bp.version is None and version:
bp.version = version
if strict_slashes is not None:
if bp.strict_slashes is None:
bp.strict_slashes = strict_slashes
bp.url_prefix = url_prefix + bp.url_prefix
bps.append(bp)
Expand Down
9 changes: 0 additions & 9 deletions sanic/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,6 @@ def __init__(self, message, status_code=None, quiet=None):
self.quiet = True


class APIVersionMismatchException(SanicException):
"""
Exception generated for route setup configuration workflow when blueprint and blueprint-groups
are being configured to use two different API Versions
"""

pass


@add_status_code(404)
class NotFound(SanicException):
"""
Expand Down
85 changes: 46 additions & 39 deletions sanic/models/futures.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
from collections import namedtuple


FutureRoute = namedtuple(
"FutureRoute",
[
"handler",
"uri",
"methods",
"host",
"strict_slashes",
"stream",
"version",
"name",
"ignore_body",
"websocket",
"subprotocols",
"unquote",
"static",
],
)
FutureListener = namedtuple("FutureListener", ["listener", "event"])
FutureMiddleware = namedtuple("FutureMiddleware", ["middleware", "attach_to"])
FutureException = namedtuple("FutureException", ["handler", "exceptions"])
FutureStatic = namedtuple(
"FutureStatic",
[
"uri",
"file_or_directory",
"pattern",
"use_modified_since",
"use_content_range",
"stream_large_files",
"name",
"host",
"strict_slashes",
"content_type",
],
)
from pathlib import PurePath
from typing import NamedTuple, List, Union, Callable


class FutureRoute(NamedTuple):
handler: str
uri: str
methods: Union[None, List[str]]
host: str
strict_slashes: bool
stream: bool
version: Union[None, int]
name: str
ignore_body: bool
websocket: bool
subprotocols: Union[None, List[str]]
unquote: bool
static: bool


class FutureListener(NamedTuple):
listener: Callable
event: str


class FutureMiddleware(NamedTuple):
middleware: Callable
attach_to: str


class FutureException(NamedTuple):
handler: Callable
exceptions: List[Exception]


class FutureStatic(NamedTuple):
uri: str
file_or_directory: Union[str, bytes, PurePath]
pattern: str
use_modified_since: bool
use_content_range: bool
stream_large_files: bool
name: str
host: Union[None, str]
strict_slashes: Union[None, bool]
content_type: Union[None, bool]
61 changes: 61 additions & 0 deletions tests/test_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,3 +893,64 @@ def third(request):

assert app.test_client.get("/f1")[1].status == 200
assert app.test_client.get("/f1/")[1].status == 200


def test_blueprint_group_versioning_and_strict_slash():
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")
bp4 = Blueprint(name="bp4", url_prefix=None)

bp5 = Blueprint(name="bp5", version=3, url_prefix="/bp5")

@bp5.get("/r1")
async def bp5_r1(request):
return json({"from": "bp5/r1"})

@bp4.post("/r1")
async def bp4_r1(request):
return json({"from": "bp4/r1"})

@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, bp4], strict_slashes=True)

@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"})

group2.insert(0, bp5)
app.blueprint([group, group2])

print(f"{app.router.routes_all}")
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 app.test_client.post("/r1/")[1].status == 404
assert app.test_client.post("/r1")[1].status == 200
assert app.test_client.get("/v3/bp5/r1")[1].status == 200
assert app.test_client.get("/v3/bp5/r1/")[1].status == 404

assert group.version == 1
assert group2.strict_slashes is True

0 comments on commit 1a5bdc5

Please sign in to comment.