Breakup App and Bluieprint
This commit is contained in:
		
							
								
								
									
										394
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										394
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -14,6 +14,8 @@ from traceback import format_exc | |||||||
| from typing import Any, Dict, Iterable, List, Optional, Set, Type, Union | from typing import Any, Dict, Iterable, List, Optional, Set, Type, Union | ||||||
| from urllib.parse import urlencode, urlunparse | from urllib.parse import urlencode, urlunparse | ||||||
|  |  | ||||||
|  | from sanic_routing.route import Route | ||||||
|  |  | ||||||
| from sanic import reloader_helpers | from sanic import reloader_helpers | ||||||
| from sanic.asgi import ASGIApp | from sanic.asgi import ASGIApp | ||||||
| from sanic.blueprint_group import BlueprintGroup | from sanic.blueprint_group import BlueprintGroup | ||||||
| @@ -28,6 +30,8 @@ from sanic.exceptions import ( | |||||||
| ) | ) | ||||||
| from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType | from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType | ||||||
| from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger | from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger | ||||||
|  | from sanic.mixins.routes import RouteMixin | ||||||
|  | from sanic.models.futures import FutureRoute | ||||||
| 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 | ||||||
| @@ -43,7 +47,7 @@ from sanic.views import CompositionView | |||||||
| from sanic.websocket import ConnectionClosed, WebSocketProtocol | from sanic.websocket import ConnectionClosed, WebSocketProtocol | ||||||
|  |  | ||||||
|  |  | ||||||
| class Sanic: | class Sanic(RouteMixin): | ||||||
|     _app_registry: Dict[str, "Sanic"] = {} |     _app_registry: Dict[str, "Sanic"] = {} | ||||||
|     test_mode = False |     test_mode = False | ||||||
|  |  | ||||||
| @@ -59,6 +63,7 @@ class Sanic: | |||||||
|         configure_logging: bool = True, |         configure_logging: bool = True, | ||||||
|         register: Optional[bool] = None, |         register: Optional[bool] = None, | ||||||
|     ) -> None: |     ) -> None: | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|         # Get name from previous stack frame |         # Get name from previous stack frame | ||||||
|         if name is None: |         if name is None: | ||||||
| @@ -161,391 +166,8 @@ class Sanic: | |||||||
|  |  | ||||||
|         return self.listener(event)(listener) |         return self.listener(event)(listener) | ||||||
|  |  | ||||||
|     # Decorator |     def _apply_route(self, route: FutureRoute) -> Route: | ||||||
|     def route( |         return self.router.add(**route._asdict()) | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         methods=frozenset({"GET"}), |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         ignore_body=False, |  | ||||||
|     ): |  | ||||||
|         """Decorate a function to be registered as a route |  | ||||||
|  |  | ||||||
|         :param uri: path of the URL |  | ||||||
|         :param methods: list or tuple of methods allowed |  | ||||||
|         :param host: |  | ||||||
|         :param strict_slashes: |  | ||||||
|         :param stream: |  | ||||||
|         :param version: |  | ||||||
|         :param name: user defined route name for url_for |  | ||||||
|         :return: tuple of routes, decorated function |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # Fix case where the user did not prefix the URL with a / |  | ||||||
|         # and will probably get confused as to why it's not working |  | ||||||
|         if not uri.startswith("/"): |  | ||||||
|             uri = "/" + uri |  | ||||||
|  |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         def response(handler): |  | ||||||
|             if isinstance(handler, tuple): |  | ||||||
|                 # if a handler fn is already wrapped in a route, the handler |  | ||||||
|                 # variable will be a tuple of (existing routes, handler fn) |  | ||||||
|                 routes, handler = handler |  | ||||||
|             else: |  | ||||||
|                 routes = [] |  | ||||||
|             args = list(signature(handler).parameters.keys()) |  | ||||||
|  |  | ||||||
|             if not args: |  | ||||||
|                 handler_name = handler.__name__ |  | ||||||
|  |  | ||||||
|                 raise ValueError( |  | ||||||
|                     f"Required parameter `request` missing " |  | ||||||
|                     f"in the {handler_name}() route?" |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|             if stream: |  | ||||||
|                 handler.is_stream = stream |  | ||||||
|  |  | ||||||
|             self.router.add( |  | ||||||
|                 uri=uri, |  | ||||||
|                 methods=methods, |  | ||||||
|                 handler=handler, |  | ||||||
|                 host=host, |  | ||||||
|                 strict_slashes=strict_slashes, |  | ||||||
|                 version=version, |  | ||||||
|                 name=name, |  | ||||||
|                 ignore_body=ignore_body, |  | ||||||
|             ) |  | ||||||
|             return routes, handler |  | ||||||
|  |  | ||||||
|         return response |  | ||||||
|  |  | ||||||
|     # Shorthand method decorators |  | ||||||
|     def get( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         ignore_body=True, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **GET** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **GET** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"GET"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|             ignore_body=ignore_body, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def post( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **POST** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **POST** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"POST"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def put( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **PUT** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **PUT** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"PUT"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def head( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         ignore_body=True, |  | ||||||
|     ): |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"HEAD"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|             ignore_body=ignore_body, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def options( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         ignore_body=True, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **OPTIONS** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **OPTIONS** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"OPTIONS"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|             ignore_body=ignore_body, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def patch( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **PATCH** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **PATCH** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"PATCH"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def delete( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         ignore_body=True, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **DELETE** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **DELETE** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request |  | ||||||
|             URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"DELETE"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|             ignore_body=ignore_body, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def add_route( |  | ||||||
|         self, |  | ||||||
|         handler, |  | ||||||
|         uri, |  | ||||||
|         methods=frozenset({"GET"}), |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         stream=False, |  | ||||||
|     ): |  | ||||||
|         """A helper method to register class instance or |  | ||||||
|         functions as a handler to the application url |  | ||||||
|         routes. |  | ||||||
|  |  | ||||||
|         :param handler: function or class instance |  | ||||||
|         :param uri: path of the URL |  | ||||||
|         :param methods: list or tuple of methods allowed, these are overridden |  | ||||||
|                         if using a HTTPMethodView |  | ||||||
|         :param host: |  | ||||||
|         :param strict_slashes: |  | ||||||
|         :param version: |  | ||||||
|         :param name: user defined route name for url_for |  | ||||||
|         :param stream: boolean specifying if the handler is a stream handler |  | ||||||
|         :return: function or class instance |  | ||||||
|         """ |  | ||||||
|         # Handle HTTPMethodView differently |  | ||||||
|         if hasattr(handler, "view_class"): |  | ||||||
|             methods = set() |  | ||||||
|  |  | ||||||
|             for method in HTTP_METHODS: |  | ||||||
|                 _handler = getattr(handler.view_class, method.lower(), None) |  | ||||||
|                 if _handler: |  | ||||||
|                     methods.add(method) |  | ||||||
|                     if hasattr(_handler, "is_stream"): |  | ||||||
|                         stream = True |  | ||||||
|  |  | ||||||
|         # handle composition view differently |  | ||||||
|         if isinstance(handler, CompositionView): |  | ||||||
|             methods = handler.handlers.keys() |  | ||||||
|             for _handler in handler.handlers.values(): |  | ||||||
|                 if hasattr(_handler, "is_stream"): |  | ||||||
|                     stream = True |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         self.route( |  | ||||||
|             uri=uri, |  | ||||||
|             methods=methods, |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         )(handler) |  | ||||||
|         return handler |  | ||||||
|  |  | ||||||
|     # Decorator |  | ||||||
|     def websocket( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         subprotocols=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Decorate a function to be registered as a websocket route |  | ||||||
|  |  | ||||||
|         :param uri: path of the URL |  | ||||||
|         :param host: Host IP or FQDN details |  | ||||||
|         :param strict_slashes: If the API endpoint needs to terminate |  | ||||||
|                                with a "/" or not |  | ||||||
|         :param subprotocols: optional list of str with supported subprotocols |  | ||||||
|         :param name: A unique name assigned to the URL so that it can |  | ||||||
|                      be used with :func:`url_for` |  | ||||||
|         :return: tuple of routes, decorated function |  | ||||||
|         """ |  | ||||||
|         self.enable_websocket() |  | ||||||
|  |  | ||||||
|         # Fix case where the user did not prefix the URL with a / |  | ||||||
|         # and will probably get confused as to why it's not working |  | ||||||
|         if not uri.startswith("/"): |  | ||||||
|             uri = "/" + uri |  | ||||||
|  |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         def response(handler): |  | ||||||
|             if isinstance(handler, tuple): |  | ||||||
|                 # if a handler fn is already wrapped in a route, the handler |  | ||||||
|                 # variable will be a tuple of (existing routes, handler fn) |  | ||||||
|                 routes, handler = handler |  | ||||||
|             else: |  | ||||||
|                 routes = [] |  | ||||||
|             websocket_handler = partial( |  | ||||||
|                 self._websocket_handler, handler, subprotocols=subprotocols |  | ||||||
|             ) |  | ||||||
|             websocket_handler.__name__ = ( |  | ||||||
|                 "websocket_handler_" + handler.__name__ |  | ||||||
|             ) |  | ||||||
|             websocket_handler.is_websocket = True |  | ||||||
|             routes.extend( |  | ||||||
|                 self.router.add( |  | ||||||
|                     uri=uri, |  | ||||||
|                     handler=websocket_handler, |  | ||||||
|                     methods=frozenset({"GET"}), |  | ||||||
|                     host=host, |  | ||||||
|                     strict_slashes=strict_slashes, |  | ||||||
|                     version=version, |  | ||||||
|                     name=name, |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|             return routes, handler |  | ||||||
|  |  | ||||||
|         return response |  | ||||||
|  |  | ||||||
|     def add_websocket_route( |     def add_websocket_route( | ||||||
|         self, |         self, | ||||||
|   | |||||||
| @@ -2,35 +2,18 @@ from collections import defaultdict, namedtuple | |||||||
|  |  | ||||||
| from sanic.blueprint_group import BlueprintGroup | from sanic.blueprint_group import BlueprintGroup | ||||||
| from sanic.constants import HTTP_METHODS | from sanic.constants import HTTP_METHODS | ||||||
|  | from sanic.mixins.routes import RouteMixin | ||||||
|  | from sanic.models.futures import ( | ||||||
|  |     FutureException, | ||||||
|  |     FutureListener, | ||||||
|  |     FutureMiddleware, | ||||||
|  |     FutureRoute, | ||||||
|  |     FutureStatic, | ||||||
|  | ) | ||||||
| from sanic.views import CompositionView | from sanic.views import CompositionView | ||||||
|  |  | ||||||
|  |  | ||||||
| FutureRoute = namedtuple( | class Blueprint(RouteMixin): | ||||||
|     "FutureRoute", |  | ||||||
|     [ |  | ||||||
|         "handler", |  | ||||||
|         "uri", |  | ||||||
|         "methods", |  | ||||||
|         "host", |  | ||||||
|         "strict_slashes", |  | ||||||
|         "stream", |  | ||||||
|         "version", |  | ||||||
|         "name", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
| FutureListener = namedtuple( |  | ||||||
|     "FutureListener", ["handler", "uri", "methods", "host"] |  | ||||||
| ) |  | ||||||
| FutureMiddleware = namedtuple( |  | ||||||
|     "FutureMiddleware", ["middleware", "args", "kwargs"] |  | ||||||
| ) |  | ||||||
| FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"]) |  | ||||||
| FutureStatic = namedtuple( |  | ||||||
|     "FutureStatic", ["uri", "file_or_directory", "args", "kwargs"] |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Blueprint: |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         name, |         name, | ||||||
| @@ -51,6 +34,8 @@ class Blueprint: | |||||||
|         :param strict_slashes: Enforce the API urls are requested with a |         :param strict_slashes: Enforce the API urls are requested with a | ||||||
|             training */* |             training */* | ||||||
|         """ |         """ | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.url_prefix = url_prefix |         self.url_prefix = url_prefix | ||||||
|         self.host = host |         self.host = host | ||||||
| @@ -64,6 +49,10 @@ class Blueprint: | |||||||
|         self.version = version |         self.version = version | ||||||
|         self.strict_slashes = strict_slashes |         self.strict_slashes = strict_slashes | ||||||
|  |  | ||||||
|  |     def route(self, *args, **kwargs): | ||||||
|  |         kwargs["apply"] = False | ||||||
|  |         return super().route(*args, **kwargs) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def group(*blueprints, url_prefix=""): |     def group(*blueprints, url_prefix=""): | ||||||
|         """ |         """ | ||||||
| @@ -106,217 +95,80 @@ class Blueprint: | |||||||
|  |  | ||||||
|         routes = [] |         routes = [] | ||||||
|  |  | ||||||
|  |         # TODO: | ||||||
|  |         # - Add BP name to handler name for all routes | ||||||
|  |  | ||||||
|         # Routes |         # Routes | ||||||
|         for future in self.routes: |         for future in self._future_routes: | ||||||
|             # attach the blueprint name to the handler so that it can be |             # attach the blueprint name to the handler so that it can be | ||||||
|             # prefixed properly in the router |             # prefixed properly in the router | ||||||
|             future.handler.__blueprintname__ = self.name |             future.handler.__blueprintname__ = self.name | ||||||
|             # Prepend the blueprint URI prefix if available |             # Prepend the blueprint URI prefix if available | ||||||
|             uri = url_prefix + future.uri if url_prefix else future.uri |             uri = url_prefix + future.uri if url_prefix else future.uri | ||||||
|  |  | ||||||
|             version = future.version or self.version |             apply_route = FutureRoute( | ||||||
|  |                 future.handler, | ||||||
|             _routes, _ = app.route( |                 uri[1:] if uri.startswith("//") else uri, | ||||||
|                 uri=uri[1:] if uri.startswith("//") else uri, |                 future.methods, | ||||||
|                 methods=future.methods, |                 future.host or self.host, | ||||||
|                 host=future.host or self.host, |                 future.strict_slashes, | ||||||
|                 strict_slashes=future.strict_slashes, |                 future.stream, | ||||||
|                 stream=future.stream, |                 future.version or self.version, | ||||||
|                 version=version, |                 future.name, | ||||||
|                 name=future.name, |                 future.ignore_body, | ||||||
|             )(future.handler) |  | ||||||
|             if _routes: |  | ||||||
|                 routes += _routes |  | ||||||
|  |  | ||||||
|         for future in self.websocket_routes: |  | ||||||
|             # attach the blueprint name to the handler so that it can be |  | ||||||
|             # prefixed properly in the router |  | ||||||
|             future.handler.__blueprintname__ = self.name |  | ||||||
|             # Prepend the blueprint URI prefix if available |  | ||||||
|             uri = url_prefix + future.uri if url_prefix else future.uri |  | ||||||
|             _routes, _ = app.websocket( |  | ||||||
|                 uri=uri, |  | ||||||
|                 host=future.host or self.host, |  | ||||||
|                 strict_slashes=future.strict_slashes, |  | ||||||
|                 name=future.name, |  | ||||||
|             )(future.handler) |  | ||||||
|             if _routes: |  | ||||||
|                 routes += _routes |  | ||||||
|  |  | ||||||
|         # Static Files |  | ||||||
|         for future in self.statics: |  | ||||||
|             # Prepend the blueprint URI prefix if available |  | ||||||
|             uri = url_prefix + future.uri if url_prefix else future.uri |  | ||||||
|             _routes = app.static( |  | ||||||
|                 uri, future.file_or_directory, *future.args, **future.kwargs |  | ||||||
|             ) |             ) | ||||||
|             if _routes: |  | ||||||
|                 routes += _routes |  | ||||||
|  |  | ||||||
|         route_names = [route.name for route in routes if route] |             _route = app._apply_route(apply_route) | ||||||
|  |  | ||||||
|         # Middleware |         # TODO: | ||||||
|         for future in self.middlewares: |         # for future in self.websocket_routes: | ||||||
|             if future.args or future.kwargs: |         #     # attach the blueprint name to the handler so that it can be | ||||||
|                 app.register_named_middleware( |         #     # prefixed properly in the router | ||||||
|                     future.middleware, |         #     future.handler.__blueprintname__ = self.name | ||||||
|                     route_names, |         #     # Prepend the blueprint URI prefix if available | ||||||
|                     *future.args, |         #     uri = url_prefix + future.uri if url_prefix else future.uri | ||||||
|                     **future.kwargs, |         #     _routes, _ = app.websocket( | ||||||
|                 ) |         #         uri=uri, | ||||||
|             else: |         #         host=future.host or self.host, | ||||||
|                 app.register_named_middleware(future.middleware, route_names) |         #         strict_slashes=future.strict_slashes, | ||||||
|  |         #         name=future.name, | ||||||
|  |         #     )(future.handler) | ||||||
|  |         #     if _routes: | ||||||
|  |         #         routes += _routes | ||||||
|  |  | ||||||
|         # Exceptions |         # # Static Files | ||||||
|         for future in self.exceptions: |         # for future in self.statics: | ||||||
|             app.exception(*future.args, **future.kwargs)(future.handler) |         #     # Prepend the blueprint URI prefix if available | ||||||
|  |         #     uri = url_prefix + future.uri if url_prefix else future.uri | ||||||
|  |         #     _routes = app.static( | ||||||
|  |         #         uri, future.file_or_directory, *future.args, **future.kwargs | ||||||
|  |         #     ) | ||||||
|  |         #     if _routes: | ||||||
|  |         #         routes += _routes | ||||||
|  |  | ||||||
|  |         # route_names = [route.name for route in routes if route] | ||||||
|  |  | ||||||
|  |         # # Middleware | ||||||
|  |         # for future in self.middlewares: | ||||||
|  |         #     if future.args or future.kwargs: | ||||||
|  |         #         app.register_named_middleware( | ||||||
|  |         #             future.middleware, | ||||||
|  |         #             route_names, | ||||||
|  |         #             *future.args, | ||||||
|  |         #             **future.kwargs, | ||||||
|  |         #         ) | ||||||
|  |         #     else: | ||||||
|  |         #         app.register_named_middleware(future.middleware, route_names) | ||||||
|  |  | ||||||
|  |         # # Exceptions | ||||||
|  |         # for future in self.exceptions: | ||||||
|  |         #     app.exception(*future.args, **future.kwargs)(future.handler) | ||||||
|  |  | ||||||
|         # Event listeners |         # Event listeners | ||||||
|         for event, listeners in self.listeners.items(): |         for event, listeners in self.listeners.items(): | ||||||
|             for listener in listeners: |             for listener in listeners: | ||||||
|                 app.listener(event)(listener) |                 app.listener(event)(listener) | ||||||
|  |  | ||||||
|     def route( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         methods=frozenset({"GET"}), |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """Create a blueprint route from a decorated function. |  | ||||||
|  |  | ||||||
|         :param uri: endpoint at which the route will be accessible. |  | ||||||
|         :param methods: list of acceptable HTTP methods. |  | ||||||
|         :param host: IP Address of FQDN for the sanic server to use. |  | ||||||
|         :param strict_slashes: Enforce the API urls are requested with a |  | ||||||
|             training */* |  | ||||||
|         :param stream: If the route should provide a streaming support |  | ||||||
|         :param version: Blueprint Version |  | ||||||
|         :param name: Unique name to identify the Route |  | ||||||
|  |  | ||||||
|         :return a decorated method that when invoked will return an object |  | ||||||
|             of type :class:`FutureRoute` |  | ||||||
|         """ |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         def decorator(handler): |  | ||||||
|             route = FutureRoute( |  | ||||||
|                 handler, |  | ||||||
|                 uri, |  | ||||||
|                 methods, |  | ||||||
|                 host, |  | ||||||
|                 strict_slashes, |  | ||||||
|                 stream, |  | ||||||
|                 version, |  | ||||||
|                 name, |  | ||||||
|             ) |  | ||||||
|             self.routes.append(route) |  | ||||||
|             return handler |  | ||||||
|  |  | ||||||
|         return decorator |  | ||||||
|  |  | ||||||
|     def add_route( |  | ||||||
|         self, |  | ||||||
|         handler, |  | ||||||
|         uri, |  | ||||||
|         methods=frozenset({"GET"}), |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|         stream=False, |  | ||||||
|     ): |  | ||||||
|         """Create a blueprint route from a function. |  | ||||||
|  |  | ||||||
|         :param handler: function for handling uri requests. Accepts function, |  | ||||||
|                         or class instance with a view_class method. |  | ||||||
|         :param uri: endpoint at which the route will be accessible. |  | ||||||
|         :param methods: list of acceptable HTTP methods. |  | ||||||
|         :param host: IP Address of FQDN for the sanic server to use. |  | ||||||
|         :param strict_slashes: Enforce the API urls are requested with a |  | ||||||
|             training */* |  | ||||||
|         :param version: Blueprint Version |  | ||||||
|         :param name: user defined route name for url_for |  | ||||||
|         :param stream: boolean specifying if the handler is a stream handler |  | ||||||
|         :return: function or class instance |  | ||||||
|         """ |  | ||||||
|         # Handle HTTPMethodView differently |  | ||||||
|         if hasattr(handler, "view_class"): |  | ||||||
|             methods = set() |  | ||||||
|  |  | ||||||
|             for method in HTTP_METHODS: |  | ||||||
|                 if getattr(handler.view_class, method.lower(), None): |  | ||||||
|                     methods.add(method) |  | ||||||
|  |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         # handle composition view differently |  | ||||||
|         if isinstance(handler, CompositionView): |  | ||||||
|             methods = handler.handlers.keys() |  | ||||||
|  |  | ||||||
|         self.route( |  | ||||||
|             uri=uri, |  | ||||||
|             methods=methods, |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         )(handler) |  | ||||||
|         return handler |  | ||||||
|  |  | ||||||
|     def websocket( |  | ||||||
|         self, uri, host=None, strict_slashes=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """Create a blueprint websocket route from a decorated function. |  | ||||||
|  |  | ||||||
|         :param uri: endpoint at which the route will be accessible. |  | ||||||
|         :param host: IP Address of FQDN for the sanic server to use. |  | ||||||
|         :param strict_slashes: Enforce the API urls are requested with a |  | ||||||
|             training */* |  | ||||||
|         :param version: Blueprint Version |  | ||||||
|         :param name: Unique name to identify the Websocket Route |  | ||||||
|         """ |  | ||||||
|         if strict_slashes is None: |  | ||||||
|             strict_slashes = self.strict_slashes |  | ||||||
|  |  | ||||||
|         def decorator(handler): |  | ||||||
|             nonlocal uri |  | ||||||
|             nonlocal host |  | ||||||
|             nonlocal strict_slashes |  | ||||||
|             nonlocal version |  | ||||||
|             nonlocal name |  | ||||||
|  |  | ||||||
|             name = f"{self.name}.{name or handler.__name__}" |  | ||||||
|             route = FutureRoute( |  | ||||||
|                 handler, uri, [], host, strict_slashes, False, version, name |  | ||||||
|             ) |  | ||||||
|             self.websocket_routes.append(route) |  | ||||||
|             return handler |  | ||||||
|  |  | ||||||
|         return decorator |  | ||||||
|  |  | ||||||
|     def add_websocket_route( |  | ||||||
|         self, handler, uri, host=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """Create a blueprint websocket route from a function. |  | ||||||
|  |  | ||||||
|         :param handler: function for handling uri requests. Accepts function, |  | ||||||
|                         or class instance with a view_class method. |  | ||||||
|         :param uri: endpoint at which the route will be accessible. |  | ||||||
|         :param host: IP Address of FQDN for the sanic server to use. |  | ||||||
|         :param version: Blueprint Version |  | ||||||
|         :param name: Unique name to identify the Websocket Route |  | ||||||
|         :return: function or class instance |  | ||||||
|         """ |  | ||||||
|         self.websocket(uri=uri, host=host, version=version, name=name)(handler) |  | ||||||
|         return handler |  | ||||||
|  |  | ||||||
|     def listener(self, event): |     def listener(self, event): | ||||||
|         """Create a listener from a decorated function. |         """Create a listener from a decorated function. | ||||||
|  |  | ||||||
| @@ -395,186 +247,3 @@ class Blueprint: | |||||||
|  |  | ||||||
|         static = FutureStatic(uri, file_or_directory, args, kwargs) |         static = FutureStatic(uri, file_or_directory, args, kwargs) | ||||||
|         self.statics.append(static) |         self.statics.append(static) | ||||||
|  |  | ||||||
|     # Shorthand method decorators |  | ||||||
|     def get( |  | ||||||
|         self, uri, host=None, strict_slashes=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **GET** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **GET** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"GET"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def post( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **POST** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **POST** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"POST"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def put( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **PUT** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **PUT** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"PUT"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def head( |  | ||||||
|         self, uri, host=None, strict_slashes=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **HEAD** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **HEAD** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"HEAD"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def options( |  | ||||||
|         self, uri, host=None, strict_slashes=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **OPTIONS** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **OPTIONS** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"OPTIONS"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def patch( |  | ||||||
|         self, |  | ||||||
|         uri, |  | ||||||
|         host=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         stream=False, |  | ||||||
|         version=None, |  | ||||||
|         name=None, |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **PATCH** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **PATCH** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"PATCH"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             stream=stream, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def delete( |  | ||||||
|         self, uri, host=None, strict_slashes=None, version=None, name=None |  | ||||||
|     ): |  | ||||||
|         """ |  | ||||||
|         Add an API URL under the **DELETE** *HTTP* method |  | ||||||
|  |  | ||||||
|         :param uri: URL to be tagged to **DELETE** method of *HTTP* |  | ||||||
|         :param host: Host IP or FQDN for the service to use |  | ||||||
|         :param strict_slashes: Instruct :class:`sanic.app.Sanic` to check |  | ||||||
|             if the request URLs need to terminate with a */* |  | ||||||
|         :param version: API Version |  | ||||||
|         :param name: Unique name that can be used to identify the Route |  | ||||||
|         :return: Object decorated with :func:`route` method |  | ||||||
|         """ |  | ||||||
|         return self.route( |  | ||||||
|             uri, |  | ||||||
|             methods=frozenset({"DELETE"}), |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|         ) |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								sanic/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sanic/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										490
									
								
								sanic/mixins/routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										490
									
								
								sanic/mixins/routes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,490 @@ | |||||||
|  | from functools import partial | ||||||
|  | from inspect import signature | ||||||
|  | from typing import List, Set | ||||||
|  |  | ||||||
|  | import websockets | ||||||
|  |  | ||||||
|  | from sanic_routing.route import Route | ||||||
|  |  | ||||||
|  | from sanic.constants import HTTP_METHODS | ||||||
|  | from sanic.models.futures import FutureRoute | ||||||
|  | from sanic.views import CompositionView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RouteMixin: | ||||||
|  |     def __init__(self) -> None: | ||||||
|  |         self._future_routes: Set[Route] = set() | ||||||
|  |         self._future_websocket_routes: Set[Route] = set() | ||||||
|  |  | ||||||
|  |     def _apply_route(self, route: FutureRoute) -> Route: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def _route( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         methods=frozenset({"GET"}), | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         stream=False, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=False, | ||||||
|  |         apply=True, | ||||||
|  |         subprotocols=None, | ||||||
|  |         websocket=False, | ||||||
|  |     ): | ||||||
|  |         """Create a blueprint route from a decorated function. | ||||||
|  |  | ||||||
|  |         :param uri: endpoint at which the route will be accessible. | ||||||
|  |         :param methods: list of acceptable HTTP methods. | ||||||
|  |         :param host: IP Address of FQDN for the sanic server to use. | ||||||
|  |         :param strict_slashes: Enforce the API urls are requested with a | ||||||
|  |             training */* | ||||||
|  |         :param stream: If the route should provide a streaming support | ||||||
|  |         :param version: Blueprint Version | ||||||
|  |         :param name: Unique name to identify the Route | ||||||
|  |  | ||||||
|  |         :return a decorated method that when invoked will return an object | ||||||
|  |             of type :class:`FutureRoute` | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         if websocket: | ||||||
|  |             self.enable_websocket() | ||||||
|  |  | ||||||
|  |         # Fix case where the user did not prefix the URL with a / | ||||||
|  |         # and will probably get confused as to why it's not working | ||||||
|  |         if not uri.startswith("/"): | ||||||
|  |             uri = "/" + uri | ||||||
|  |  | ||||||
|  |         if strict_slashes is None: | ||||||
|  |             strict_slashes = self.strict_slashes | ||||||
|  |  | ||||||
|  |         def decorator(handler): | ||||||
|  |             nonlocal uri | ||||||
|  |             nonlocal methods | ||||||
|  |             nonlocal host | ||||||
|  |             nonlocal strict_slashes | ||||||
|  |             nonlocal stream | ||||||
|  |             nonlocal version | ||||||
|  |             nonlocal name | ||||||
|  |             nonlocal ignore_body | ||||||
|  |             nonlocal subprotocols | ||||||
|  |             nonlocal websocket | ||||||
|  |  | ||||||
|  |             if isinstance(handler, tuple): | ||||||
|  |                 # if a handler fn is already wrapped in a route, the handler | ||||||
|  |                 # variable will be a tuple of (existing routes, handler fn) | ||||||
|  |                 _, handler = handler | ||||||
|  |  | ||||||
|  |             if websocket: | ||||||
|  |                 websocket_handler = partial( | ||||||
|  |                     self._websocket_handler, | ||||||
|  |                     handler, | ||||||
|  |                     subprotocols=subprotocols, | ||||||
|  |                 ) | ||||||
|  |                 websocket_handler.__name__ = ( | ||||||
|  |                     "websocket_handler_" + handler.__name__ | ||||||
|  |                 ) | ||||||
|  |                 websocket_handler.is_websocket = True | ||||||
|  |                 handler = websocket_handler | ||||||
|  |  | ||||||
|  |             # TODO: | ||||||
|  |             # - THink this thru.... do we want all routes namespaced? | ||||||
|  |             # - | ||||||
|  |             name = self._generate_name(handler, name) | ||||||
|  |  | ||||||
|  |             route = FutureRoute( | ||||||
|  |                 handler, | ||||||
|  |                 uri, | ||||||
|  |                 methods, | ||||||
|  |                 host, | ||||||
|  |                 strict_slashes, | ||||||
|  |                 stream, | ||||||
|  |                 version, | ||||||
|  |                 name, | ||||||
|  |                 ignore_body, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             self._future_routes.add(route) | ||||||
|  |  | ||||||
|  |             args = list(signature(handler).parameters.keys()) | ||||||
|  |             if websocket and len(args) < 2: | ||||||
|  |                 handler_name = handler.__name__ | ||||||
|  |  | ||||||
|  |                 raise ValueError( | ||||||
|  |                     f"Required parameter `request` and/or `ws` missing " | ||||||
|  |                     f"in the {handler_name}() route?" | ||||||
|  |                 ) | ||||||
|  |             elif not args: | ||||||
|  |                 handler_name = handler.__name__ | ||||||
|  |  | ||||||
|  |                 raise ValueError( | ||||||
|  |                     f"Required parameter `request` missing " | ||||||
|  |                     f"in the {handler_name}() route?" | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |             if not websocket and stream: | ||||||
|  |                 handler.is_stream = stream | ||||||
|  |  | ||||||
|  |             if apply: | ||||||
|  |                 self._apply_route(route) | ||||||
|  |  | ||||||
|  |             return route, handler | ||||||
|  |  | ||||||
|  |         return decorator | ||||||
|  |  | ||||||
|  |     def route( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         methods=frozenset({"GET"}), | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         stream=False, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=False, | ||||||
|  |         apply=True, | ||||||
|  |     ): | ||||||
|  |         return self._route( | ||||||
|  |             uri=uri, | ||||||
|  |             methods=methods, | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             stream=stream, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             ignore_body=ignore_body, | ||||||
|  |             apply=apply, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def add_route( | ||||||
|  |         self, | ||||||
|  |         handler, | ||||||
|  |         uri, | ||||||
|  |         methods=frozenset({"GET"}), | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         stream=False, | ||||||
|  |     ): | ||||||
|  |         """A helper method to register class instance or | ||||||
|  |         functions as a handler to the application url | ||||||
|  |         routes. | ||||||
|  |  | ||||||
|  |         :param handler: function or class instance | ||||||
|  |         :param uri: path of the URL | ||||||
|  |         :param methods: list or tuple of methods allowed, these are overridden | ||||||
|  |                         if using a HTTPMethodView | ||||||
|  |         :param host: | ||||||
|  |         :param strict_slashes: | ||||||
|  |         :param version: | ||||||
|  |         :param name: user defined route name for url_for | ||||||
|  |         :param stream: boolean specifying if the handler is a stream handler | ||||||
|  |         :return: function or class instance | ||||||
|  |         """ | ||||||
|  |         # Handle HTTPMethodView differently | ||||||
|  |         if hasattr(handler, "view_class"): | ||||||
|  |             methods = set() | ||||||
|  |  | ||||||
|  |             for method in HTTP_METHODS: | ||||||
|  |                 _handler = getattr(handler.view_class, method.lower(), None) | ||||||
|  |                 if _handler: | ||||||
|  |                     methods.add(method) | ||||||
|  |                     if hasattr(_handler, "is_stream"): | ||||||
|  |                         stream = True | ||||||
|  |  | ||||||
|  |         # handle composition view differently | ||||||
|  |         if isinstance(handler, CompositionView): | ||||||
|  |             methods = handler.handlers.keys() | ||||||
|  |             for _handler in handler.handlers.values(): | ||||||
|  |                 if hasattr(_handler, "is_stream"): | ||||||
|  |                     stream = True | ||||||
|  |                     break | ||||||
|  |  | ||||||
|  |         if strict_slashes is None: | ||||||
|  |             strict_slashes = self.strict_slashes | ||||||
|  |  | ||||||
|  |         self.route( | ||||||
|  |             uri=uri, | ||||||
|  |             methods=methods, | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             stream=stream, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |         )(handler) | ||||||
|  |         return handler | ||||||
|  |  | ||||||
|  |     # Shorthand method decorators | ||||||
|  |     def get( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=True, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **GET** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **GET** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"GET"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             ignore_body=ignore_body, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def post( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         stream=False, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **POST** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **POST** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"POST"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             stream=stream, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def put( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         stream=False, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **PUT** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **PUT** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"PUT"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             stream=stream, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def head( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=True, | ||||||
|  |     ): | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"HEAD"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             ignore_body=ignore_body, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def options( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=True, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **OPTIONS** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **OPTIONS** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"OPTIONS"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             ignore_body=ignore_body, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def patch( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         stream=False, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **PATCH** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **PATCH** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"PATCH"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             stream=stream, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def delete( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         ignore_body=True, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         Add an API URL under the **DELETE** *HTTP* method | ||||||
|  |  | ||||||
|  |         :param uri: URL to be tagged to **DELETE** method of *HTTP* | ||||||
|  |         :param host: Host IP or FQDN for the service to use | ||||||
|  |         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||||
|  |             URLs need to terminate with a */* | ||||||
|  |         :param version: API Version | ||||||
|  |         :param name: Unique name that can be used to identify the Route | ||||||
|  |         :return: Object decorated with :func:`route` method | ||||||
|  |         """ | ||||||
|  |         return self.route( | ||||||
|  |             uri, | ||||||
|  |             methods=frozenset({"DELETE"}), | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             ignore_body=ignore_body, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def websocket( | ||||||
|  |         self, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |         subprotocols=None, | ||||||
|  |         apply: bool = True, | ||||||
|  |     ): | ||||||
|  |         """Create a blueprint websocket route from a decorated function. | ||||||
|  |  | ||||||
|  |         :param uri: endpoint at which the route will be accessible. | ||||||
|  |         :param host: IP Address of FQDN for the sanic server to use. | ||||||
|  |         :param strict_slashes: Enforce the API urls are requested with a | ||||||
|  |             training */* | ||||||
|  |         :param version: Blueprint Version | ||||||
|  |         :param name: Unique name to identify the Websocket Route | ||||||
|  |         """ | ||||||
|  |         return self._route( | ||||||
|  |             uri=uri, | ||||||
|  |             host=host, | ||||||
|  |             methods=None, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |             apply=apply, | ||||||
|  |             subprotocols=subprotocols, | ||||||
|  |             websocket=True, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def add_websocket_route( | ||||||
|  |         self, | ||||||
|  |         handler, | ||||||
|  |         uri, | ||||||
|  |         host=None, | ||||||
|  |         strict_slashes=None, | ||||||
|  |         subprotocols=None, | ||||||
|  |         version=None, | ||||||
|  |         name=None, | ||||||
|  |     ): | ||||||
|  |         """ | ||||||
|  |         A helper method to register a function as a websocket route. | ||||||
|  |  | ||||||
|  |         :param handler: a callable function or instance of a class | ||||||
|  |                         that can handle the websocket request | ||||||
|  |         :param host: Host IP or FQDN details | ||||||
|  |         :param uri: URL path that will be mapped to the websocket | ||||||
|  |                     handler | ||||||
|  |                     handler | ||||||
|  |         :param strict_slashes: If the API endpoint needs to terminate | ||||||
|  |                 with a "/" or not | ||||||
|  |         :param subprotocols: Subprotocols to be used with websocket | ||||||
|  |                 handshake | ||||||
|  |         :param name: A unique name assigned to the URL so that it can | ||||||
|  |                 be used with :func:`url_for` | ||||||
|  |         :return: Objected decorated by :func:`websocket` | ||||||
|  |         """ | ||||||
|  |         if strict_slashes is None: | ||||||
|  |             strict_slashes = self.strict_slashes | ||||||
|  |  | ||||||
|  |         return self.websocket( | ||||||
|  |             uri, | ||||||
|  |             host=host, | ||||||
|  |             strict_slashes=strict_slashes, | ||||||
|  |             subprotocols=subprotocols, | ||||||
|  |             version=version, | ||||||
|  |             name=name, | ||||||
|  |         )(handler) | ||||||
|  |  | ||||||
|  |     def _generate_name(self, handler, name: str) -> str: | ||||||
|  |         return name or handler.__name__ | ||||||
							
								
								
									
										0
									
								
								sanic/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sanic/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										27
									
								
								sanic/models/futures.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								sanic/models/futures.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | from collections import namedtuple | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FutureRoute = namedtuple( | ||||||
|  |     "FutureRoute", | ||||||
|  |     [ | ||||||
|  |         "handler", | ||||||
|  |         "uri", | ||||||
|  |         "methods", | ||||||
|  |         "host", | ||||||
|  |         "strict_slashes", | ||||||
|  |         "stream", | ||||||
|  |         "version", | ||||||
|  |         "name", | ||||||
|  |         "ignore_body", | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | FutureListener = namedtuple( | ||||||
|  |     "FutureListener", ["handler", "uri", "methods", "host"] | ||||||
|  | ) | ||||||
|  | FutureMiddleware = namedtuple( | ||||||
|  |     "FutureMiddleware", ["middleware", "args", "kwargs"] | ||||||
|  | ) | ||||||
|  | FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"]) | ||||||
|  | FutureStatic = namedtuple( | ||||||
|  |     "FutureStatic", ["uri", "file_or_directory", "args", "kwargs"] | ||||||
|  | ) | ||||||
| @@ -40,6 +40,7 @@ class Router(BaseRouter): | |||||||
|         handler, |         handler, | ||||||
|         host=None, |         host=None, | ||||||
|         strict_slashes=False, |         strict_slashes=False, | ||||||
|  |         stream=False, | ||||||
|         ignore_body=False, |         ignore_body=False, | ||||||
|         version=None, |         version=None, | ||||||
|         name=None, |         name=None, | ||||||
| @@ -48,6 +49,7 @@ class Router(BaseRouter): | |||||||
|         # - host |         # - host | ||||||
|         # - strict_slashes |         # - strict_slashes | ||||||
|         # - ignore_body |         # - ignore_body | ||||||
|  |         # - stream | ||||||
|         if version is not None: |         if version is not None: | ||||||
|             version = str(version).strip("/").lstrip("v") |             version = str(version).strip("/").lstrip("v") | ||||||
|             uri = "/".join([f"/v{version}", uri.lstrip("/")]) |             uri = "/".join([f"/v{version}", uri.lstrip("/")]) | ||||||
| @@ -56,5 +58,6 @@ class Router(BaseRouter): | |||||||
|             path=uri, handler=handler, methods=methods, name=name |             path=uri, handler=handler, methods=methods, name=name | ||||||
|         ) |         ) | ||||||
|         route.ctx.ignore_body = ignore_body |         route.ctx.ignore_body = ignore_body | ||||||
|  |         route.ctx.stream = stream | ||||||
|  |  | ||||||
|         return route |         return route | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Adam Hopkins
					Adam Hopkins