Skip to content

Commit

Permalink
Merge branch 'master' into release-21.3
Browse files Browse the repository at this point in the history
  • Loading branch information
ahopkins authored Mar 21, 2021
2 parents 60e5ecd + 6763e2b commit 91eb440
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 79 deletions.
25 changes: 10 additions & 15 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,27 +676,23 @@ async def handle_request(self, request: Request):
response = None
try:
# Fetch handler from router
(
route,
handler,
kwargs,
) = self.router.get(request)
route, handler, kwargs = self.router.get(
request.path, request.method, request.headers.get("host")
)

request._match_info = kwargs
request.route = route
request.name = route.name
request.uri_template = f"/{route.path}"
request.endpoint = request.name

if (
request.stream
and request.stream.request_body
request.stream.request_body # type: ignore
and not route.ctx.ignore_body
):

if hasattr(handler, "is_stream"):
# Streaming handler: lift the size limit
request.stream.request_max_size = float("inf")
request.stream.request_max_size = float( # type: ignore
"inf"
)
else:
# Non-streaming handler: preload body
await request.receive_body()
Expand Down Expand Up @@ -730,8 +726,7 @@ async def handle_request(self, request: Request):
if response:
response = await request.respond(response)
else:
if request.stream:
response = request.stream.response
response = request.stream.response # type: ignore
# Make sure that response is finished / run StreamingHTTP callback

if isinstance(response, BaseHTTPResponse):
Expand All @@ -757,9 +752,9 @@ async def _websocket_handler(
):
request.app = self
if not getattr(handler, "__blueprintname__", False):
request.endpoint = handler.__name__
request._name = handler.__name__
else:
request.endpoint = (
request._name = (
getattr(handler, "__blueprintname__", "") + handler.__name__
)

Expand Down
27 changes: 4 additions & 23 deletions sanic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,19 @@
from sanic.mixins.signals import SignalMixin


class Base(type):
def __new__(cls, name, bases, attrs):
init = attrs.get("__init__")

def __init__(self, *args, **kwargs):
nonlocal init
nonlocal name

bases = [
b for base in type(self).__bases__ for b in base.__bases__
]

for base in bases:
base.__init__(self, *args, **kwargs)

if init:
init(self, *args, **kwargs)

attrs["__init__"] = __init__
return type.__new__(cls, name, bases, attrs)


class BaseSanic(
RouteMixin,
MiddlewareMixin,
ListenerMixin,
ExceptionMixin,
SignalMixin,
metaclass=Base,
):
__fake_slots__: Tuple[str, ...]

def __init__(self, *args, **kwargs) -> None:
for base in BaseSanic.__bases__:
base.__init__(self, *args, **kwargs) # type: ignore

def __str__(self) -> str:
return f"<{self.__class__.__name__} {self.name}>"

Expand Down
1 change: 1 addition & 0 deletions sanic/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def __init__(
version: Optional[int] = None,
strict_slashes: Optional[bool] = None,
):
super().__init__()

self._apps: Set[Sanic] = set()
self.ctx = SimpleNamespace()
Expand Down
24 changes: 18 additions & 6 deletions sanic/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,14 @@ class Request:
"_remote_addr",
"_socket",
"_match_info",
"_name",
"app",
"body",
"conn_info",
"ctx",
"endpoint",
"head",
"headers",
"method",
"name",
"parsed_args",
"parsed_not_grouped_args",
"parsed_files",
Expand All @@ -106,7 +105,6 @@ class Request:
"route",
"stream",
"transport",
"uri_template",
"version",
)

