From d402d0362ee1a619b41230b25cc099433b816925 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Sun, 21 Feb 2021 21:29:41 +0200 Subject: [PATCH] Cleanup type checking --- sanic/app.py | 15 ++++++++++++--- sanic/blueprints.py | 36 ++++++++++++++++++++++++++---------- sanic/config.py | 7 ++++--- sanic/errorpages.py | 23 ++++++++++++----------- sanic/mixins/listeners.py | 2 +- sanic/mixins/routes.py | 4 ++-- sanic/request.py | 6 +++--- sanic/router.py | 2 +- sanic/server.py | 3 ++- 9 files changed, 63 insertions(+), 35 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 028cfe8f..3958f9e2 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -243,6 +243,7 @@ class Sanic(BaseSanic): self.named_response_middleware[_rn] = deque() if middleware not in self.named_response_middleware[_rn]: self.named_response_middleware[_rn].appendleft(middleware) + return middleware def _apply_exception_handler(self, handler: FutureException): """Decorate a function to be registered as a handler for exceptions @@ -257,12 +258,12 @@ class Sanic(BaseSanic): self.error_handler.add(e, handler.handler) else: self.error_handler.add(exception, handler.handler) - return handler + return handler.handler def _apply_listener(self, listener: FutureListener): return self.register_listener(listener.listener, listener.event) - def _apply_route(self, route: FutureRoute) -> Route: + def _apply_route(self, route: FutureRoute) -> List[Route]: params = route._asdict() websocket = params.pop("websocket", False) subprotocols = params.pop("subprotocols", None) @@ -277,7 +278,15 @@ class Sanic(BaseSanic): websocket_handler.__name__ = route.handler.__name__ # type: ignore websocket_handler.is_websocket = True # type: ignore params["handler"] = websocket_handler - return self.router.add(**params) + + routes = self.router.add(**params) + if isinstance(routes, Route): + routes = [routes] + for r in routes: + r.ctx.websocket = websocket + r.ctx.static = params.get("static", False) + + return routes def _apply_static(self, static: FutureStatic) -> Route: return self._register_static(static) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index f3391e10..3705eb3f 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -1,8 +1,11 @@ from collections import defaultdict -from typing import Optional +from typing import Dict, List, Optional + +from sanic_routing.route import Route # type: ignore from sanic.base import BaseSanic from sanic.blueprint_group import BlueprintGroup +from sanic.handlers import ListenerType, MiddlewareType, RouteHandler from sanic.models.futures import FutureRoute, FutureStatic @@ -37,12 +40,12 @@ class Blueprint(BaseSanic): self.url_prefix = url_prefix self.host = host - self.routes = [] - self.websocket_routes = [] - self.exceptions = [] - self.listeners = defaultdict(list) - self.middlewares = [] - self.statics = [] + self.routes: List[Route] = [] + self.websocket_routes: List[Route] = [] + self.exceptions: List[RouteHandler] = [] + self.listeners: Dict[str, List[ListenerType]] = {} + self.middlewares: List[MiddlewareType] = [] + self.statics: List[RouteHandler] = [] self.version = version self.strict_slashes = strict_slashes @@ -107,6 +110,9 @@ class Blueprint(BaseSanic): url_prefix = options.get("url_prefix", self.url_prefix) routes = [] + middleware = [] + exception_handlers = [] + listeners = defaultdict(list) # Routes for future in self._future_routes: @@ -159,12 +165,22 @@ class Blueprint(BaseSanic): # Middleware if route_names: for future in self._future_middleware: - app._apply_middleware(future, route_names) + middleware.append(app._apply_middleware(future, route_names)) # Exceptions for future in self._future_exceptions: - app._apply_exception_handler(future) + exception_handlers.append(app._apply_exception_handler(future)) # Event listeners for listener in self._future_listeners: - app._apply_listener(listener) + listeners[listener.event].append(app._apply_listener(listener)) + + self.routes = [route for route in routes if isinstance(route, Route)] + + # Deprecate these in 21.6 + self.websocket_routes = [ + route for route in self.routes if route.ctx.websocket + ] + self.middlewares = middleware + self.exceptions = exception_handlers + self.listeners = dict(listeners) diff --git a/sanic/config.py b/sanic/config.py index f14dd489..042f03d6 100644 --- a/sanic/config.py +++ b/sanic/config.py @@ -113,8 +113,8 @@ class Config(dict): config.update_config("${some}/py/file") Yes you can put environment variable here, but they must be provided - in format: ``${some_env_var}``, and mark that ``$some_env_var`` is treated - as plain string. + in format: ``${some_env_var}``, and mark that ``$some_env_var`` is + treated as plain string. You can upload app config by providing dict holding settings. @@ -134,7 +134,8 @@ class Config(dict): config.update_config(C) - `See user guide `__ + `See user guide + `__ """ if isinstance(config, (bytes, str, Path)): diff --git a/sanic/errorpages.py b/sanic/errorpages.py index 325a3379..ceb8c92f 100644 --- a/sanic/errorpages.py +++ b/sanic/errorpages.py @@ -1,5 +1,6 @@ """ -Sanic `provides a pattern `_ +Sanic `provides a pattern +`_ for providing a response when an exception occurs. However, if you do no handle an exception, it will provide a fallback. There are three fallback types: @@ -72,9 +73,9 @@ class BaseRenderer: status_text = STATUS_CODES.get(self.status, b"Error Occurred").decode() return f"{self.status} — {status_text}" - def render(self) -> str: + def render(self) -> HTTPResponse: """ - Outputs the exception as a ``str`` for response. + Outputs the exception as a :class:`HTTPResponse`. :return: The formatted exception :rtype: str @@ -86,14 +87,14 @@ class BaseRenderer: ) return output() - def minimal(self) -> str: # noqa + def minimal(self) -> HTTPResponse: # noqa """ Provide a formatted message that is meant to not show any sensitive data or details. """ raise NotImplementedError - def full(self) -> str: # noqa + def full(self) -> HTTPResponse: # noqa """ Provide a formatted message that has all details and is mean to be used primarily for debugging and non-production environments. @@ -145,7 +146,7 @@ class HTMLRenderer(BaseRenderer): "{body}" ) - def full(self): + def full(self) -> HTTPResponse: return html( self.OUTPUT_HTML.format( title=self.title, @@ -156,7 +157,7 @@ class HTMLRenderer(BaseRenderer): status=self.status, ) - def minimal(self): + def minimal(self) -> HTTPResponse: return html( self.OUTPUT_HTML.format( title=self.title, @@ -217,7 +218,7 @@ class TextRenderer(BaseRenderer): OUTPUT_TEXT = "{title}\n{bar}\n{text}\n\n{body}" SPACER = " " - def full(self): + def full(self) -> HTTPResponse: return text( self.OUTPUT_TEXT.format( title=self.title, @@ -228,7 +229,7 @@ class TextRenderer(BaseRenderer): status=self.status, ) - def minimal(self): + def minimal(self) -> HTTPResponse: return text( self.OUTPUT_TEXT.format( title=self.title, @@ -277,11 +278,11 @@ class JSONRenderer(BaseRenderer): Render an exception as JSON. """ - def full(self): + def full(self) -> HTTPResponse: output = self._generate_output(full=True) return json(output, status=self.status, dumps=dumps) - def minimal(self): + def minimal(self) -> HTTPResponse: output = self._generate_output(full=False) return json(output, status=self.status, dumps=dumps) diff --git a/sanic/mixins/listeners.py b/sanic/mixins/listeners.py index 4e586b76..95645fbd 100644 --- a/sanic/mixins/listeners.py +++ b/sanic/mixins/listeners.py @@ -1,6 +1,6 @@ from enum import Enum, auto from functools import partial -from typing import Any, Callable, Coroutine, List, Optional, Set, Union +from typing import Any, Callable, Coroutine, List, Optional, Union from sanic.models.futures import FutureListener diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index 0c227e17..efe01648 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -30,7 +30,7 @@ class RouteMixin: self._future_routes: Set[FutureRoute] = set() self._future_statics: Set[FutureStatic] = set() self.name = "" - self.strict_slashes = False + self.strict_slashes: Optional[bool] = False def _apply_route(self, route: FutureRoute) -> Route: raise NotImplementedError # noqa @@ -41,7 +41,7 @@ class RouteMixin: def route( self, uri: str, - methods: Iterable[str] = frozenset({"GET"}), + methods: Optional[Iterable[str]] = None, host: Optional[str] = None, strict_slashes: Optional[bool] = None, stream: bool = False, diff --git a/sanic/request.py b/sanic/request.py index 175d2ef1..c17bb324 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -21,7 +21,6 @@ if TYPE_CHECKING: import email.utils import uuid -from asyncio.transports import Transport from collections import defaultdict from http.cookies import SimpleCookie from types import SimpleNamespace @@ -39,6 +38,7 @@ from sanic.headers import ( parse_xforwarded, ) from sanic.log import error_logger, logger +from sanic.models.protocol_types import TransportProtocol from sanic.response import BaseHTTPResponse, HTTPResponse @@ -116,7 +116,7 @@ class Request: headers: Header, version: str, method: str, - transport: Transport, + transport: TransportProtocol, app: Sanic, ): self.raw_url = url_bytes @@ -148,7 +148,7 @@ class Request: self.uri_template: Optional[str] = None self.request_middleware_started = False self._cookies: Optional[Dict[str, str]] = None - self._match_info = {} + self._match_info: Dict[str, Any] = {} self.stream: Optional[Http] = None self.endpoint: Optional[str] = None diff --git a/sanic/router.py b/sanic/router.py index 7c06079e..a9ae57ca 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -33,7 +33,7 @@ class Router(BaseRouter): @lru_cache(maxsize=ROUTER_CACHE_SIZE) def _get( self, path, method, host - ) -> Tuple[RouteHandler, Dict[str, Any], str, str, bool,]: + ) -> Tuple[RouteHandler, Dict[str, Any], str, str, bool]: try: route, handler, params = self.resolve( path=path, diff --git a/sanic/server.py b/sanic/server.py index 9f25895e..96f394ca 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -40,6 +40,7 @@ from sanic.config import Config from sanic.exceptions import RequestTimeout, ServiceUnavailable from sanic.http import Http, Stage from sanic.log import logger +from sanic.models.protocol_types import TransportProtocol from sanic.request import Request @@ -69,7 +70,7 @@ class ConnInfo: "ssl", ) - def __init__(self, transport: Transport, unix=None): + def __init__(self, transport: TransportProtocol, unix=None): self.ssl: bool = bool(transport.get_extra_info("sslcontext")) self.server = self.client = "" self.server_port = self.client_port = 0