Add convenience for annotated handlers (#2225)

This commit is contained in:
Adam Hopkins 2021-08-30 20:04:44 +03:00 committed by GitHub
parent f32ef20b74
commit 2e5c288fea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 0 deletions

View File

@ -1,5 +1,9 @@
from __future__ import annotations
from functools import lru_cache
from inspect import signature
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from uuid import UUID
from sanic_routing import BaseRouter # type: ignore
from sanic_routing.exceptions import NoMethod # type: ignore
@ -106,6 +110,8 @@ class Router(BaseRouter):
version = str(version).strip("/").lstrip("v")
uri = "/".join([f"{version_prefix}{version}", uri.lstrip("/")])
uri = self._normalize(uri, handler)
params = dict(
path=uri,
handler=handler,
@ -187,3 +193,24 @@ class Router(BaseRouter):
raise SanicException(
f"Invalid route: {route}. Parameter names cannot use '__'."
)
def _normalize(self, uri: str, handler: RouteHandler) -> str:
if "<" not in uri:
return uri
sig = signature(handler)
mapping = {
param.name: param.annotation.__name__.lower()
for param in sig.parameters.values()
if param.annotation in (str, int, float, UUID)
}
reconstruction = []
for part in uri.split("/"):
if part.startswith("<") and ":" not in part:
name = part[1:-1]
annotation = mapping.get(name)
if annotation:
part = f"<{name}:{annotation}>"
reconstruction.append(part)
return "/".join(reconstruction)

View File

@ -0,0 +1,39 @@
from uuid import UUID
import pytest
from sanic import json
@pytest.mark.parametrize(
"idx,path,expectation",
(
(0, "/abc", "str"),
(1, "/123", "int"),
(2, "/123.5", "float"),
(3, "/8af729fe-2b94-4a95-a168-c07068568429", "UUID"),
),
)
def test_annotated_handlers(app, idx, path, expectation):
def build_response(num, foo):
return json({"num": num, "type": type(foo).__name__})
@app.get("/<foo>")
def handler0(_, foo: str):
return build_response(0, foo)
@app.get("/<foo>")
def handler1(_, foo: int):
return build_response(1, foo)
@app.get("/<foo>")
def handler2(_, foo: float):
return build_response(2, foo)
@app.get("/<foo>")
def handler3(_, foo: UUID):
return build_response(3, foo)
_, response = app.test_client.get(path)
assert response.json["num"] == idx
assert response.json["type"] == expectation