Expand All @@ -124,6 +122,7 @@ def __init__(
# TODO: Content-Encoding detection
self._parsed_url = parse_url(url_bytes)
self._id: Optional[Union[uuid.UUID, str, int]] = None
self._name: Optional[str] = None
self.app = app

self.headers = headers
Expand All @@ -136,7 +135,6 @@ def __init__(
self.body = b""
self.conn_info: Optional[ConnInfo] = None
self.ctx = SimpleNamespace()
self.name: Optional[str] = None
self.parsed_forwarded: Optional[Options] = None
self.parsed_json = None
self.parsed_form = None
Expand All @@ -147,12 +145,10 @@ def __init__(
self.parsed_not_grouped_args: DefaultDict[
Tuple[bool, bool, str, str], List[Tuple[str, str]]
] = defaultdict(list)
self.uri_template: Optional[str] = None
self.request_middleware_started = False
self._cookies: Optional[Dict[str, str]] = None
self._match_info: Dict[str, Any] = {}
self.stream: Optional[Http] = None
self.endpoint: Optional[str] = None
self.route: Optional[Route] = None
self._protocol = None

Expand Down Expand Up @@ -207,6 +203,22 @@ async def receive_body(self):
if not self.body:
self.body = b"".join([data async for data in self.stream])

@property
def name(self):
if self._name:
return self._name
elif self.route:
return self.route.name
return None

@property
def endpoint(self):
return self.name

@property
def uri_template(self):
return f"/{self.route.path}"

@property
def protocol(self):
if not self._protocol:
Expand Down
23 changes: 5 additions & 18 deletions sanic/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from sanic.constants import HTTP_METHODS
from sanic.exceptions import MethodNotSupported, NotFound, SanicException
from sanic.models.handler_types import RouteHandler
from sanic.request import Request


ROUTER_CACHE_SIZE = 1024
Expand All @@ -27,16 +26,11 @@ class Router(BaseRouter):
DEFAULT_METHOD = "GET"
ALLOWED_METHODS = HTTP_METHODS

# Putting the lru_cache on Router.get() performs better for the benchmarsk
# at tests/benchmark/test_route_resolution_benchmark.py
# However, overall application performance is significantly improved
# with the lru_cache on this method.
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def _get(
self, path, method, host
self, path: str, method: str, host: Optional[str]
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
try:
route, handler, params = self.resolve(
return self.resolve(
path=path,
method=method,
extra={"host": host},
Expand All @@ -50,14 +44,9 @@ def _get(
allowed_methods=e.allowed_methods,
)

return (
route,
handler,
params,
)

@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def get( # type: ignore
self, request: Request
self, path: str, method: str, host: Optional[str]
) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
"""
Retrieve a `Route` object containg the details about how to handle
Expand All @@ -69,9 +58,7 @@ def get( # type: ignore
correct response
:rtype: Tuple[ Route, RouteHandler, Dict[str, Any]]
"""
return self._get(
request.path, request.method, request.headers.get("host")
)
return self._get(path, method, host)

def add( # type: ignore
self,
Expand Down
38 changes: 22 additions & 16 deletions tests/benchmark/test_route_resolution_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ async def test_resolve_route_no_arg_string_path(
)
router, simple_routes = sanic_router(route_details=simple_routes)
route_to_call = choice(simple_routes)
request = Request(
"/{}".format(route_to_call[-1]).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
)

result = benchmark.pedantic(
router.get,
(
Request(
"/{}".format(route_to_call[-1]).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
),
request.path,
request.method,
request.headers.get("host"),
),
iterations=1000,
rounds=1000,
Expand All @@ -56,18 +59,21 @@ async def test_resolve_route_with_typed_args(
)

print("{} -> {}".format(route_to_call[-1], url))
request = Request(
"/{}".format(url).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
)

result = benchmark.pedantic(
router.get,
(
Request(
"/{}".format(url).encode(),
{"host": "localhost"},
"v1",
route_to_call[0],
None,
None,
),
request.path,
request.method,
request.headers.get("host"),
),
iterations=1000,
rounds=1000,
Expand Down
7 changes: 7 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,10 @@ def test_app_set_context(app):

retrieved = Sanic.get_app(app.name)
assert retrieved.ctx.foo == 1


def test_subclass_initialisation():
class CustomSanic(Sanic):
pass

CustomSanic("test_subclass_initialisation")
21 changes: 21 additions & 0 deletions tests/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ def test_request_id_defaults_uuid():
assert request.id == request.id == request._id


def test_name_none():
request = Request(b"/", {}, None, "GET", None, None)

assert request.name is None


def test_name_from_route():
request = Request(b"/", {}, None, "GET", None, None)
route = Mock()
request.route = route

assert request.name == route.name


def test_name_from_set():
request = Request(b"/", {}, None, "GET", None, None)
request._name = "foo"

assert request.name == "foo"


@pytest.mark.parametrize(
"request_id,expected_type",
(
Expand Down
4 changes: 3 additions & 1 deletion tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ def handler(request):
request = Request(path, headers, None, "GET", None, app)

try:
app.router.get(request=request)
app.router.get(
request.path, request.method, request.headers.get("host")
)
except NotFound:
response = 404
except Exception:
Expand Down

0 comments on commit 91eb440

Please sign in to comment.